{- Copyright 2016 Markus Ongyerth This file is part of Monky. Monky is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Monky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Monky. If not, see . -} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE CPP #-} {-| Module : Monky.Network.Dynamic Description : Allows access to information about they systems network Maintainer : ongy Stability : testing Portability : Linux This module exports a handle to access dynamic network configurations. This will update when a new network interface appears or disappears. The value returned by this will be the sum over all interfaces. -} module Monky.Network.Dynamic ( getUHandles , UHandles , Handles , getMultiReadWrite ) where import Data.Bits ((.|.)) import Control.Monad (when) import Control.Concurrent (forkIO, threadWaitRead) import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as IM import qualified Data.Map as M import Data.IORef import qualified Data.ByteString.Char8 as BS import System.Linux.Netlink import System.Linux.Netlink.Constants import System.Linux.Netlink.Route import Monky.Network.Static #if MIN_VERSION_base(4,8,0) #else import Control.Applicative ((<$>)) #endif -- |A Wrapper than also carries the name, for comparision data NetHandle = NetHandle String NetworkHandle -- |The map we keep our handles in (the Int is the Interface ID on the system) type Handles = IntMap NetHandle -- |The actual handel exposed and used by this module type UHandles = (IORef Handles, String -> Bool) instance Show NetHandle where show (NetHandle x _) = x -- |The fold function used for 'getMultiReadWrite' handling the IO and Maybe stuff foldF :: NetworkHandle -> IO (Maybe (Int, Int)) -> IO (Maybe (Int, Int)) foldF h o = do m <- getReadWrite h case m of (Just (r, w)) -> do om <- o case om of Just (oldr, oldw) -> return $ Just (oldr + r, oldw + w) Nothing -> return $ Just (r, w) Nothing -> o -- |Get the sum of all read/write rates from our network devices or Nothing if none is active getMultiReadWrite :: Handles -> IO (Maybe (Int, Int)) getMultiReadWrite = IM.foldr (\(NetHandle _ v) -> foldF v) (return Nothing) -- |Logic for adding a new device to our Handles gotNew :: Int -> String -> Handles -> IO Handles gotNew index name m = case IM.lookup index m of Nothing -> do h <- getNetworkHandle name return $IM.insert index (NetHandle name h) m Just (NetHandle x v) -> if x == name then return m else do h <- getNetworkHandle name closeNetworkHandle v return $IM.adjust (const (NetHandle name h)) index m -- |Logic for removing a handle form Handles after we lost the interface lostOld :: Int -> Handles -> IO Handles lostOld index m = case IM.lookup index m of Nothing -> return m (Just (NetHandle _ h)) -> closeNetworkHandle h >> return (IM.delete index m) -- |The packet used to drump all current network devices requestPacket :: RoutePacket requestPacket = let flags = fNLM_F_REQUEST .|. fNLM_F_MATCH .|. fNLM_F_ROOT header = Header eRTM_GETLINK flags 42 0 msg = NLinkMsg 0 0 0 in Packet header msg M.empty -- |Read the interface name and index from 'RoutePacket' readInterface :: RoutePacket -> (Int, String) readInterface (Packet _ msg attrs) = let (Just name) = M.lookup eIFLA_IFNAME attrs names = init $ BS.unpack name -- Drop \0 index = interfaceIndex msg in (fromIntegral index, names) readInterface x = error ("Something went wrong while getting interfaces: " ++ show x) -- |Get all current interfaces from our system getCurrentDevs :: IO [(Int, String)] getCurrentDevs = do sock <- makeSocket ifs <- query sock requestPacket closeSocket sock return $map readInterface ifs -- |Get the 'Handles' wrapper for all current interfaces getNetworkHandles :: (String -> Bool) -> IO Handles getNetworkHandles f = do interfaces <- filter (f . snd) <$> getCurrentDevs foldr build (return IM.empty) interfaces where build (index, dev) m = gotNew index dev =<< m -- |Handle an incomming rtneltink message and update the handle doUpdate :: UHandles -> RoutePacket -> IO () doUpdate (mr, f) (Packet hdr msg attrs) -- for now we will assume that we want the interface | messageType hdr == eRTM_NEWLINK = do let (Just name) = M.lookup eIFLA_IFNAME attrs let names = init $BS.unpack name -- Drop \0 when (f names) $ do -- Add let index = interfaceIndex msg m <- readIORef mr nm <- gotNew (fromIntegral index) names m writeIORef mr nm | messageType hdr == eRTM_DELLINK = do let index = interfaceIndex msg m <- readIORef mr nm <- lostOld (fromIntegral index) m writeIORef mr nm | otherwise = return () -- Ignore everything else doUpdate _ _ = return () -- |Updater loop, it blocks on the netlink socker until it gets a message updaterLoop :: NetlinkSocket -> UHandles -> IO () updaterLoop sock h = do threadWaitRead (getNetlinkFd sock) packet <- recvOne sock :: IO [RoutePacket] mapM_ (doUpdate h) packet updaterLoop sock h -- |Start the update loop for adding/removing interfaces updater :: UHandles -> IO () updater h = do sock <- makeSocket joinMulticastGroup sock 1 updaterLoop sock h -- |Get the new handle this module exports and start its updater loop getUHandles :: (String -> Bool) -- ^Will be given the name of the handle, only add interface if this returns true (think of it as filter over all possible interfaces) -> IO UHandles getUHandles f = do handle <- getNetworkHandles f ref <- newIORef handle _ <- forkIO (updater (ref, f)) return (ref, f)