Win32-dhcp-server 0.1 → 0.2
raw patch · 9 files changed
+258/−113 lines, 9 filesdep +Win32-errorsdep +textdep −safe
Dependencies added: Win32-errors, text
Dependencies removed: safe
Files
- ChangeLog +6/−0
- Data/Ip.hs +25/−15
- Data/Mac.hs +40/−25
- System/Win32/DHCP.hs +140/−64
- System/Win32/DHCP/LengthBuffer.hs +0/−1
- System/Win32/DHCP/SEARCH_INFO.hs +6/−2
- Utils.hs +8/−0
- Win32-dhcp-server.cabal +11/−6
- examples/cabal.hs +22/−0
+ ChangeLog view
@@ -0,0 +1,6 @@+0.2 +=== + +* All functions throw Win32Exception exceptions. +* strict Text values are favored over String +* drop dependency on safe
Data/Ip.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE OverloadedStrings #-} -- | This module was taken, with modifications, from the -- <http://hackage.haskell.org/package/maccatcher maccatcher> package. @@ -15,12 +16,16 @@ ) where import Control.Applicative +import Control.Monad (unless) import Data.Bits -import Data.List (intercalate) +import Data.Text (Text) +import qualified Data.Text as T +import Data.Text.Read import Data.Word import Foreign -import Safe +import Utils (fmapL, note) + -- |An Ip can be used as an IP address or subnet address. -- -- A `Show` instance is omitted to avoid confusion. Use `showIp` and `readIp` @@ -32,25 +37,30 @@ -- -- >>> showIp $ fromOctets 192 168 1 100 -- "192.168.1.100" -showIp :: Ip -> String -showIp ip = intercalate "." . map show $ [a, b, c, d] +showIp :: Ip -> Text +showIp ip = T.intercalate "." . map (T.pack . show) $ [a, b, c, d] where (a, b, c, d) = toOctets ip --- |Parse a `String` value as an `Ip`. The string should be of the form +-- |Parse a `Text` value as an `Ip`. The string should be of the form -- "X.X.X.X" where each 'X' is a decimal value between 0 and 255 inclusive. -- --- >>> let Just ip = readIp "192.168.1.100" +-- >>> let Right ip = readIp "192.168.1.100" -- >>> toOctets ip -- (192, 168, 1, 100) -readIp :: String -> Maybe Ip -readIp s = case octets' s of - [a, b, c, d] -> fromOctets <$> readMay a <*> readMay b - <*> readMay c <*> readMay d - _ -> Nothing - where - octets' os = case break (=='.') (dropWhile (=='.') os) of - ("", _) -> [] - (o, os') -> o : octets' os' +readIp :: Text -> Either String Ip +readIp s = fmapL (\e -> "Error parsing IP address: " ++ e) $ do + (a, s2) <- decimal s + (b, s3) <- dot s2 >>= decimal + (c, s4) <- dot s3 >>= decimal + (d, s5) <- dot s4 >>= decimal + unless (s5 == "") $ Left "exactly 4 octets were expected." + -- Confirm that all digits are in range + fromOctets <$> digit a <*> digit b <*> digit c <*> digit d + where + dot = note "Expected '.' character." . T.stripPrefix "." + digit :: Int -> Either String Word8 + digit x | x < 0 || x > 255 = Left "digit out of range." + | otherwise = Right $ fromIntegral x -- |An IP address is 32-bits wide. This function will construct an `Ip` from -- 4 octets.
Data/Mac.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-} + -- | This module was taken, with modifications, from the -- <http://hackage.haskell.org/package/maccatcher maccatcher> package. module Data.Mac @@ -13,13 +15,20 @@ ) where import Control.Applicative +import Control.Monad (unless) import Data.Bits -import Data.List +import Data.Monoid +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Lazy as L +import qualified Data.Text.Lazy.Builder as L +import qualified Data.Text.Lazy.Builder.Int as L +import Data.Text.Read import Data.Word import Foreign -import Numeric (showHex) -import Safe +import Utils + -- |A Mac is a 6-byte unique identifier used in layer-two network addressing. -- Its `Storable` instance occupies 6 bytes of memory when `poke`d with the -- first byte occupying the lowest address, and the last byte occupying the @@ -57,35 +66,41 @@ -- -- >>> showMac ":" $ fromOctets 0x11 0x22 0x33 0x44 0x55 0x66 -- "11:22:33:44:55:66" -showMac :: String -> Mac -> String -showMac sep mac = intercalate sep - . map (\n -> pad 2 $ showHex n "") - $ [a, b, c, d, e, f] +showMac :: Text -> Mac -> Text +showMac sep mac = T.intercalate sep . map octet $ [a, b, c, d, e, f] where + octet x = pad (2 :: Int) . L.toStrict . L.toLazyText . L.hexadecimal $ x (a, b, c, d, e, f) = toOctets mac - -pad :: Int -> String -> String -pad n str = replicate zeroes '0' ++ str - where - zeroes = max 0 (n - length str) + pad n str = T.replicate (max 0 (n - T.length str)) "0" <> str --- |Parse a `String` value as a `Mac`. The string should not use any +-- |Parse a `Text` value as a `Mac`. The string should not use any -- separators between octets. -- --- >>> let Just mac = readMac "000102030405" +-- >>> let Right mac = readMac "000102030405" -- >>> toOctets mac -- (0, 1, 2, 3, 4, 5) -readMac :: String -> Maybe Mac -readMac str = case sequence (go str) of - Just [a, b, c, d, e, f] -> Just $ fromOctets a b c d e f - _ -> Nothing - where - go :: String -> [Maybe Word8] - -- There should always be 2 characters - go [_] = [Nothing] - -- We've reached the end - go [] = [] - go s = let (b, rest) = splitAt 2 s in readMay ("0x" ++ b) : go rest +readMac :: Text -> Either String Mac +readMac s = fmapL (\e -> "Error parsing MAC address: " ++ e) $ do + (a, s2) <- octet s + (b, s3) <- octet s2 + (c, s4) <- octet s3 + (d, s5) <- octet s4 + (e, s6) <- octet s5 + (f, s7) <- octet s6 + unless (s7 == "") $ Left "exactly 6 octets were expected." + fromOctets <$> digit a <*> digit b <*> digit c + <*> digit d <*> digit e <*> digit f + where + octet :: Text -> Either String (Int, Text) + octet s0 = do + (a, s2) <- hexadecimal a0 + unless (s2 == "") $ Left "invalid characters" + return (a, rest) + where + (a0, rest) = T.splitAt 2 s0 + digit :: Int -> Either String Word8 + digit x | x < 0 || x > 255 = Left "digit out of range." + | otherwise = Right $ fromIntegral x -- |A Mac address is 48-bits wide. This function will construct a `Mac` from -- 6 octets.
System/Win32/DHCP.hs view
@@ -1,3 +1,12 @@+{-# LANGUAGE OverloadedStrings #-} + +-- | +-- module: System.Win32.DHCP +-- copyright: (c) Michael Steele, 2014 +-- license: BSD3 +-- maintainer: mikesteele81@gmail.com +-- stability: experimental +-- portability: Windows module System.Win32.DHCP ( DhcpApi () , loadDHCP @@ -20,14 +29,20 @@ , removeReservation ) where +import Control.Applicative import Control.Monad (unless) +import Data.Char (chr) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Foreign as T import Foreign import Foreign.C.Types -import System.Win32.Types +import System.Win32.Types hiding (withTString, withTStringLen) +import qualified System.Win32.Error as E +import qualified System.Win32.Error.Foreign as E import Data.Ip - import System.Win32.DHCP.DhcpStructure import System.Win32.DHCP.HOST_INFO import System.Win32.DHCP.Internal @@ -47,7 +62,7 @@ data Context = Context { -- | The DHCP server management API uses an RPC mechanism to control the -- server. - contextServer :: !String + contextServer :: !Text , -- | Scopes are identified by a subnet identifier. This is useful in -- cases where multiple scopes are defined, but still required in the -- common case of a single scope. @@ -60,21 +75,27 @@ -- with that name. This action corresponds to MSDN's DhcpDeleteClientInfoV4 -- function. deleteClient :: DhcpApi - -> String - -- ^ Unicode string that specifies the IP address or hostname of the DHCP + -> Text + -- ^ String which specifies the IP address or hostname of the DHCP -- server. -> SEARCH_INFO -- ^ Define how to lookup a client to delete. Only deleting based on -- IP addresses have been tested. -> IO () - -- ^ This action may throw exceptions. According to Microsoft's protocol - -- specification an ERROR_DHCP_JET_ERROR (0x00004E2D) will be returned - -- if no client was found on the server. + -- ^ This function will throw an 'E.Win32Exception' when the internal Win32 + -- call returnes an error condition. MSDN lists the following exceptions, + -- but others might be thrown as well: + -- + -- ['E.DhcpReservedClient'] The specified DHCP client is a reserved DHCP + -- client. + -- + -- ['E.DhcpJetError'] An error occurred while accessing the DHCP server + -- database, or the client entry is not present in the database. deleteClient api server si = withTString server $ \pserver -> - withSearchInfo si $ \psi -> do - failUnlessSuccess (unwords ["DeleteClientInfo", server, show si]) - $ c_DeleteClientInfo api pserver psi + withSearchInfo si $ \psi -> + E.failUnlessSuccess "DeleteClientInfo" + $ c_DeleteClientInfo api pserver psi -- | Perform a lookup operation for all client lease records within a -- scope. This action corresponds to MSDN's DhcpEnumSubnetClientsV4 function. @@ -83,8 +104,12 @@ -- ^ Specify which server and scope to search for client leases. -> IO [Client] -- ^ The empty list means that no client records exist for the provided - -- subnet. An exception will be thrown if the provided server or subnet - -- does not exist, or any other error occurs. + -- subnet. This function will throw an 'E.Win32Exception' when the + -- internal Win32 call returnes an error condition. MSDN lists the + -- following exceptions, but others might be thrown as well: + -- + -- ['E.DhcpJetError'] An error occurred while accessing the DHCP server + -- database. enumClients dhcp (Context server subnet) = enumSubnetClientsV4 dhcp server subnet @@ -92,7 +117,7 @@ -- `Nothing` is returned when no lease was found. This corresponds to MSDN's -- DhcpGetClientInfoV4 function. lookupClient :: DhcpApi - -> String + -> Text -- ^ According to MSDN this must specify the IP address of the server. -- Other functions (including this one) may or may not also accept -- a Unicode host name. @@ -100,11 +125,12 @@ -- ^ Define how to lookup a client. Only searching based on an -- IP addresses has been tested. -> IO (Maybe Client) - -- ^ A `Nothing` indicates that no client was found. An exception will - -- be thrown if any internal error occurs. Though MSDN documents don't say - -- so, an ERROR_DHCP_JET_ERROR (0x00004E2D) may be thrown if no client - -- record is found. The reason I think this is that other functions are - -- said to behave like this. + -- ^ A `Nothing` indicates that no client was found. This function will throw an 'E.Win32Exception' when the + -- internal Win32 call returnes an error condition. MSDN lists the + -- following exceptions, but others might be thrown as well: + -- + -- ['E.DhcpJetError'] An error occurred while accessing the DHCP server + -- database. lookupClient api serverip si = withTString serverip $ \pserverip -> withSearchInfo si $ \psi -> @@ -112,7 +138,7 @@ -- DhcpGetClientInfo takes a structure on the stack. siType <- peek (castPtr psi :: Ptr CInt) siPayload <- peekElemOff (castPtr psi :: Ptr (Ptr ())) 4 - failUnlessSuccess (unwords ["GetClientInfoV4", serverip, show si]) + E.failUnlessSuccess "GetClientInfoV4" $ c_GetClientInfoV4 api pserverip siType siPayload ppclientinfo pclientinfo <- peek ppclientinfo if pclientinfo == nullPtr @@ -123,7 +149,28 @@ poke (castPtr pclientinfo) nullPtr return $ Just clientinfo -addReservation :: DhcpApi -> Context -> Mapping -> IO () +addReservation :: DhcpApi -> Context -> Mapping + -> IO () + -- ^ This function will throw an 'E.Win32Exception' when the internal Win32 + -- call returnes an error condition. MSDN lists the following exceptions, + -- but others might be thrown as well: + -- + -- ['E.DhcpSubnetNotPresent'] The specified IPv4 subnet does not exist + -- on the DHCP server. + -- + -- ['E.DhcpJetError'] An error occurred while accessing the DHCP server + -- database. + -- + -- ['E.DhcpNotReservedClient'] The specified DHCP client is not a + -- reserved client. + -- + -- ['E.DhcpInvalidRange'] The specified IPv4 range does not match an + -- existing IPv4 range. + -- + -- ['E.ScopeRangePolicyChangeConflict'] An IP address range is + -- configured for a policy in this scope. This operation cannot be + -- performed on the scope IP address range until the policy IP address + -- range is suitably modified. addReservation dhcp (Context server subnet) mapping = addSubnetElementV4 dhcp server subnet $ ReservedIps (Reservation mapping Both) @@ -145,13 +192,33 @@ -- ^ Specify whether any active leases for the reservation should be -- removed as well. -> IO () + -- ^ This function will throw an 'E.Win32Exception' when the internal Win32 + -- call returnes an error condition. MSDN lists the following exceptions, + -- but others might be thrown as well: + -- + -- ['E.DhcpSubnetNotPresent'] The specified IPv4 subnet does not exist + -- on the DHCP server. + -- + -- ['E.DhcpElementCantRemove'] Failure can occur for any number of + -- reasons. + -- + -- ['E.DhcpJetError'] An error occurred while accessing the DHCP server + -- database. + -- + -- ['E.DhcpInvalidRange'] The specified IPv4 range does not match an + -- existing IPv4 range. + -- + -- ['E.ScopeRangePolicyChangeConflict'] An IP address range is + -- configured for a policy in this scope. This operation cannot be + -- performed on the scope IP address range until the policy IP address + -- range is suitably modified. removeReservation dhcp (Context server subnet) mapping ct force = removeSubnetElementV4 dhcp server subnet (ReservedIps $ Reservation mapping ct) fForce where fForce = if force then FullForce else NoForce -enumSubnetClientsV4 :: DhcpApi -> String -> Ip -> IO [Client] +enumSubnetClientsV4 :: DhcpApi -> Text -> Ip -> IO [Client] enumSubnetClientsV4 dhcp server subnet = withTString server $ \pServer -> -- inout parameter. Supply on successive calls to retreive more elements @@ -163,11 +230,11 @@ -- Failure to use the returned resumeHandle may result in an internal access -- violation within RPCRT4.dll. let loop acc = do - ret <- c_EnumSubnetClientsV4 dhcp pServer (toWord32 subnet) - pResumeHandle 0xFFFFFFFF ppInfoArray - pElementsRead pElementsTotal - unless (elem ret [eRROR_SUCCESS, eRROR_MORE_DATA, eRROR_NO_MORE_ITEMS]) $ do - failWith (unwords ["EnumSubnetClientsV4", server, showIp subnet]) ret + ret <- E.fromDWORD <$> c_EnumSubnetClientsV4 dhcp pServer (toWord32 subnet) + pResumeHandle 0xFFFFFFFF ppInfoArray + pElementsRead pElementsTotal + unless (elem ret [E.Success, E.MoreData, E.NoMoreItems]) + $ E.failWith "EnumSubnetClientsV4" ret pInfoArray <- peek ppInfoArray elems <- if pInfoArray == nullPtr then return [] @@ -178,20 +245,14 @@ return elems elementsTotal <- peek pElementsTotal elementsRead <- peek pElementsRead - if (ret == eRROR_NO_MORE_ITEMS || (ret == eRROR_SUCCESS && elementsTotal == elementsRead)) - then return (eRROR_SUCCESS, elems:acc) + if (ret == E.NoMoreItems || (ret == E.Success && elementsTotal == elementsRead)) + then return (elems:acc) else loop (elems:acc) - (ret, revelemss) <- loop [] - failUnlessSuccess (unwords ["EnumSubnetClientsV4", server, showIp subnet]) - $ return ret + revelemss <- loop [] return $ concat $ reverse revelemss - where - eRROR_SUCCESS = 0x00000000 - eRROR_NO_MORE_ITEMS = 0x00000103 - eRROR_MORE_DATA = 0x000000ea -enumSubnetElementsV4 :: DhcpApi -> String -> Ip -> CInt +enumSubnetElementsV4 :: DhcpApi -> Text -> Ip -> CInt -> IO [SUBNET_ELEMENT_DATA_V4] enumSubnetElementsV4 dhcp server subnet elementType = withTString server $ \pServer -> @@ -204,12 +265,11 @@ -- Failure to use the returned resumeHandle may result in an internal access -- violation within RPCRT4.dll. let loop acc = do - ret <- c_EnumSubnetElementsV4 dhcp pServer (toWord32 subnet) elementType - pResumeHandle 0xFFFFFFFF ppEnumElementInfoArray - pElementsRead pElementsTotal - unless (elem ret [eRROR_SUCCESS, eRROR_MORE_DATA, eRROR_NO_MORE_ITEMS]) $ do - failWith (unwords ["EnumSubnetElementsV4", server - , showIp subnet, show elementType]) ret + ret <- E.fromDWORD <$> c_EnumSubnetElementsV4 dhcp pServer (toWord32 subnet) elementType + pResumeHandle 0xFFFFFFFF ppEnumElementInfoArray + pElementsRead pElementsTotal + unless (elem ret [E.Success, E.MoreData, E.NoMoreItems]) + $ E.failWith "EnumSubnetElementsV4" ret pEnumElementInfoArray <- peek ppEnumElementInfoArray elems <- if pEnumElementInfoArray == nullPtr then return [] @@ -219,33 +279,25 @@ poke ppEnumElementInfoArray nullPtr return elems elementsTotal <- peek pElementsTotal - if (ret == eRROR_NO_MORE_ITEMS || (ret == eRROR_SUCCESS && elementsTotal == 0)) - then return (eRROR_SUCCESS, elems:acc) + if (ret == E.NoMoreItems || (ret == E.Success && elementsTotal == 0)) + then return (elems:acc) else loop (elems:acc) - (ret, revelemss) <- loop [] - failUnlessSuccess (unwords ["EnumSubnetElementsV4", server - , showIp subnet, show elementType]) - $ return ret + revelemss <- loop [] return $ concat $ reverse revelemss - where - eRROR_SUCCESS = 0x00000000 - eRROR_NO_MORE_ITEMS = 0x00000103 - eRROR_MORE_DATA = 0x000000ea - -addSubnetElementV4 :: DhcpApi -> String -> Ip -> SUBNET_ELEMENT_DATA_V4 -> IO () +addSubnetElementV4 :: DhcpApi -> Text -> Ip -> SUBNET_ELEMENT_DATA_V4 -> IO () addSubnetElementV4 dhcp server subnet elementData = withTString server $ \pServer -> withDhcp subnetElementData elementData $ \pElementData -> - failUnlessSuccess (unwords ["AddSubnetElementsV4", server, showIp subnet]) + E.failUnlessSuccess "AddSubnetElementsV4" $ c_AddSubnetElementV4 dhcp pServer (toWord32 subnet) pElementData -- | Remove an IPv4 subnet element from an IPv4 subnet defined on the DHCPv4 -- server. removeSubnetElementV4 :: DhcpApi - -> String -- ^ Unicode string that specifies the IP address, such as + -> Text -- ^ Unicode string that specifies the IP address, such as -- \"123.456.789.012\", or hostname, such as \"server\", -- of the DHCP server. -> Ip -- ^ 'DWORD' value that specifies the IP address of the @@ -261,21 +313,45 @@ -> FORCE_FLAG -- ^ DHCP_FORCE_FLAG enumeration value that indicates -- whether or not the clients affected by the removal of -- the subnet element should also be deleted. - -- + -- -- Note If the flag is set to DhcpNoForce and this subnet -- has served an IPv4 address to DHCPv4/BOOTP clients, the -- IPv4 range is not deleted; conversely, if the flag is -- set to DhcpFullForce, the IPv4 range is deleted along -- with the DHCPv4 client lease record on the DHCPv4 -- server. - -> IO () -- ^ Following the convention of the Win32 package, an exeption - -- indicating the win32 error code will be raised if the - -- underlying API call returns anything other than - -- ERROR_SUCCESS. + -> IO () + -- ^ This function will throw an 'E.Win32Exception' when the internal Win32 + -- call returnes an error condition. MSDN lists the following exceptions, + -- but others might be thrown as well: + -- + -- ['E.DhcpSubnetNotPresent'] The specified IPv4 subnet does not exist + -- on the DHCP server. + -- + -- ['E.DhcpElementCantRemove'] Failure can occur for any number of + -- reasons. + -- + -- ['E.DhcpJetError'] An error occurred while accessing the DHCP server + -- database. + -- + -- ['E.DhcpInvalidRange'] The specified IPv4 range does not match an + -- existing IPv4 range. + -- + -- ['E.ScopeRangePolicyChangeConflict'] An IP address range is + -- configured for a policy in this scope. This operation cannot be + -- performed on the scope IP address range until the policy IP address + -- range is suitably modified. removeSubnetElementV4 dhcp server subnet elementData forceFlag = withTString server $ \pServer -> withDhcp subnetElementData elementData $ \pElementData -> - failUnlessSuccess (unwords ["RemoveSubnetElementV4", server, showIp subnet - , (show . elementTypeOf) elementData]) + E.failUnlessSuccess "RemoveSubnetElementV4" $ c_RemoveSubnetElementV4 dhcp pServer (toWord32 subnet) pElementData (fromIntegral . fromEnum $ forceFlag) + +withTStringLen :: Text -> (LPTSTR -> T.I16 -> IO a) -> IO a +withTStringLen text act = T.useAsPtr (T.snoc text (chr 0x0)) $ \ptr len -> + act (castPtr ptr) len + +withTString :: Text -> (LPTSTR -> IO a) -> IO a +withTString text act = withTStringLen text $ \ptr _ -> do + act ptr
System/Win32/DHCP/LengthBuffer.hs view
@@ -9,7 +9,6 @@ import System.Win32.Types (DWORD) import System.Win32.DHCP.DhcpStructure -import Utils (freeptr) -- |A LengthBuffer is a list of items which can be marshalled in and out -- of memory with a `DhcpArray` instance.
System/Win32/DHCP/SEARCH_INFO.hs view
@@ -1,9 +1,13 @@+{-# LANGUAGE OverloadedStrings #-} + module System.Win32.DHCP.SEARCH_INFO ( SEARCH_INFO_TYPE , SEARCH_INFO (..) , withSearchInfo ) where +import Data.Monoid ((<>)) +import qualified Data.Text as T import Foreign import Foreign.C.Types @@ -44,8 +48,8 @@ | ClientName !String instance Show SEARCH_INFO where - show (ClientIpAddress ip) = "ClientIpAddress " ++ showIp ip - show (ClientHardwareAddress mac) = "ClientHardwareAddress " ++ showMac ":" mac + show (ClientIpAddress ip) = T.unpack $ "ClientIpAddress " <> showIp ip + show (ClientHardwareAddress mac) = T.unpack $ "ClientHardwareAddress " <> showMac ":" mac show (ClientName name) = "ClientName " ++ name siTypeOf :: SEARCH_INFO -> SEARCH_INFO_TYPE
Utils.hs view
@@ -28,3 +28,11 @@ withMaybeTString :: Maybe String -> (LPTSTR -> IO a) -> IO a withMaybeTString Nothing f = f nullPtr withMaybeTString (Just str) f = withTString str f + +note :: e -> Maybe a -> Either e a +note e Nothing = Left e +note _ (Just x) = Right x + +fmapL :: (a -> b) -> Either a r -> Either b r +fmapL f (Left x) = Left (f x) +fmapL _ (Right x) = (Right x)
Win32-dhcp-server.cabal view
@@ -1,6 +1,6 @@ Name: Win32-dhcp-server Synopsis: Win32 DHCP Server Management API. -Version: 0.1 +Version: 0.2 License: BSD3 License-file: LICENSE Author: Michael Steele @@ -17,7 +17,7 @@ This package provides a partial binding to the Win32 DHCP Server Management API. Its purpose is to query and control a Microsoft DHCP server. Enough functionality is defined so so that Ipv4 client lease and reservation - records can be manipulated from software. + records can be manipulated programmatically. . Here are a few notes on the required environment: . @@ -35,6 +35,7 @@ . import Data.Ip import Data.Mac + import qualified Data.Text.IO as T import System.Win32.DHCP . main :: IO () @@ -42,16 +43,20 @@   api <- loadDHCP   clients <- enumClients api context   let macs = map (showMac \":\" . clientHardwareAddress) clients -   mapM_ putStrLn macs +   mapM_ T.putStrLn macs   where -   Just subnet = readIp \"192.168.1.0\" +   Right subnet = readIp \"192.168.1.0\"   context = Context \"192.168.1.5\" subnet @ +extra-source-files: + ChangeLog + examples/*.hs Library - Build-depends: base >= 4.6 && < 4.8 - , safe + Build-depends: base >= 4.6 && < 4.8 + , text > 0.11 && < 1.2 , Win32 + , Win32-errors >= 0.2 && < 1.0 default-language: Haskell2010 Exposed-modules: Data.Ip , Data.Mac
+ examples/cabal.hs view
@@ -0,0 +1,22 @@+-- This is the example used in the main .cabal file. + +{-# LANGUAGE OverloadedStrings #-} + +-- Print all MAC addresses with an active client lease +module Main where + +import Data.Ip +import Data.Mac +import qualified Data.Text.IO as T +import System.Win32.DHCP + +main :: IO () +main = do + api <- loadDHCP + clients <- enumClients api context + let macs = map (showMac ":" . clientHardwareAddress) clients + mapM_ T.putStrLn macs + where + Right subnet = readIp "192.168.1.0" + context = Context "192.168.1.5" subnet +