netlink-1.0.0.4: System/Linux/Netlink/GeNetlink/Control.hs
{-# LANGUAGE CPP #-}
{-|
Module : System.Linux.Netlink.GeNetlink.Control
Description : This module implements the control protocol of genetlink
Maintainer : ongy
Stability : testing
Portability : Linux
This module provides utility functions and datatypes for the genetlink control
protocol.
This has to be used by implementations of netlink families based on genetlink
to lookup their current id, since that is determined at runtime.
-}
module System.Linux.Netlink.GeNetlink.Control
( CtrlAttribute(..)
, CtrlAttrMcastGroup(..)
, CtrlPacket(..)
, CTRLPacket
, ctrlPacketFromGenl
, CtrlAttrOpData(..)
, ctrlPackettoGenl
, getFamilyId
, getFamilyIdS
, getFamilyWithMulticasts
, getFamilyWithMulticastsS
, getMulticastGroups
, getMulticast
, getFamilie
, getFamilies
)
where
#if MIN_VERSION_base(4,8,0)
#else
import Control.Applicative ((<$>))
#endif
import Data.Bits ((.|.))
import Data.Serialize.Get
import Data.Serialize.Put
import Data.List (intercalate)
import Data.Map (fromList, lookup, toList, Map)
import Data.ByteString (ByteString, append, empty)
import Data.ByteString.Char8 (pack, unpack)
import Data.Word (Word16, Word32)
import Data.Maybe (fromMaybe, mapMaybe)
import Prelude hiding (lookup)
import System.Linux.Netlink
import System.Linux.Netlink.Constants
import System.Linux.Netlink.GeNetlink
import System.Linux.Netlink.GeNetlink.Constants
import System.Linux.Netlink.Helpers (g32, g16)
-- |Datatype for multicast groups as returned by the control protocol
data CtrlAttrMcastGroup = CAMG {grpName :: String, grpId :: Word32 } deriving (Eq, Show)
-- |Datatype for AttrOpData as returned by the control protocol
data CtrlAttrOpData = CAO {opId :: Word32, opFlags :: Word32 } deriving (Eq, Show)
-- |Attributes defined by the control family
data CtrlAttribute =
CTRL_ATTR_UNSPEC ByteString |
CTRL_ATTR_FAMILY_ID Word16 |
CTRL_ATTR_FAMILY_NAME String |
CTRL_ATTR_VERSION Word32 |
CTRL_ATTR_HDRSIZE Word32 |
CTRL_ATTR_MAXATTR Word32 |
CTRL_ATTR_OPS [CtrlAttrOpData] |
CTRL_ATTR_MCAST_GROUPS [CtrlAttrMcastGroup] |
CTRL_ATTR_UNKNOWN Int ByteString
deriving (Eq, Show)
-- |Typesafe control packet
data CtrlPacket = CtrlPacket
{
ctrlHeader :: Header
, ctrlGeHeader :: GenlHeader
, ctrlAttributes :: [CtrlAttribute]
} deriving (Eq)
instance Show CtrlPacket where
show packet =
show (ctrlHeader packet) ++ '\n':show (ctrlGeHeader packet) ++
"Attrs:\n" ++ intercalate "\n" (map show (ctrlAttributes packet))
-- |typedef for control messages
type CTRLPacket = GenlPacket NoData
--
-- Start ctrl utility
--
getW16 :: ByteString -> Maybe Word16
getW16 x = e2M (runGet g16 x)
getW32 :: ByteString -> Maybe Word32
getW32 x = e2M (runGet g32 x)
e2M :: Either a b -> Maybe b
e2M (Right x) = Just x
e2M _ = Nothing
getMcastGroupAttr :: (Int, ByteString) -> Maybe CtrlAttrMcastGroup
getMcastGroupAttr (_, x) = do
attrs <- e2M $runGet getAttributes x
name <- lookup eCTRL_ATTR_MCAST_GRP_NAME attrs
fid <- lookup eCTRL_ATTR_MCAST_GRP_ID attrs
-- This init is ok because the name will always have the \0
CAMG (init . unpack $ name) <$> getW32 fid
getMcastGroupAttrs :: ByteString -> Maybe [CtrlAttrMcastGroup]
getMcastGroupAttrs x = case runGet getAttributes x of
(Right y) -> mapM getMcastGroupAttr $ toList y
_ -> Nothing
getOpAttr :: (Int, ByteString) -> Maybe CtrlAttrOpData
getOpAttr (_, x) = do
attrs <- e2M $runGet getAttributes x
oid <- getW32 =<< lookup eCTRL_ATTR_OP_ID attrs
ofl <- getW32 =<< lookup eCTRL_ATTR_OP_FLAGS attrs
return $ CAO oid ofl
getOpAttrs :: ByteString -> Maybe [CtrlAttrOpData]
getOpAttrs x = case runGet getAttributes x of
(Right y) -> mapM getOpAttr $ toList y
_ -> Nothing
getAttribute :: (Int, ByteString) -> CtrlAttribute
getAttribute (i, x) = fromMaybe (CTRL_ATTR_UNKNOWN i x) $makeAttribute i x
makeAttribute :: Int -> ByteString -> Maybe CtrlAttribute
makeAttribute i x
| i == eCTRL_ATTR_UNSPEC = Just $CTRL_ATTR_UNSPEC x
| i == eCTRL_ATTR_FAMILY_ID = fmap CTRL_ATTR_FAMILY_ID $getW16 x
| i == eCTRL_ATTR_FAMILY_NAME = Just . CTRL_ATTR_FAMILY_NAME . init $unpack x
| i == eCTRL_ATTR_VERSION = fmap CTRL_ATTR_VERSION $getW32 x
| i == eCTRL_ATTR_HDRSIZE = fmap CTRL_ATTR_HDRSIZE $getW32 x
| i == eCTRL_ATTR_MAXATTR = fmap CTRL_ATTR_MAXATTR $getW32 x
| i == eCTRL_ATTR_OPS = fmap CTRL_ATTR_OPS $getOpAttrs x
| i == eCTRL_ATTR_MCAST_GROUPS = fmap CTRL_ATTR_MCAST_GROUPS $getMcastGroupAttrs x
| otherwise = Nothing
ctrlAttributesFromAttributes :: Map Int ByteString -> [CtrlAttribute]
ctrlAttributesFromAttributes = map getAttribute . toList
-- |Convert "normal" 'Packet's into typesafe 'CtrlPacket's
ctrlPacketFromGenl :: CTRLPacket -> Maybe CtrlPacket
ctrlPacketFromGenl (Packet h g attrs) = Just (CtrlPacket h (genlDataHeader g) a)
where a = ctrlAttributesFromAttributes attrs
ctrlPacketFromGenl _ = Nothing
putW16 :: Word16 -> ByteString
putW16 x = runPut (putWord16host x)
putW32 :: Word32 -> ByteString
putW32 x = runPut (putWord32host x)
-- AttrOps and McastGroup are broken, but generally we shouldn't send these anyway
cATA :: CtrlAttribute -> (Int, ByteString)
cATA (CTRL_ATTR_UNSPEC x) = (eCTRL_ATTR_UNSPEC , x)
cATA (CTRL_ATTR_FAMILY_ID x) = (eCTRL_ATTR_FAMILY_ID , putW16 x)
cATA (CTRL_ATTR_FAMILY_NAME x) = (eCTRL_ATTR_FAMILY_NAME , pack (x ++ "\n"))
cATA (CTRL_ATTR_VERSION x) = (eCTRL_ATTR_VERSION , putW32 x)
cATA (CTRL_ATTR_HDRSIZE x) = (eCTRL_ATTR_HDRSIZE , putW32 x)
cATA (CTRL_ATTR_MAXATTR x) = (eCTRL_ATTR_MAXATTR , putW32 x)
cATA (CTRL_ATTR_OPS _) = (eCTRL_ATTR_OPS , empty)
cATA (CTRL_ATTR_MCAST_GROUPS _) = (eCTRL_ATTR_MCAST_GROUPS, empty)
cATA (CTRL_ATTR_UNKNOWN i x) = (i , x)
ctrlAttributesToAttribute :: CtrlAttribute -> (Int, ByteString)
ctrlAttributesToAttribute = cATA
-- |Convert the typesafe 'CtrPacket' into a 'CTRLPacket' so it can be sent
ctrlPackettoGenl :: CtrlPacket -> CTRLPacket
ctrlPackettoGenl (CtrlPacket h g attrs)= Packet h (GenlData g NoData) a
where a = fromList $map ctrlAttributesToAttribute attrs
-- Hardcoding the request ID is not the most elegant, but shouldn't be a problem
-- since the family should be obvious in the answer
familyMcastRequest :: Word16 -> CTRLPacket
familyMcastRequest fid = let
header = Header 16 fNLM_F_REQUEST 42 0
geheader = GenlHeader eCTRL_CMD_GETFAMILY 0
attrs = fromList [(eCTRL_ATTR_FAMILY_ID, runPut $putWord16host fid)] in
Packet header (GenlData geheader NoData) attrs
familyIdRequest :: String -> CTRLPacket
familyIdRequest name = let
header = Header 16 fNLM_F_REQUEST 33 0
geheader = GenlHeader eCTRL_CMD_GETFAMILY 0
attrs = fromList [(eCTRL_ATTR_FAMILY_NAME, pack name `append` pack "\0")] in
Packet header (GenlData geheader NoData) attrs
-- |A safe version of 'getFamilyId'
getFamilyIdS :: NetlinkSocket -> String -> IO (Maybe Word16)
getFamilyIdS s m = do
may <- getFamilyWithMulticastsS s m
return $fmap fst may
-- |A safe version of 'getFamilyWithMulticasts'
getFamilyWithMulticastsS :: NetlinkSocket -> String -> IO (Maybe (Word16, [CtrlAttrMcastGroup]))
getFamilyWithMulticastsS s m = do
packet <- queryOne s (familyIdRequest m)
let ctrl = ctrlPacketFromGenl packet
return $ makeTupl . ctrlAttributes <$> ctrl
where getIdFromList (CTRL_ATTR_FAMILY_ID x:_) = x
getIdFromList (_:xs) = getIdFromList xs
getIdFromList [] = -1
makeTupl attrs = (getIdFromList attrs, getMCFromList attrs)
-- |Get the id for a netlink family by name
getFamilyId :: NetlinkSocket -> String -> IO Word16
getFamilyId = fmap (fmap fst) . getFamilyWithMulticasts
-- |get the id and multicast groups of a netlink family by name
getFamilyWithMulticasts :: NetlinkSocket -> String -> IO (Word16, [CtrlAttrMcastGroup])
getFamilyWithMulticasts s m = do
may <- getFamilyWithMulticastsS s m
return $fromMaybe (error "Could not find family") may
-- |Get the 'CtrlPacket' describing a single family
getFamilie :: NetlinkSocket -> String -> IO (Maybe CtrlPacket)
getFamilie sock name =
ctrlPacketFromGenl <$> queryOne sock (familyIdRequest name)
-- |Get 'CtrlPacket's for every currently registered GeNetlink family
getFamilies :: NetlinkSocket -> IO [CtrlPacket]
getFamilies sock = do
mapMaybe ctrlPacketFromGenl <$> query sock familiesRequest
where familiesRequest = let header = Header 16 (fNLM_F_REQUEST .|. fNLM_F_ROOT .|. fNLM_F_MATCH) 33 0
geheader = GenlHeader eCTRL_CMD_GETFAMILY 0
attrs = fromList [] in
Packet header (GenlData geheader NoData) attrs
-- |get the mutlicast groups of a netlink family by id
getMulticastGroups :: NetlinkSocket -> Word16 -> IO [CtrlAttrMcastGroup]
getMulticastGroups sock fid = do
packet <- queryOne sock (familyMcastRequest fid)
let (CtrlPacket _ _ attrs) = fromMaybe (error "Got infalid family id for request") . ctrlPacketFromGenl $packet
return $getMCFromList attrs
getMCFromList :: [CtrlAttribute] -> [CtrlAttrMcastGroup]
getMCFromList (CTRL_ATTR_MCAST_GROUPS x:_) = x
getMCFromList (_:xs) = getMCFromList xs
getMCFromList [] = []
-- |Get id of multicast group by name
getMulticast :: String -> [CtrlAttrMcastGroup] -> Maybe Word32
getMulticast _ [] = Nothing
getMulticast name (CAMG gname gid:xs) = if name == gname
then Just gid
else getMulticast name xs