-- ----------------------------------------------------------------------------- -- | -- Module : Text.IPv6Addr -- Copyright : (c) Michel Boucey 2011-2013 -- License : BSD-Style -- Maintainer : michel.boucey@gmail.com -- Stability : provisional -- -- Dealing with IPv6 address text representations, canonization and manipulations. -- -- ----------------------------------------------------------------------------- module Text.IPv6Addr.Internal ( colon , doubleColon , sixteenBits , ipv4Addr , expandTokens , maybeIPv6AddrToken , maybeIPv6AddrTokens , ipv4AddrToIPv6AddrTokens , ipv6TokensToText , ipv6TokensToIPv6Addr , isIPv6Addr , maybeTokIPv6Addr , maybeTokPureIPv6Addr , fromDoubleColon , toDoubleColon , networkInterfacesIPv6AddrList ) where import Control.Monad (replicateM) import Data.Char (isDigit,isHexDigit,toLower) import Data.Function (on) import Data.List (group,isSuffixOf,elemIndex,elemIndices,intersperse) import Numeric (showHex) import qualified Data.Text as T import Data.Text.Read (decimal) import Data.Maybe (fromJust,isJust) import Network.Info import Text.IPv6Addr.Types tokdot = T.pack "." tokcolon = T.pack ":" tokdcolon = T.pack "::" tok0 = T.pack "0" tok4x0 = T.pack "0000" tok1 = T.pack "1" tokffff = T.pack "ffff" tok64 = T.pack "64" tokff9b = T.pack "ff9b" tokfe80 = T.pack "fe80" tok5efe = T.pack "5efe" tok200 = T.pack "200" tokenizedBy :: Char -> T.Text -> [T.Text] tokenizedBy c = T.groupBy ((==) `on` (== c)) -- -- Validation of IPv6 address tokens -- dot :: T.Text -> Maybe IPv4AddrToken dot t | t == tokdot = Just Dot | otherwise = Nothing colon :: T.Text -> Maybe IPv6AddrToken colon t | t == tokcolon = Just Colon | otherwise = Nothing doubleColon :: T.Text -> Maybe IPv6AddrToken doubleColon t | t == tokdcolon = Just DoubleColon | otherwise = Nothing sixteenBits:: T.Text -> Maybe IPv6AddrToken eightBitsToken :: T.Text -> Maybe IPv4AddrToken eightBitsToken t = case decimal t of Right p -> do let i = fst p if i >= 0 && i <= 255 && snd p == T.empty then Just (EightBits t) else Nothing Left _ -> Nothing ipv4Token :: T.Text -> Maybe IPv4AddrToken ipv4Token t | isJust(dot t) = Just Dot | isJust(eightBitsToken t) = Just (EightBits t) | otherwise = Nothing ipv4Addr :: T.Text -> Maybe IPv6AddrToken ipv4Addr t = do let r = map ipv4Token $ tokenizedBy '.' t if (Nothing `notElem` r) && (length r == 7) then Just (IPv4Addr t) else Nothing sixteenBits t = if T.length t < 5 then do -- "Leading zeros MUST be suppressed" (RFC 5952, 4.1) let t'= T.dropWhile (=='0') t if T.length t' < 5 && T.all isHexDigit t' then -- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3) Just (if T.null t' then AllZeros else SixteenBits $ T.toLower t') else Nothing else Nothing expandTokens :: [IPv6AddrToken] -> [IPv6AddrToken] expandTokens = map expTok where expTok AllZeros = SixteenBits tok4x0 expTok (SixteenBits s) = do let ls = T.length s SixteenBits (if ls < 4 then T.replicate (4 - ls) tok0 `T.append` s else s) expTok t = t -- | Returns 'Just' one of the valid 'IPv6AddrToken', or 'Nothing'. maybeIPv6AddrToken :: T.Text -> Maybe IPv6AddrToken maybeIPv6AddrToken t | isJust t' = t' | isJust(colon t) = Just Colon | isJust(doubleColon t) = Just DoubleColon | isJust(ipv4Addr t) = Just (IPv4Addr t) | otherwise = Nothing where t' = sixteenBits t -- | Returns the corresponding 'Text' of an IPv6 address token. ipv6TokenToText :: IPv6AddrToken -> T.Text ipv6TokenToText (SixteenBits s) = s ipv6TokenToText Colon = tokcolon ipv6TokenToText DoubleColon = tokdcolon -- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1) ipv6TokenToText AllZeros = tok0 ipv6TokenToText (IPv4Addr a) = a -- | Given an arbitrary list of 'IPv6AddrToken', returns the corresponding 'Text'. ipv6TokensToText :: [IPv6AddrToken] -> T.Text ipv6TokensToText l = T.concat $ map ipv6TokenToText l -- | Returns 'True' if a list of 'IPv6AddrToken' constitutes a valid IPv6 Address. isIPv6Addr :: [IPv6AddrToken] -> Bool isIPv6Addr [] = False isIPv6Addr [DoubleColon] = True isIPv6Addr [DoubleColon,SixteenBits tok1] = True isIPv6Addr tks = diffNext tks && (do let cdctks = countDoubleColon tks let lentks = length tks let lasttk = last tks let lenconst = (lentks == 15 && cdctks == 0) || (lentks < 15 && cdctks == 1) firstValidToken tks && (case countIPv4Addr tks of 0 -> case lasttk of SixteenBits _ -> lenconst DoubleColon -> lenconst AllZeros -> lenconst otherwise -> False 1 -> case lasttk of IPv4Addr _ -> (lentks == 13 && cdctks == 0) || (lentks < 12 && cdctks == 1) otherwise -> False otherwise -> False)) where diffNext [_] = True diffNext [a,a'] = a /= a' diffNext (a:as) = (a /= head as) && diffNext as firstValidToken l = case head l of SixteenBits _ -> True DoubleColon -> True AllZeros -> True otherwise -> False countDoubleColon l = length $ elemIndices DoubleColon l countIPv4Addr = foldr oneMoreIPv4Addr 0 where oneMoreIPv4Addr t c = case t of IPv4Addr _ -> c + 1 otherwise -> c -- | Returns 'Just' a list of 'IPv6AddrToken', or 'Nothing'. maybeIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken] maybeIPv6AddrTokens t = mapM maybeIPv6AddrToken $ tokenizedBy ':' t -- | This is the main function which returns 'Just' the list of a tokenized IPv6 -- address text representation validated against RFC 4291 and canonized -- in conformation with RFC 5952, or 'Nothing'. maybeTokIPv6Addr :: T.Text -> Maybe [IPv6AddrToken] maybeTokIPv6Addr t = do ltks <- maybeIPv6AddrTokens t if isIPv6Addr ltks then Just $ (ipv4AddrReplacement . toDoubleColon . fromDoubleColon) ltks else Nothing where ipv4AddrReplacement ltks' = if ipv4AddrRewrite ltks' then init ltks' ++ ipv4AddrToIPv6AddrTokens (last ltks') else ltks' -- | Returns 'Just' the list of tokenized pure IPv6 address, always rewriting an -- embedded IPv4 address if present. maybeTokPureIPv6Addr :: T.Text -> Maybe [IPv6AddrToken] maybeTokPureIPv6Addr t = do ltks <- maybeIPv6AddrTokens t if isIPv6Addr ltks then Just $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) ltks else Nothing where ipv4AddrReplacement ltks' = init ltks' ++ ipv4AddrToIPv6AddrTokens (last ltks') -- | An embedded IPv4 address have to be rewritten to output a pure IPv6 Address -- text representation in hexadecimal digits. But some well-known prefixed IPv6 -- addresses have to keep visible in their text representation the fact that -- they deals with IPv4 to IPv6 transition process (RFC 5952 Section 5): -- -- IPv4-compatible IPv6 address like "::1.2.3.4" -- -- IPv4-mapped IPv6 address like "::ffff:1.2.3.4" -- -- IPv4-translated address like "::ffff:0:1.2.3.4" -- -- IPv4-translatable address like "64:ff9b::1.2.3.4" -- -- ISATAP address like "fe80::5efe:1.2.3.4" -- ipv4AddrRewrite :: [IPv6AddrToken] -> Bool ipv4AddrRewrite tks = case last tks of IPv4Addr _ -> do let itks = init tks not (itks == [DoubleColon] || itks == [DoubleColon,SixteenBits tokffff,Colon] || itks == [DoubleColon,SixteenBits tokffff,Colon,AllZeros,Colon] || itks == [SixteenBits tok64,Colon,SixteenBits tokff9b,DoubleColon] || [SixteenBits tok200,Colon,SixteenBits tok5efe,Colon] `isSuffixOf` itks || [AllZeros,Colon,SixteenBits tok5efe,Colon] `isSuffixOf` itks || [DoubleColon,SixteenBits tok5efe,Colon] `isSuffixOf` itks) otherwise -> False -- | Rewrites 'Just' an embedded 'IPv4Addr' into the corresponding list of pure -- 'IPv6Addr' tokens. -- -- > ipv4AddrToIPv6AddrTokens (IPv4Addr "127.0.0.1") == [SixteenBits "7f0",Colon,SixteenBits "1"] -- ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken] ipv4AddrToIPv6AddrTokens t = case t of IPv4Addr a -> do let m = toHex a [fromJust $ sixteenBits ((!!) m 0 `T.append` addZero ((!!) m 1)) ,Colon ,fromJust $ sixteenBits ((!!) m 2 `T.append` addZero ((!!) m 3))] _ -> [t] where toHex a = map (\x -> T.pack $ showHex (read (T.unpack x)::Int) "") $ T.split (=='.') a addZero d = if T.length d == 1 then tok0 `T.append` d else d fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken] fromDoubleColon tks = if DoubleColon `notElem` tks then tks else do let s = splitAt (fromJust $ elemIndex DoubleColon tks) tks let fsts = fst s let snds = if not (null (snd s)) then tail(snd s) else [] -- let snds = if length(snd s) >= 1 then tail(snd s) else [] let fste = if null fsts then [] else fsts ++ [Colon] let snde = if null snds then [] else Colon : snds fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace tks) ++ snde where quantityOfAllZerosTokenToReplace x = ntks tks - foldl (\c x -> if (x /= DoubleColon) && (x /= Colon) then c+1 else c) 0 x where ntks tks = if countIPv4Addr tks == 1 then 7 else 8 allZerosTokensReplacement x = intersperse Colon (replicate x AllZeros) toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken] toDoubleColon tks = zerosToDoubleColon tks (zerosRunToReplace $ zerosRunsList tks) where zerosToDoubleColon :: [IPv6AddrToken] -> (Int,Int) -> [IPv6AddrToken] -- No all zeros token, so no double colon replacement... zerosToDoubleColon ls (_,0) = ls -- "The symbol '::' MUST NOT be used to shorten just one 16-bit 0 field" (RFC 5952 4.2.2) zerosToDoubleColon ls (_,1) = ls zerosToDoubleColon ls (i,l) = let ls' = filter (/= Colon) ls in intersperse Colon (take i ls') ++ [DoubleColon] ++ intersperse Colon (drop (i+l) ls') zerosRunToReplace t = let l = longestLengthZerosRun t in (firstLongestZerosRunIndex t l,l) where firstLongestZerosRunIndex x y = sum . snd . unzip $ takeWhile (/=(True,y)) x longestLengthZerosRun x = maximum $ map longest x where longest t = case t of (True,i) -> i otherwise -> 0 zerosRunsList x = map helper $ groupZerosRuns x where helper h = (head h == AllZeros, lh) where lh = length h groupZerosRuns = group . filter (/= Colon) ipv6TokensToIPv6Addr :: [IPv6AddrToken] -> Maybe IPv6Addr ipv6TokensToIPv6Addr l = Just $ IPv6Addr $ ipv6TokensToText l networkInterfacesIPv6AddrList :: IO [(String,IPv6)] networkInterfacesIPv6AddrList = getNetworkInterfaces >>= \n -> return $ map networkInterfacesIPv6Addr n where networkInterfacesIPv6Addr (NetworkInterface n _ a _) = (n,a)