5

Network.Socket 中提供了原始套接字类型,但在绑定套接字附近有一条注释“当前仅支持 Unix 域套接字和 Internet 系列”。如何在 haskell 中使用原始套接字?

作为工作 Python 代码,我想要实现的目标是:

import binascii
import socket

# Create raw socket.
ethType = b'FFFF' # 2 bytes, has to be >= 0x0600. FFFF is "unavailable" by IEEE.
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
sock.bind( ('lo', int(ethType,16)) )

# Create packet.
srcMac  = b'000000000000' # 6 bytes
destMac = b'000000000000' # 6 bytes
header = binascii.a2b_hex( destMac + srcMac + ethType ) # 14 bytes
message = b'Hello, World!'
sock.send(header + message)

# Receive such packets
while True: print (sock.recv(65535))

EDIT1:在 Haskell 中,我sock <- socket AF_PACKET Raw 0xFFFF用来创建一个套接字,但bindSocket需要 aSockAddr作为参数,可用的构造函数是

SockAddrInet PortNumber HostAddress  
SockAddrInet6 PortNumber FlowInfo HostAddress6 ScopeID   
SockAddrUnix String

但这些似乎都不对。

EDIT2:感谢 Yuras 的评论,我收到了工作包:

import Network.Socket
sock <- socket AF_PACKET Raw 0xFFFF
recv sock 0xFFFF

EDIT3:尝试从套接字发送数据包而不绑定它会导致异常:

sock <- socket AF_PACKET Raw 0xFFFF
send sock "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\255\255"
*** Exception: send: does not exist (No such device or address)

这是有道理的,因为内核对于实际传输数据包的接口没有任何线索。有没有办法将(原始)套接字绑定到 Haskell 中的接口?

4

4 回答 4

4

Network.Pcap可用于发送和接收原始数据。

import qualified Data.ByteString.Char8 as B
import Network.Pcap

main = do
    -- open device
    dev <- openLive "lo" 65535 False 0
    setFilter dev "ether proto 0xFFFF" True 0

    -- send
    sendPacketBS dev (B.pack "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\255\255Hello, World!")

    -- receive
    loopBS dev (-1) (\_ packet -> putStrLn (show packet))
于 2012-04-21T11:38:58.270 回答
1

有类似的任务,我能想到的唯一方法是使用FFI。当前network(撰写本文时为 2.4.0.1)只知道三个SockAddr系列,其中没有一个适用于AF_PACKET套接字。需要的是struct sockaddr_ll,这是不可用的。

有一个补丁添加了SockAddrRaw,但即使它被合并,它似乎最终还是丢失了。

我的代码很脏(抱歉,这是我第一个“严肃”的 FFI 代码),但我想我还是会分享它。

首先,我创建了BindPacketHack.hsc.

{-# LANGUAGE CPP, ForeignFunctionInterface #-}

module BindPacketHack (bindPacket) where

import Foreign
import Foreign.C.Types
import Foreign.C.String
import Network.Socket
import Network.Socket.Internal (zeroMemory)

#include <sys/socket.h>
#include <netpacket/packet.h>
#include <net/if.h>

data SockAddrLL = SockAddrLL { family :: CShort
                             , protocol :: CShort
                             , ifindex :: CInt }
   deriving (Eq, Show)

instance Storable SockAddrLL where
    sizeOf _    = (#size struct sockaddr_ll)
    alignment _ = alignment (undefined :: CInt)
    peek _      = error "peek is not implemented, sorry"
    poke ptr sa = do
        zeroMemory ptr (fromIntegral $ sizeOf sa)
        (#poke struct sockaddr_ll, sll_family) ptr (family sa)
        (#poke struct sockaddr_ll, sll_protocol) ptr (protocol sa)
        (#poke struct sockaddr_ll, sll_ifindex) ptr (ifindex sa)


foreign import ccall unsafe "if_nametoindex"
    c_if_nametoindex :: CString -> IO CInt

ifaceIndex :: String -> IO CInt
ifaceIndex name = withCString name c_if_nametoindex


foreign import ccall unsafe "bind"
    c_bind_ll :: CInt -> Ptr SockAddrLL -> CInt -> IO CInt

bindLL :: Socket -> SockAddrLL -> IO Integer
bindLL s sa = alloca $ \ptr -> do
    poke ptr sa
    ret <- c_bind_ll (fdSocket s) ptr (fromIntegral $ sizeOf sa)
    return $ toInteger ret


bindPacket :: Socket -> ProtocolNumber -> String -> IO ()
bindPacket s proto ifname = do
    ifindex <- ifaceIndex ifname
    bindLL s (sockAddrLL (fromIntegral proto) ifindex)
    return ()

您可能会注意到,这SockAddrLL是不完整的,因此我的实现非常有限。这是因为我只需要这三个字段,其余字段被清零。不过,添加更多是微不足道的。

运行hsc2hs BindPacketHack.hsc,它会解析C头文件并.hs出现一个文件。现在你可以像这样使用它:

s <- socket AF_PACKET Raw eth_PPPoEDiscovery
setFdOption (Fd $ fdSocket s) CloseOnExec True
setSocketOption s Broadcast 1
bindPacket s eth_PPPoEDiscovery "eth0"
send s rawPPPoEPacket -- don't forget Ethernet header
于 2012-12-18T12:36:39.593 回答
1

首先,你不需要绑定原始套接字来使用它。从man 7 raw

可以使用 bind(2) 调用将原始套接字绑定到特定的本地地址。如果未绑定,则接收所有具有指定 IP 协议的数据包。

如果要绑定它,则可以使用bindSocket,因为原始套接字使用与使用相同的sockaddr结构ip

原始套接字使用 ip(7) 中定义的标准 sockaddr_in 地址结构

于 2012-04-19T15:06:54.720 回答
1

Network.Socket支持此接口。

于 2012-04-19T13:50:16.137 回答