如何在数据链路层(以太网)监听并在传输层响应

use*_*494 7 unix sockets go

我想要做的是侦听 IPv6 的以太网帧并响应特定端口上的 UDP 调用。

我能够捕获我关心的以太网帧并解析出 UDP 有效负载,但是当我尝试回显该有效负载时,我遇到了问题。这是我的“服务器”代码:

func main() {
    fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(htons(syscall.ETH_P_IPV6)))
    iface, err := net.InterfaceByName("lo")
    if err != nil {
        log.Fatal(err)
    }

    err = syscall.BindToDevice(fd, iface.Name)
    if err != nil {
        log.Fatal(err)
    }

    for {
        buf := make([]byte, iface.MTU)
        n, callerAddr, err := syscall.Recvfrom(fd, buf, 0)
        if err != nil {
            log.Fatal(err)
        }
        data := buf[:n]
        packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default)
        udpPacket := packet.Layer(layers.LayerTypeUDP)
        if udpPacket != nil {
            udpPck, _ := udpPacket.(*layers.UDP)

            // I only care about calls to 8080 for this example
            if udpPck.DstPort != 8080 {
                continue
            }

            err = udpPck.SetNetworkLayerForChecksum(packet.NetworkLayer()); if err != nil {
                log.Fatal(err)
            }

            log.Print(packet)
            log.Printf("UDP Port from %v --> %v", udpPck.SrcPort, udpPck.DstPort)
            log.Printf("Payload '%v'", string(udpPck.Payload))

            // Flip the source and destination so it can go back to the caller
            ogDst := udpPck.DstPort
            udpPck.DstPort = udpPck.SrcPort
            udpPck.SrcPort = ogDst

            buffer := gopacket.NewSerializeBuffer()
            options := gopacket.SerializeOptions{ComputeChecksums: true}

            // Rebuild the packet with the new source and destination port
            err := gopacket.SerializePacket(buffer, options, packet)
            if err != nil {
                log.Fatal(err)
            }

            log.Printf("Writing the payload back to the caller: %v", callerAddr)
            log.Print(packet)

            err = syscall.Sendto(fd, buffer.Bytes(), 0, callerAddr)
            if err != nil {
                log.Fatal(err)
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

然后我在同一台机器上运行的客户端代码:

func main() {
    conn, err := net.DialUDP("udp6", &net.UDPAddr{
        IP:   net.IPv6loopback,
        Port: 0,
    }, &net.UDPAddr{
        IP:   net.IPv6loopback,
        Port: 8080,
    })
    if err != nil {
        log.Fatal(err)
    }

    _, _ = conn.Write([]byte("Hello World"))

    log.Print("Waiting for response")
    buf := make([]byte, 65535)
    n, _, err := conn.ReadFrom(buf)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Response message '%v'", string(buf[:n]))
}
Run Code Online (Sandbox Code Playgroud)

来自客户端的问题是连接被拒绝read udp6 [::1]:56346->[::1]:8080: recvfrom: connection refused,我猜这可能来自 linux 内核,因为严格来说我没有将任何东西绑定到 8080。

我需要来自 IPv6 标头(上面没有看到)的数据,这就是为什么我需要在数据链路层上进行侦听,但是由于我还需要响应 UDP 请求,所以事情变得有点棘手。

我有但不喜欢的一个选项是在单独的 goroutine 中做一个标准net.ListenUDP,然后在读取数据后阻塞,直到从系统调用套接字侦听器读取 IPv6 标头,然后从那里响应 udp 连接。如果这是我唯一的选择,我会接受它,但我很想看看是否有更好的办法。

Son*_*Gao 1

我认为即使您通过构建链路层帧进行响应,您仍然需要侦听 UDP 端口。否则,系统的网络堆栈将使用 ICMP 消息进行响应,这就是导致“连接被拒绝”错误的原因。

我还没有尝试过这个,但我认为如果你从接口中删除 IP 地址,它会阻止内核 IP 堆栈在其上运行。但是,您可能需要处理 ARP 消息。

或者,您可以尝试使用TUN/TAP 接口,以便您可以完全控制用户空间中发生的情况。