1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
|
------------------------------------------------------------------------------
-- |
-- Module : Network.Pcap
-- Copyright : Bryan O'Sullivan 2007, Antiope Associates LLC 2004
-- License : BSD-style
-- Maintainer : bos@serpentine.com
-- Stability : experimental
-- Portability : non-portable
--
-- The 'Network.Pcap' module is a high(ish) level binding to all of
-- the functions in @libpcap@. See <http://www.tcpdump.org> for more
-- information.
--
-- This module is built on the lower-level 'Network.Pcap.Base' module,
-- which is slightly more efficient. Don\'t use 'Network.Pcap.Base'
-- unless profiling data indicates that you need to.
--
-- Only a minimum of marshaling is done on received packets. To
-- convert captured packet data to a 'B.ByteString' (space efficient,
-- and with /O(1)/ access to every byte in a captured packet), use
-- 'toBS'.
--
-- Note that the 'SockAddr' exported here is not the @SockAddr@ from
-- 'Network.Socket'. The @SockAddr@ from 'Network.Socket' corresponds
-- to @struct sockaddr_in@ in BSD terminology. The 'SockAddr' record
-- here is BSD's @struct sockaddr@. See W.R.Stevens, TCP Illustrated,
-- volume 2, for further elucidation.
--
-- This binding should be portable across systems that can use the
-- @libpcap@ from @tcpdump.org@. It does not yet work with Winpcap, a
-- similar library for Windows, although adapting it should not prove
-- difficult.
--
------------------------------------------------------------------------------
module Network.Pcap
(
-- * Types
PcapHandle
, DumpHandle
, BpfProgram
, Callback
, CallbackBS
, Direction(..)
, Link(..)
, Interface(..)
, PcapAddr(..)
, SockAddr(..)
, Network(..)
, PktHdr(..)
, Statistics(..)
-- * Device opening
, openOffline -- :: FilePath -> IO Pcap
, openLive -- :: String -> Int -> Bool -> Int -> IO Pcap
, openDead -- :: Int -> Int -> IO Pcap
, openDump -- :: PcapHandle -> FilePath -> IO Pdump
-- * Filter handling
, setFilter -- :: PcapHandle -> String -> Bool -> Word32 -> IO ()
, compileFilter -- :: Int -> Int -> String -> Bool -> Word32 -> IO BpfProgram
-- * Device utilities
, lookupDev -- :: IO String
, findAllDevs -- :: IO [Interface]
, lookupNet -- :: String -> IO Network
-- * Interface control
, setNonBlock -- :: PcapHandle -> Bool -> IO ()
, getNonBlock -- :: PcapHandle -> IO Bool
, setDirection
-- * Link layer utilities
, datalink -- :: PcapHandle -> IO Link
, setDatalink -- :: PcapHandle -> Link -> IO ()
, listDatalinks -- :: PcapHandle -> IO [Link]
-- * Packet processing
, dispatch -- :: PcapHandle -> Int -> Callback -> IO Int
, loop -- :: PcapHandle -> Int -> Callback -> IO Int
, next -- :: PcapHandle -> IO (PktHdr, Ptr Word8)
, dump -- :: Ptr PcapDumpTag -> Ptr PktHdr -> Ptr Word8 -> IO ()
-- ** 'B.ByteString' variants
, dispatchBS -- :: PcapHandle -> Int -> CallbackBS -> IO Int
, loopBS -- :: PcapHandle -> Int -> CallbackBS -> IO Int
, nextBS -- :: PcapHandle -> IO (PktHdr, B.ByteStringa)
, dumpBS -- :: Ptr PcapDumpTag -> Ptr PktHdr -> B.ByteString -> IO ()
-- * Sending packets
, sendPacket
, sendPacketBS
-- * Conversion
, toBS
, hdrTime
, hdrDiffTime
-- * Miscellaneous
, statistics -- :: PcapHandle -> IO Statistics
, version -- :: PcapHandle -> IO (Int, Int)
, isSwapped -- :: PcapHandle -> IO Bool
, snapshotLen -- :: PcapHandle -> IO Int
) where
#ifdef BYTESTRING_IN_BASE
import qualified Data.ByteString.Base as B
import qualified Data.ByteString.Base as BU
#else
import qualified Data.ByteString.Internal as B
import qualified Data.ByteString.Unsafe as BU
#endif
import Data.Int (Int64)
import Data.Time.Clock (DiffTime, picosecondsToDiffTime)
import Data.Word (Word8, Word32)
import Foreign.Ptr (Ptr, castPtr)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import qualified Network.Pcap.Base as P
import Network.Pcap.Base (BpfProgram, Callback, Interface(..), Link(..),
Network(..), Direction(..),
PcapAddr(..), PktHdr(..), SockAddr(..), Statistics,
compileFilter, findAllDevs, lookupDev, lookupNet)
-- | Packet capture handle.
newtype PcapHandle = PcapHandle (ForeignPtr P.PcapTag)
-- | Dump file handle.
newtype DumpHandle = DumpHandle (ForeignPtr P.PcapDumpTag)
-- | Callback using 'B.ByteString' for packet body.
type CallbackBS = PktHdr -> B.ByteString -> IO ()
--
-- Open a device
--
-- | 'openOffline' opens a dump file for reading. The file format is
-- the same as used by @tcpdump@ and Wireshark. The string @\"-\"@ is
-- a synonym for @stdin@.
--
openOffline :: FilePath -- ^ name of dump file to read
-> IO PcapHandle
openOffline = fmap PcapHandle . P.openOffline
-- | 'openLive' is used to get a 'PcapHandle' that can be used to look
-- at packets on the network. The arguments are the device name, the
-- snapshot length (in bytes), the promiscuity of the interface
-- ('True' == promiscuous) and a timeout in microseconds.
--
-- The timeout allows the packet filter to delay while accumulating
-- multiple packets, which is more efficient than reading packets one
-- by one. A timeout of zero will wait indefinitely for \"enough\"
-- packets to arrive.
--
-- Using @\"any\"@ as the device name will capture packets from all
-- interfaces. On some systems, reading from the @\"any\"@ device is
-- incompatible with setting the interfaces into promiscuous mode. In
-- that case, only packets whose link layer addresses match those of
-- the interfaces are captured.
--
openLive :: String -- ^ device name
-> Int -- ^ snapshot length
-> Bool -- ^ set interface to promiscuous mode?
-> Int64 -- ^ timeout in microseconds
-> IO PcapHandle
openLive name snaplen promisc timeout =
let timeout' | timeout <= 0 = 0
| otherwise = fromIntegral (timeout `div` 1000)
in PcapHandle `fmap` P.openLive name snaplen promisc timeout'
-- | 'openDead' is used to get a 'PcapHandle' without opening a file
-- or device. It is typically used to test packet filter compilation
-- by 'setFilter'. The arguments are the link type and the snapshot
-- length.
--
openDead :: Link -- ^ datalink type
-> Int -- ^ snapshot length
-> IO PcapHandle
openDead link snaplen = PcapHandle `fmap` P.openDead link snaplen
{-# INLINE withPcap #-}
withPcap :: PcapHandle -> (Ptr P.PcapTag -> IO a) -> IO a
withPcap (PcapHandle hdl) = withForeignPtr hdl
{-# INLINE withDump #-}
withDump :: DumpHandle -> (Ptr P.PcapDumpTag -> IO a) -> IO a
withDump (DumpHandle hdl) = withForeignPtr hdl
--
-- Open a dump device
--
-- | 'openDump' opens a dump file for writing. This dump file is
-- written to by the 'dump' function.
openDump :: PcapHandle -- ^ packet capture handle
-> FilePath -- ^ name of dump file to write to
-> IO DumpHandle
openDump pch name = withPcap pch $ \hdl ->
DumpHandle `fmap` P.openDump hdl name
--
-- Set the filter
--
-- | Set a filter on the specified packet capture handle. Valid filter
-- strings are those accepted by @tcpdump@.
setFilter :: PcapHandle -- ^ handle on which to set filter
-> String -- ^ filter string
-> Bool -- ^ optimize?
-> Word32 -- ^ IPv4 network mask
-> IO ()
setFilter pch filt opt mask = withPcap pch $ \hdl ->
P.setFilter hdl filt opt mask
--
-- Set or read the device mode (blocking/nonblocking)
--
-- | Set the given 'PcapHandle' into non-blocking mode if the second
-- argument is 'True', otherwise put it in blocking mode. Note that
-- the 'PcapHandle' must have been obtained from 'openLive'.
--
setNonBlock :: PcapHandle -- ^ must have been obtained from 'openLive'
-> Bool -- ^ set\/unset blocking mode
-> IO ()
setNonBlock pch block = withPcap pch $ \hdl -> P.setNonBlock hdl block
-- | Return the blocking status of the 'PcapHandle'. 'True' indicates
-- that the handle is non-blocking. Handles referring to dump files
-- opened by 'openDump' always return 'False'.
getNonBlock :: PcapHandle -> IO Bool
getNonBlock pch = withPcap pch P.getNonBlock
-- | Specify the direction in which packets are to be captured.
-- Complete functionality is not necessarily available on all
-- platforms.
setDirection :: PcapHandle -> Direction -> IO ()
setDirection pch dir = withPcap pch $ \hdl -> P.setDirection hdl dir
{-# INLINE hdrTime #-}
-- | Get the timestamp of a packet as a single quantity, in
-- microseconds.
hdrTime :: PktHdr -> Int64
hdrTime pkt = fromIntegral (hdrSeconds pkt) * 1000000 +
fromIntegral (hdrUseconds pkt)
-- | Get the timestamp of a packet as a 'DiffTime'.
hdrDiffTime :: PktHdr -> DiffTime
hdrDiffTime pkt = picosecondsToDiffTime (fromIntegral (hdrTime pkt) * 1000000)
--
-- Reading packets
--
-- | Wrap a callback that expects a 'B.ByteString' so that it is
-- usable as a regular 'Callback'.
wrapBS :: CallbackBS -> Callback
wrapBS f hdr ptr = do
let len = hdrCaptureLength hdr
bs <- B.create (fromIntegral len) $ \p -> B.memcpy p ptr (fromIntegral len)
f hdr bs
-- | Collect and process packets.
--
-- The count is the maximum number of packets to process before
-- returning. A count of -1 means process all of the packets received
-- in one buffer (if a live capture) or all of the packets in a dump
-- file (if offline).
--
-- The callback function is passed two arguments, a packet header
-- record and a pointer to the packet data (@Ptr Word8@). THe header
-- record contains the number of bytes captured, which can be used to
-- marshal the data into a list, array, or 'B.ByteString' (using
-- 'toBS').
--
dispatch :: PcapHandle
-> Int -- ^ number of packets to process
-> Callback -- ^ packet processing function
-> IO Int -- ^ number of packets read
dispatch pch count f = withPcap pch $ \hdl -> P.dispatch hdl count f
-- | Variant of 'dispatch' for use with 'B.ByteString'.
dispatchBS :: PcapHandle
-> Int -- ^ number of packets to process
-> CallbackBS -- ^ packet processing function
-> IO Int -- ^ number of packets read
dispatchBS pch count f = withPcap pch $ \hdl -> P.dispatch hdl count (wrapBS f)
-- | Similar to 'dispatch', but loop until the number of packets
-- specified by the second argument are read. A negative value loops
-- forever.
--
-- This function does not return when a live read timeout occurs. Use
-- 'dispatch' instead if you want to specify a timeout.
loop :: PcapHandle
-> Int -- ^ number of packets to read (-1 == loop forever)
-> Callback -- ^ packet processing function
-> IO Int -- ^ number of packets read
loop pch count f = withPcap pch $ \hdl -> P.loop hdl count f
-- | Variant of 'loop' for use with 'B.ByteString'.
loopBS :: PcapHandle
-> Int -- ^ number of packets to read (-1 == loop forever)
-> CallbackBS -- ^ packet processing function
-> IO Int -- ^ number of packets read
loopBS pch count f = withPcap pch $ \hdl -> P.loop hdl count (wrapBS f)
-- | Send a raw packet through the network interface.
sendPacket :: PcapHandle
-> Ptr Word8 -- ^ packet data (including link-level header)
-> Int -- ^ packet size
-> IO ()
sendPacket pch buf size = withPcap pch $ \hdl -> P.sendPacket hdl buf size
-- | Variant of 'sendPacket' for use with 'B.ByteString'.
sendPacketBS :: PcapHandle
-> B.ByteString -- ^ packet data (including link-level header)
-> IO ()
sendPacketBS pch s = BU.unsafeUseAsCStringLen s $ \(buf, len) ->
sendPacket pch (castPtr buf) len
-- | Represent a captured packet as a 'B.ByteString'. Suitable for
-- use as is with the result of 'next', or use @'curry' 'toBS'@ inside
-- a 'Callback' with 'dispatch'.
toBS :: (PktHdr, Ptr Word8) -> IO (PktHdr, B.ByteString)
toBS (hdr, ptr) = do
let len = hdrCaptureLength hdr
s <- B.create (fromIntegral len) $ \p -> B.memcpy p ptr (fromIntegral len)
return (hdr, s)
-- | Read the next packet (equivalent to calling 'dispatch' with a
-- count of 1).
next :: PcapHandle -> IO (PktHdr, Ptr Word8)
next pch = withPcap pch P.next
nextBS :: PcapHandle -> IO (PktHdr, B.ByteString)
nextBS pch = withPcap pch P.next >>= toBS
-- | Write the packet data given by the second and third arguments to
-- a dump file opened by 'openDead'. 'dump' is designed so it can be
-- easily used as a default callback function by 'dispatch' or 'loop'.
dump :: DumpHandle
-> Ptr PktHdr -- ^ packet header record
-> Ptr Word8 -- ^ packet data
-> IO ()
dump dh hdr pkt = withDump dh $ \hdl -> P.dump hdl hdr pkt
dumpBS :: DumpHandle
-> Ptr PktHdr -- ^ packet header record
-> B.ByteString -- ^ packet data
-> IO ()
dumpBS dh hdr s =
withDump dh $ \hdl ->
BU.unsafeUseAsCString s $ P.dump hdl hdr . castPtr
--
-- Datalink manipulation
--
-- | Returns the datalink type associated with the given handle.
datalink :: PcapHandle -> IO Link
datalink pch = withPcap pch P.datalink
-- | Sets the datalink type for the given handle.
setDatalink :: PcapHandle -> Link -> IO ()
setDatalink pch link = withPcap pch $ \hdl -> P.setDatalink hdl link
-- | List all the datalink types supported by the given
-- handle. Entries from the resulting list are valid arguments to
-- 'setDatalink'.
listDatalinks :: PcapHandle -> IO [Link]
listDatalinks pch = withPcap pch P.listDatalinks
-- | Returns the number of packets received, the number of packets
-- dropped by the packet filter and the number of packets dropped by
-- the interface (before processing by the packet filter).
statistics :: PcapHandle -> IO Statistics
statistics pch = withPcap pch P.statistics
-- | Version of the library. The returned pair consists of the major
-- and minor version numbers.
version :: PcapHandle -> IO (Int, Int)
version pch = withPcap pch P.version
-- | 'isSwapped' returns 'True' if the current dump file uses a
-- different byte order than the one native to the system.
isSwapped :: PcapHandle -> IO Bool
isSwapped pch = withPcap pch P.isSwapped
-- | The snapshot length that was used in the call to 'openLive'.
snapshotLen :: PcapHandle -> IO Int
snapshotLen pch = withPcap pch P.snapshotLen
|