module Monky.IP.Raw
( IP(..)
, IP4
, IP6
, parseIP
, ipFromBS
, familyToNum
, AddressFamily(..)
, getAddrFamily
)
where
import Data.ByteString (ByteString, useAsCStringLen, packCStringLen)
import qualified Data.ByteString as BS (length)
import Data.Serialize (decode)
import Data.Word (Word32, Word64)
import Foreign.C.String (CString, withCString, peekCString)
import Foreign.C.Types (CInt(..), CChar)
import Foreign.Marshal.Alloc (alloca, allocaBytes)
import Foreign.Marshal.Utils (with)
import Foreign.Ptr (Ptr, castPtr)
import Foreign.Storable (Storable(..))
import System.IO.Unsafe (unsafePerformIO)
foreign import ccall "inet_pton" c_pton :: CInt -> CString -> Ptr IP4 -> IO ()
foreign import ccall "inet_ntop" c_ntop :: CInt -> Ptr a -> Ptr CChar -> Word64 -> IO (Ptr CChar)
foreign import ccall "ntohl" ntohl :: Word32 -> Word32
foreign import ccall "htonl" htonl :: Word32 -> Word32
foreign import ccall "memcpy" memcpy :: Ptr a -> Ptr b -> Word64 -> IO ()
newtype IP4 = IP4 Word32 deriving (Eq)
newtype IP6 = IP6 ByteString deriving (Eq)
data IP
= IPv4 IP4
| IPv6 IP6
deriving (Eq)
data AddressFamily
= AF_UNSPEC
| AF_INET
| AF_INET6
instance Storable IP4 where
sizeOf _ = 4
alignment _ = alignment (undefined :: Word32)
peek p = fmap (IP4 . ntohl) . peek $ castPtr p
poke p (IP4 w) = poke (castPtr p) $ htonl w
instance Storable IP6 where
sizeOf _ = 16
alignment _ = alignment (undefined :: Word64)
peek p = fmap IP6 $ packCStringLen (castPtr p, 16)
poke p (IP6 w) = useAsCStringLen w (\(b, _) -> memcpy p b 16)
instance Show IP where
show (IPv4 ip) = show ip
show (IPv6 ip) = show ip
instance Show IP6 where
show = showIP6
instance Show IP4 where
show = showIP
showIPIO :: IP4 -> IO String
showIPIO ip = allocaBytes 16 (\str ->
with ip (\ptr -> c_ntop (familyToNum AF_INET) ptr str 16) >> peekCString str)
showIP :: IP4 -> String
showIP ip = unsafePerformIO (showIPIO ip)
parseIPIO :: String -> IO IP4
parseIPIO xs =
withCString xs (\str -> do
alloca (\ptr -> c_pton (familyToNum AF_INET) str ptr >> peek ptr))
parseIP :: String -> IP4
parseIP str = unsafePerformIO (parseIPIO str)
ipFromBS :: ByteString -> IP
ipFromBS bs = if BS.length bs == 16
then IPv6 (IP6 bs)
else case decode bs of
(Left err) -> error ("Failed to decode ip: " ++ err)
(Right x) -> IPv4 (IP4 x)
showIP6IO :: IP6 -> IO String
showIP6IO ip = allocaBytes 16 (\str ->
with ip (\ptr -> c_ntop (familyToNum AF_INET6) ptr str 46) >> peekCString str)
showIP6 :: IP6 -> String
showIP6 ip = unsafePerformIO (showIP6IO ip)
familyToNum :: Num a => AddressFamily -> a
familyToNum AF_UNSPEC = 0
familyToNum AF_INET = 2
familyToNum AF_INET6 = 10
getAddrFamily :: IP -> AddressFamily
getAddrFamily (IPv6 _) = AF_INET6
getAddrFamily (IPv4 _) = AF_INET