diff --git a/hw-ip.cabal b/hw-ip.cabal
--- a/hw-ip.cabal
+++ b/hw-ip.cabal
@@ -1,65 +1,64 @@
--- This file has been generated from package.yaml by hpack version 0.18.1.
---
--- see: https://github.com/sol/hpack
-
-name:           hw-ip
-version:        0.4.2
-synopsis:       Library for manipulating IP addresses and CIDR blocks
-description:    Please see README.md
-category:       Network
-homepage:       https://github.com/haskell-works/hw-ip#readme
-bug-reports:    https://github.com/haskell-works/hw-ip/issues
-author:         John Ky,
-                David Turnbull,
-                Jian Wan
-maintainer:     newhoggy@gmail.com
-copyright:      2018 John Ky
-license:        BSD3
-license-file:   LICENSE
-build-type:     Simple
-cabal-version:  >= 1.10
-
+cabal-version: 1.12
+name: hw-ip
+version: 1.0.0.0
+license: BSD3
+license-file: LICENSE
+copyright: 2018 John Ky, David Turnbull, Jian Wan
+maintainer: newhoggy@gmail.com
+author: John Ky,
+        David Turnbull,
+        Jian Wan
+homepage: https://github.com/haskell-works/hw-ip#readme
+bug-reports: https://github.com/haskell-works/hw-ip/issues
+synopsis: Library for manipulating IP addresses and CIDR blocks
+description:
+    Please see README.md
+category: Network
+build-type: Simple
 extra-source-files:
     README.md
 
 source-repository head
-  type: git
-  location: https://github.com/haskell-works/hw-ip
+    type: git
+    location: https://github.com/haskell-works/hw-ip
 
 library
-  hs-source-dirs:
-      src
-  build-depends:
-      attoparsec
-    , generic-lens
-    , hw-bits
-    , text
-    , base >=4.7 && <5
-  exposed-modules:
+    exposed-modules:
       HaskellWorks.Data.Network.Ip
+      HaskellWorks.Data.Network.Ip.Ip
+      HaskellWorks.Data.Network.Ip.Ipv6
       HaskellWorks.Data.Network.Ip.Internal
-      HaskellWorks.Data.Network.Ip.Parser.Text
+      HaskellWorks.Data.Network.Ip.Ipv4
       HaskellWorks.Data.Network.Ip.Type
-  other-modules:
-      Paths_hw_ip
-  default-language: Haskell2010
+    hs-source-dirs: src
+    other-modules:
+        Paths_hw_ip
+    default-language: Haskell2010
+    build-depends:
+        base            >= 4            && < 4.13
+      , appar           >= 0.1.7        && < 0.2
+      , attoparsec      >= 0.13.2.2     && < 0.14
+      , generic-lens    >= 0.5.1.0      && < 1.2
+      , hw-bits         >= 0.7.0.2      && < 0.8
+      , iproute         >= 1.7.3        && < 1.8
+      , text            >= 1.2.2.2      && < 1.3
 
 test-suite hw-ip-test
-  type: exitcode-stdio-1.0
-  main-is: Spec.hs
-  hs-source-dirs:
-      test
-  ghc-options: -threaded -rtsopts -with-rtsopts=-N
-  build-depends:
-      attoparsec
-    , generic-lens
-    , hw-bits
-    , text
-    , base
-    , hedgehog
-    , hw-hspec-hedgehog
-    , hw-ip
-    , hspec
-  other-modules:
-      HaskellWorks.Data.Network.IpSpec
-  default-language: Haskell2010
+    type: exitcode-stdio-1.0
+    main-is: Spec.hs
+    hs-source-dirs: test
+    other-modules:
+        HaskellWorks.Data.Network.IpSpec
+        Paths_hw_ip
+    default-language: Haskell2010
+    ghc-options: -threaded -rtsopts -with-rtsopts=-N
+    build-depends:
+        base              >= 4          && < 4.13
+      , attoparsec
+      , generic-lens
+      , hedgehog          >= 0.5.3      && < 0.7
+      , hspec             >= 2.4.4      && < 2.6
+      , hw-bits
+      , hw-hspec-hedgehog >= 0.1.0.2    && < 0.2
+      , hw-ip
+      , text
diff --git a/src/HaskellWorks/Data/Network/Ip.hs b/src/HaskellWorks/Data/Network/Ip.hs
--- a/src/HaskellWorks/Data/Network/Ip.hs
+++ b/src/HaskellWorks/Data/Network/Ip.hs
@@ -1,73 +1,9 @@
-{-# LANGUAGE BangPatterns          #-}
-{-# LANGUAGE DuplicateRecordFields #-}
-
 module HaskellWorks.Data.Network.Ip
-  ( Z.Ipv4Address(Ipv4Address)
-  , Z.Ipv4NetMask(Ipv4NetMask)
-  , Z.Ipv4Block(Ipv4Block)
-  , bitPower
-  , blockSize
-  , isCanonical
-  , splitBlock
-  , textToMaybeIpv4Address
-  , ipv4AddressToString
-  , ipv4AddressToText
-  , ipv4AddressToWords
-  , firstIpv4Address
-  , lastIpv4Address
+  ( module X
   ) where
 
-import Control.Monad
-import Data.Word
-import GHC.Generics
-import HaskellWorks.Data.Bits.BitWise
-
-import qualified Data.Attoparsec.Text                     as AP
-import qualified Data.Text                                as T
-import qualified HaskellWorks.Data.Network.Ip.Internal    as I
-import qualified HaskellWorks.Data.Network.Ip.Parser.Text as APT
-import qualified HaskellWorks.Data.Network.Ip.Type        as Z
-
-bitPower :: Z.Ipv4NetMask -> Word64
-bitPower (Z.Ipv4NetMask m) = fromIntegral (32 - m)
-
-isCanonical :: Z.Ipv4Block -> Bool
-isCanonical (Z.Ipv4Block (Z.Ipv4Address b) m) = ((b .>. bitPower m) .<. bitPower m) == b
-
-splitBlock :: Z.Ipv4Block -> Maybe (Z.Ipv4Block, Z.Ipv4Block)
-splitBlock (Z.Ipv4Block (Z.Ipv4Address b) (Z.Ipv4NetMask m)) =
-  if m >= 0 && m < 32
-    then  let !hm       = m + 1
-              !halfMask = Z.Ipv4NetMask hm
-              !c        = fromIntegral ((0x100000000 :: Word64) .>. fromIntegral (m + 1))
-          in  Just
-              ( Z.Ipv4Block (Z.Ipv4Address  b     ) halfMask
-              , Z.Ipv4Block (Z.Ipv4Address (b + c)) halfMask
-              )
-    else  Nothing
-
-blockSize :: Z.Ipv4Block -> Int
-blockSize (Z.Ipv4Block _ m) = 2 ^ bitPower m
-
-firstIpv4Address :: Z.Ipv4Block -> Z.Ipv4Address
-firstIpv4Address (Z.Ipv4Block base _) = base
-
-lastIpv4Address :: Z.Ipv4Block -> Z.Ipv4Address
-lastIpv4Address b@(Z.Ipv4Block (Z.Ipv4Address base) _) = Z.Ipv4Address (base + fromIntegral (blockSize b) - 1)
-
-textToMaybeIpv4Address :: T.Text -> Maybe Z.Ipv4Address
-textToMaybeIpv4Address t = AP.maybeResult =<< AP.parseWith (return mempty) APT.ipv4Address t
-
-ipv4AddressToString :: Z.Ipv4Address -> String
-ipv4AddressToString = show
-
-ipv4AddressToText :: Z.Ipv4Address -> T.Text
-ipv4AddressToText = T.pack . ipv4AddressToString
-
-ipv4AddressToWords :: Z.Ipv4Address -> (Word8, Word8, Word8, Word8)
-ipv4AddressToWords (Z.Ipv4Address w) =
-  ( fromIntegral (w .>. 24) .&. 0xff
-  , fromIntegral (w .>. 16) .&. 0xff
-  , fromIntegral (w .>.  8) .&. 0xff
-  , fromIntegral (w         .&. 0xff)
-  )
+import HaskellWorks.Data.Network.Ip.Internal as X
+import HaskellWorks.Data.Network.Ip.Ip       as X
+import HaskellWorks.Data.Network.Ip.Ipv4     as X
+import HaskellWorks.Data.Network.Ip.Ipv6     as X
+import HaskellWorks.Data.Network.Ip.Type     as X
diff --git a/src/HaskellWorks/Data/Network/Ip/Internal.hs b/src/HaskellWorks/Data/Network/Ip/Internal.hs
--- a/src/HaskellWorks/Data/Network/Ip/Internal.hs
+++ b/src/HaskellWorks/Data/Network/Ip/Internal.hs
@@ -29,6 +29,9 @@
   <|>    ds 1 9 #<*># ds 0 9
   <|>    ds 0 9
 
+whitespace :: AP.Parser ()
+whitespace = void $ many (AP.satisfy isSpace)
+
 ipv4Address :: AP.Parser Word32
 ipv4Address = fourOctetsToWord32
   <$> (octet <* AP.char '.')
@@ -36,9 +39,6 @@
   <*> (octet <* AP.char '.')
   <*>  octet
 
-whitespace :: AP.Parser ()
-whitespace = void $ many (AP.satisfy isSpace)
-
 ipv4NetMask :: AP.Parser Word8
 ipv4NetMask =  d 3   #<*># ds 0 2
   <|>          d 2   #<*># ds 0 9
@@ -57,3 +57,6 @@
   _    <- AP.char '/'
   mask <- ipv4NetMask
   return (addr, mask)
+
+word32x4ToWords :: (Word32, Word32, Word32, Word32) -> [Word32]
+word32x4ToWords (a, b, c, d) = [a, b, c, d]
diff --git a/src/HaskellWorks/Data/Network/Ip/Ip.hs b/src/HaskellWorks/Data/Network/Ip/Ip.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Network/Ip/Ip.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE DuplicateRecordFields      #-}
+{-# LANGUAGE InstanceSigs               #-}
+{-# LANGUAGE OverloadedStrings          #-}
+
+module HaskellWorks.Data.Network.Ip.Ip
+  ( IpBlock(..)
+  , isValidIpBlock
+  , firstAddress
+  , lastAddress
+  ) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Char
+import Data.Maybe
+import Data.Word
+import GHC.Generics
+import HaskellWorks.Data.Bits.BitWise
+import Text.Read
+
+import qualified Data.Attoparsec.Text                  as AP
+import qualified Data.Bits                             as B
+import qualified Data.Text                             as T
+import qualified HaskellWorks.Data.Network.Ip.Internal as I
+import qualified HaskellWorks.Data.Network.Ip.Ipv4     as I4
+import qualified HaskellWorks.Data.Network.Ip.Ipv6     as I6
+import qualified Text.ParserCombinators.ReadPrec       as RP
+
+data IpBlock = IpBlockV4 I4.Ipv4Block | IpBlockV6 I6.Ipv6Block
+  deriving (Eq, Ord, Generic)
+
+instance Show IpBlock where
+  showsPrec _ (IpBlockV4 a) = shows a
+  showsPrec _ (IpBlockV6 a) = shows a
+
+instance Read IpBlock where
+  readsPrec _ s =
+    case readMaybe s :: Maybe I4.Ipv4Block of
+      Just ipv4 -> [(IpBlockV4 ipv4, "")]
+
+      Nothing ->
+        case readMaybe s :: Maybe I6.Ipv6Block of
+          Just ipv6 -> [(IpBlockV6 ipv6, "")]
+          Nothing   -> []
+
+isValidIpBlock :: IpBlock -> Bool
+isValidIpBlock (IpBlockV4 b) = I4.isValidIpv4Block b
+isValidIpBlock (IpBlockV6 b) = I6.isValidIpv6Block b
+
+firstAddress :: IpBlock -> (Word32, Word32, Word32, Word32)
+firstAddress (IpBlockV4 i4b)                 = firstAddress (IpBlockV6 (I6.ipv4BlockToMappedIpv6Block i4b))
+firstAddress (IpBlockV6 (I6.Ipv6Block ip _)) = I6.words ip
+
+lastAddress :: IpBlock -> (Word32, Word32, Word32, Word32)
+lastAddress (IpBlockV4 ib) = (0, 0, 0xFFFF, I4.word (I4.lastIpv4Address ib))
+lastAddress (IpBlockV6 (I6.Ipv6Block ip (I6.Ipv6NetMask msk))) =
+    let (w1, w2, w3, w4) = I6.words ip
+        lt = I6.masksIpv6 $ fromIntegral msk
+        w1' = w1 .|. (lt !! 0)
+        w2' = w2 .|. (lt !! 1)
+        w3' = w3 .|. (lt !! 2)
+        w4' = w4 .|. (lt !! 3) in
+      (w1', w2', w3', w4')
diff --git a/src/HaskellWorks/Data/Network/Ip/Ipv4.hs b/src/HaskellWorks/Data/Network/Ip/Ipv4.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Network/Ip/Ipv4.hs
@@ -0,0 +1,140 @@
+{-# LANGUAGE BangPatterns               #-}
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE DuplicateRecordFields      #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE InstanceSigs               #-}
+
+module HaskellWorks.Data.Network.Ip.Ipv4
+  ( Ipv4Address(..)
+  , Ipv4NetMask(..)
+  , Ipv4Block(..)
+  , isValidIpv4Block
+  , bitPower
+  , blockSize
+  , isCanonical
+  , splitBlock
+  , textToMaybeIpv4Address
+  , parseIpv4Address
+  , ipv4AddressToString
+  , ipv4AddressToText
+  , ipv4AddressToWords
+  , firstIpv4Address
+  , lastIpv4Address
+  ) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Char
+import Data.Maybe
+import Data.Word
+import GHC.Generics
+import HaskellWorks.Data.Bits.BitWise
+import Text.Read
+
+import qualified Data.Attoparsec.Text                  as AP
+import qualified Data.Bits                             as B
+import qualified Data.Text                             as T
+import qualified HaskellWorks.Data.Network.Ip.Internal as I
+import qualified Text.ParserCombinators.ReadPrec       as RP
+
+newtype Ipv4Address = Ipv4Address
+  { word :: Word32
+  } deriving (Enum, Eq, Ord, Generic)
+
+instance Show Ipv4Address where
+  showsPrec _ (Ipv4Address w) =
+    shows ((w .>. 24) .&. 0xff) . ('.':) .
+    shows ((w .>. 16) .&. 0xff) . ('.':) .
+    shows ((w .>.  8) .&. 0xff) . ('.':) .
+    shows ( w         .&. 0xff)
+
+instance Read Ipv4Address where
+  readsPrec :: Int -> String -> [(Ipv4Address, String)]
+  readsPrec _ s = case AP.parseWith (return mempty) (I.whitespace *> I.ipv4Address) (T.pack s) of
+    Just result -> case result of
+      AP.Done i r   -> [(Ipv4Address r, T.unpack i)]
+      AP.Partial _  -> []
+      AP.Fail a b c -> []
+    Nothing -> []
+
+newtype Ipv4NetMask = Ipv4NetMask
+  { word8 :: Word8
+  } deriving (Enum, Eq, Ord, Show, Generic)
+
+data Ipv4Block = Ipv4Block
+  { base :: !Ipv4Address
+  , mask :: !Ipv4NetMask
+  } deriving (Eq, Ord)
+
+instance Show Ipv4Block where
+  showsPrec _ (Ipv4Block b (Ipv4NetMask m)) = shows b . ('/':) . shows m
+
+instance Read Ipv4Block where
+  readsPrec :: Int -> String -> [(Ipv4Block, String)]
+  readsPrec _ s = case AP.parseWith (return mempty) (I.whitespace *> I.ipv4Block) (T.pack s) of
+    Just result -> case result of
+      AP.Done i (a, m) ->
+        case validIpv4Block $ Ipv4Block (Ipv4Address a) (Ipv4NetMask m) of
+          Just b  -> [(b, T.unpack i)]
+          Nothing -> []
+      AP.Partial _    -> []
+      AP.Fail a b c   -> []
+    Nothing -> []
+
+-- shift the address left by the amount of mask bits to reveal only host bits
+-- if any bits left are non-zero, then the mask is not big enough
+validIpv4Block :: Ipv4Block -> Maybe Ipv4Block
+validIpv4Block b@(Ipv4Block (Ipv4Address word) (Ipv4NetMask mask)) =
+  if word `B.shiftL` fromIntegral mask `B.xor` 0 == 0
+    then pure b
+    else Nothing
+
+isValidIpv4Block :: Ipv4Block -> Bool
+isValidIpv4Block = isJust . validIpv4Block
+
+firstIpv4Address :: Ipv4Block -> Ipv4Address
+firstIpv4Address (Ipv4Block base _) = base
+
+lastIpv4Address :: Ipv4Block -> Ipv4Address
+lastIpv4Address b@(Ipv4Block (Ipv4Address base) _) = Ipv4Address (base + fromIntegral (blockSize b) - 1)
+
+bitPower :: Ipv4NetMask -> Word64
+bitPower (Ipv4NetMask m) = fromIntegral (32 - m)
+
+isCanonical :: Ipv4Block -> Bool
+isCanonical (Ipv4Block (Ipv4Address b) m) = ((b .>. bitPower m) .<. bitPower m) == b
+
+splitBlock :: Ipv4Block -> Maybe (Ipv4Block, Ipv4Block)
+splitBlock (Ipv4Block (Ipv4Address b) (Ipv4NetMask m)) =
+  if m >= 0 && m < 32
+    then  let !hm       = m + 1
+              !halfMask = Ipv4NetMask hm
+              !c        = fromIntegral ((0x100000000 :: Word64) .>. fromIntegral (m + 1))
+          in  Just
+              ( Ipv4Block (Ipv4Address  b     ) halfMask
+              , Ipv4Block (Ipv4Address (b + c)) halfMask
+              )
+    else  Nothing
+
+blockSize :: Ipv4Block -> Int
+blockSize (Ipv4Block _ m) = 2 ^ bitPower m
+
+textToMaybeIpv4Address :: T.Text -> Maybe Ipv4Address
+textToMaybeIpv4Address t = AP.maybeResult =<< AP.parseWith (return mempty) parseIpv4Address t
+
+ipv4AddressToString :: Ipv4Address -> String
+ipv4AddressToString = show
+
+ipv4AddressToText :: Ipv4Address -> T.Text
+ipv4AddressToText = T.pack . ipv4AddressToString
+
+ipv4AddressToWords :: Ipv4Address -> (Word8, Word8, Word8, Word8)
+ipv4AddressToWords (Ipv4Address w) =
+  ( fromIntegral (w .>. 24) .&. 0xff
+  , fromIntegral (w .>. 16) .&. 0xff
+  , fromIntegral (w .>.  8) .&. 0xff
+  , fromIntegral (w         .&. 0xff)
+  )
+
+parseIpv4Address :: AP.Parser Ipv4Address
+parseIpv4Address = Ipv4Address <$> I.ipv4Address
diff --git a/src/HaskellWorks/Data/Network/Ip/Ipv6.hs b/src/HaskellWorks/Data/Network/Ip/Ipv6.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Network/Ip/Ipv6.hs
@@ -0,0 +1,124 @@
+{-# LANGUAGE DataKinds                  #-}
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE DuplicateRecordFields      #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE InstanceSigs               #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE TypeApplications           #-}
+
+module HaskellWorks.Data.Network.Ip.Ipv6
+  ( Ipv6Address(..)
+  , Ipv6NetMask(..)
+  , Ipv6Block(..)
+  , ipv4BlockToMappedIpv6Block
+  , parseIpv6Block
+  , masksIpv6
+  , isValidIpv6Block
+  ) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Bits
+import Data.Char
+import Data.Generics.Product.Any
+import Data.Maybe
+import Data.Word
+import GHC.Generics
+import Prelude                   hiding (words)
+import Text.Read
+
+import qualified Data.Attoparsec.Text                  as AP
+import qualified Data.Bits                             as B
+import qualified Data.IP                               as D
+import qualified Data.String                           as S
+import qualified Data.Text                             as T
+import qualified HaskellWorks.Data.Network.Ip.Internal as I
+import qualified HaskellWorks.Data.Network.Ip.Ipv4     as I4
+import qualified Text.ParserCombinators.ReadPrec       as RP
+
+newtype Ipv6Address = Ipv6Address
+  { words :: (Word32, Word32, Word32, Word32)
+  } deriving (Eq, Ord, Generic)
+
+instance Show Ipv6Address where
+  showsPrec _ (Ipv6Address w) = shows (D.fromHostAddress6 w)
+
+instance Read Ipv6Address where
+  readsPrec :: Int -> String -> [(Ipv6Address, String)]
+  readsPrec _ s =
+    case readMaybe s :: Maybe D.IPv6 of
+      Just ip -> [(Ipv6Address (D.toHostAddress6 ip), "")]
+      Nothing -> []
+
+newtype Ipv6NetMask = Ipv6NetMask
+  { word :: Word8
+  } deriving (Enum, Eq, Ord, Show, Generic)
+
+instance Read Ipv6NetMask where
+  readsPrec _ s =
+    case Ipv6NetMask <$> m of
+      Just maskv6 -> [(maskv6, "")]
+      Nothing     -> []
+    where
+      m = mfilter (\a -> a >= 0 && a <= 128) (readMaybe s)
+
+data Ipv6Block = Ipv6Block
+  { base :: !Ipv6Address
+  , mask :: !Ipv6NetMask
+  } deriving (Eq, Ord, Generic)
+
+instance Read Ipv6Block where
+  readsPrec _ s =
+    case T.unpack <$> T.split (== '/') (T.pack s) of
+      [addr, mask] ->
+        case readMaybe addr :: Maybe Ipv6Address of
+          Just ipv6 ->
+            case readMaybe mask of
+              Just maskv6 ->
+                let i6b = Ipv6Block ipv6 maskv6 in
+                  [(i6b, "") | isValidIpv6Block i6b]
+              Nothing     -> []
+          Nothing -> []
+      _ -> []
+
+instance Show Ipv6Block where
+  showsPrec _ (Ipv6Block b (Ipv6NetMask m))  = shows b . ('/':) . shows m
+
+parseIpv6Block :: T.Text -> Either T.Text Ipv6Block
+parseIpv6Block t =
+  case T.unpack <$> T.split (== '/') t of
+    [addr, mask] ->
+      case readMaybe addr :: Maybe Ipv6Address of
+        Just ipv6 ->
+          case readMaybe mask of
+            Just maskv6 -> Right $ Ipv6Block ipv6 maskv6
+            Nothing     -> Left "cannot read mask"
+        Nothing -> Left "cannot read addr"
+    _ -> Left "invalid input string"
+
+masksIpv6 :: Word8 -> [Word32]
+masksIpv6 m =
+  let e = 0xFFFFFFFF :: Word32
+      -- bits: number of bits which should be 1
+      maskValue bits = e `shiftR` (32 - bits) in
+    if m < 32 then
+      [maskValue (32 - fromIntegral m), e, e, e]
+    else if m < 64 then
+      [0, maskValue (64 - fromIntegral m), e, e]
+    else if m < 96 then
+      [0, 0, maskValue (96 - fromIntegral m), e]
+    else if m < 128 then
+      [0, 0, 0, maskValue (128 - fromIntegral m)]
+    else
+      [0, 0, 0, 0]
+
+isValidIpv6Block :: Ipv6Block -> Bool
+isValidIpv6Block (Ipv6Block b (Ipv6NetMask m)) =
+  let lt = masksIpv6 m
+      ipv6 = I.word32x4ToWords (words b) in
+    ipv6 == zipWith (.&.) ipv6 (zipWith xor ipv6 lt)
+
+ipv4BlockToMappedIpv6Block :: I4.Ipv4Block -> Ipv6Block
+ipv4BlockToMappedIpv6Block (I4.Ipv4Block b m) =
+  -- RFC-4291, "IPv4-Mapped IPv6 Address"
+  Ipv6Block (Ipv6Address (0, 0, 0xFFFF, I4.word b)) (Ipv6NetMask (96 + I4.word8 m))
diff --git a/src/HaskellWorks/Data/Network/Ip/Parser/Text.hs b/src/HaskellWorks/Data/Network/Ip/Parser/Text.hs
deleted file mode 100644
--- a/src/HaskellWorks/Data/Network/Ip/Parser/Text.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module HaskellWorks.Data.Network.Ip.Parser.Text
-  ( ipv4Address
-  ) where
-
-import qualified Data.Attoparsec.Text                  as AP
-import qualified HaskellWorks.Data.Network.Ip.Internal as I
-import qualified HaskellWorks.Data.Network.Ip.Type     as HW
-
-ipv4Address :: AP.Parser HW.Ipv4Address
-ipv4Address = HW.Ipv4Address <$> I.ipv4Address
diff --git a/src/HaskellWorks/Data/Network/Ip/Type.hs b/src/HaskellWorks/Data/Network/Ip/Type.hs
--- a/src/HaskellWorks/Data/Network/Ip/Type.hs
+++ b/src/HaskellWorks/Data/Network/Ip/Type.hs
@@ -1,75 +1,14 @@
-{-# LANGUAGE DeriveGeneric              #-}
 {-# LANGUAGE DuplicateRecordFields      #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE InstanceSigs               #-}
-{-# LANGUAGE OverloadedStrings          #-}
 
 module HaskellWorks.Data.Network.Ip.Type
-  ( Ipv4Address(..)
-  , Ipv4NetMask(..)
-  , Ipv4Block(..)
+  ( I4.Ipv4Address(..)
+  , I4.Ipv4NetMask(..)
+  , I4.Ipv4Block(..)
+  , I6.Ipv6Address(..)
+  , I6.Ipv6NetMask(..)
+  , I6.Ipv6Block(..)
   ) where
 
-import Data.Maybe
-import Data.Word
-import GHC.Generics
-import HaskellWorks.Data.Bits.BitWise
-import Control.Monad
-
-import qualified Data.Attoparsec.Text                  as AP
-import qualified Data.Bits                             as B
-import qualified Data.Text                             as T
-import qualified HaskellWorks.Data.Network.Ip.Internal as I
-import qualified Text.ParserCombinators.ReadPrec       as RP
-
-newtype Ipv4Address = Ipv4Address
-  { word :: Word32
-  } deriving (Enum, Eq, Ord, Generic)
-
-instance Show Ipv4Address where
-  showsPrec _ (Ipv4Address w) =
-    shows ((w .>. 24) .&. 0xff) . ('.':) .
-    shows ((w .>. 16) .&. 0xff) . ('.':) .
-    shows ((w .>.  8) .&. 0xff) . ('.':) .
-    shows ( w         .&. 0xff)
-
-instance Read Ipv4Address where
-  readsPrec :: Int -> String -> [(Ipv4Address, String)]
-  readsPrec _ s = case AP.parseWith (return mempty) (I.whitespace *> I.ipv4Address) (T.pack s) of
-    Just result -> case result of
-      AP.Done i r   -> [(Ipv4Address r, T.unpack i)]
-      AP.Partial _  -> []
-      AP.Fail a b c -> []
-    Nothing -> []
-
-newtype Ipv4NetMask = Ipv4NetMask
-  { word :: Word8
-  } deriving (Enum, Eq, Ord, Show, Generic)
-
-data Ipv4Block = Ipv4Block
-  { base :: !Ipv4Address
-  , mask :: !Ipv4NetMask
-  } deriving (Eq, Ord)
-
-instance Show Ipv4Block where
-  showsPrec _ (Ipv4Block b (Ipv4NetMask m)) = shows b . ('/':) . shows m
-
-instance Read Ipv4Block where
-  readsPrec :: Int -> String -> [(Ipv4Block, String)]
-  readsPrec _ s = case AP.parseWith (return mempty) (I.whitespace *> I.ipv4Block) (T.pack s) of
-    Just result -> case result of
-      AP.Done i (a, m) ->
-        case validIpv4Block $ Ipv4Block (Ipv4Address a) (Ipv4NetMask m) of
-          Just b  -> [(b, T.unpack i)]
-          Nothing -> []
-      AP.Partial _     -> []
-      AP.Fail a b c    -> []
-    Nothing -> []
-
--- shift the address left by the amount of mask bits to reveal only host bits
--- if any bits left are non-zero, then the mask is not big enough
-validIpv4Block :: Ipv4Block -> Maybe Ipv4Block
-validIpv4Block b@(Ipv4Block (Ipv4Address word) (Ipv4NetMask mask)) =
-  if word `B.shiftL` fromIntegral mask `B.xor` 0 == 0
-    then pure b
-    else Nothing
+import qualified HaskellWorks.Data.Network.Ip.Ipv4 as I4
+import qualified HaskellWorks.Data.Network.Ip.Ipv6 as I6
diff --git a/test/HaskellWorks/Data/Network/IpSpec.hs b/test/HaskellWorks/Data/Network/IpSpec.hs
--- a/test/HaskellWorks/Data/Network/IpSpec.hs
+++ b/test/HaskellWorks/Data/Network/IpSpec.hs
@@ -3,6 +3,7 @@
 module HaskellWorks.Data.Network.IpSpec (spec) where
 
 import HaskellWorks.Data.Network.Ip
+import HaskellWorks.Data.Network.Ip.Ipv4
 import HaskellWorks.Data.Network.Ip.Internal
 import HaskellWorks.Hspec.Hedgehog
 import Hedgehog
@@ -24,23 +25,23 @@
       AP.parseOnly octet (T.pack . show $ b) === Right b
 
   describe "Ipv4Address" $ do
-    it "should implement show" $ require $ property $ do
+    it "should implement show" $ requireTest $ do
       show (Ipv4Address 0x000000ff) === "0.0.0.255"
       show (Ipv4Address 0x0000ff00) === "0.0.255.0"
       show (Ipv4Address 0x00ff0000) === "0.255.0.0"
       show (Ipv4Address 0xff000000) === "255.0.0.0"
 
-    it "should implement read" $ require $ property $ do
+    it "should implement read" $ requireTest $ do
       read "1.2.3.4"      === Ipv4Address 0x01020304
       read "10.20.30.40"  === Ipv4Address 0x0a141e28
       read "1.2.3.12"     === Ipv4Address 0x0102030c
       read "1.2.3.160"    === Ipv4Address 0x010203a0
 
-    it "should be possible to extract the octets" $ require $ property $ do
+    it "should be possible to extract the octets" $ requireTest $ do
       ipv4AddressToWords (Ipv4Address 0x01020304) === (1, 2, 3, 4)
 
   describe "Ipv4Block" $ do
-    it "should implement show" $ require $ property $ do
+    it "should implement show" $ requireTest $ do
       show (Ipv4Block (Ipv4Address 0x000000ff) (Ipv4NetMask 32)) === "0.0.0.255/32"
       show (Ipv4Block (Ipv4Address 0x0000ff00) (Ipv4NetMask 32)) === "0.0.255.0/32"
       show (Ipv4Block (Ipv4Address 0x00ff0000) (Ipv4NetMask 32)) === "0.255.0.0/32"
@@ -60,22 +61,22 @@
       show (firstIpv4Address $ Ipv4Block (Ipv4Address 0xff000000) (Ipv4NetMask 21)) === "255.0.0.0"
       show (lastIpv4Address  $ Ipv4Block (Ipv4Address 0xff000000) (Ipv4NetMask 21)) === "255.0.7.255"
 
-    it "should implement read" $ require $ property $ do
+    it "should implement read" $ requireTest $ do
       read "1.0.0.0/8"  === Ipv4Block (Ipv4Address 0x01000000) (Ipv4NetMask 8)
       read "1.2.0.0/16" === Ipv4Block (Ipv4Address 0x01020000) (Ipv4NetMask 16)
       read "1.2.3.4/32" === Ipv4Block (Ipv4Address 0x01020304) (Ipv4NetMask 32)
 
-    it "should implement splitBlock" $ require $ property $ do
+    it "should implement splitBlock" $ requireTest $ do
       splitBlock (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask 32)) === Nothing
       splitBlock (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask 31)) === Just (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask 32), Ipv4Block (Ipv4Address 0x00000001) (Ipv4NetMask 32))
       splitBlock (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask 30)) === Just (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask 31), Ipv4Block (Ipv4Address 0x00000002) (Ipv4NetMask 31))
       splitBlock (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask  0)) === Just (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask  1), Ipv4Block (Ipv4Address 0x80000000) (Ipv4NetMask  1))
 
-    it "should implement blockSize" $ require $ property $ do
+    it "should implement blockSize" $ requireTest $ do
       blockSize (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask 32)) === 1
       blockSize (Ipv4Block (Ipv4Address 0x00000000) (Ipv4NetMask  0)) === 0x100000000
 
-    it "should validate masks" $ require $ property $ do
+    it "should validate masks" $ requireTest $ do
       (TR.readMaybe "1.2.3.4/8"  :: Maybe Ipv4Block) === Nothing
       (TR.readMaybe "1.2.3.4/0"  :: Maybe Ipv4Block) === Nothing
       (TR.readMaybe "1.2.3.4/32" :: Maybe Ipv4Block) === (Just $ Ipv4Block (Ipv4Address 0x01020304) (Ipv4NetMask 32))
