Как слушать на уровне канала данных (ethe rnet) и отвечать на транспортном уровне - PullRequest
7 голосов
/ 28 мая 2020

То, что я пытаюсь сделать, это прослушать кадры ethe rnet для IPv6 и ответить на вызовы UDP на конкретном c порту.

Я могу захватить кадры ethe rnet I заботиться о полезной нагрузке 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)
            }
        }
}

А затем мой клиентский код, работающий на том же компьютере:

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]))
}

Проблема со стороны клиента - отказ в соединении read udp6 [::1]:56346->[::1]:8080: recvfrom: connection refused, который, как я предполагаю, будет исходить от ядра linux, поскольку я, строго говоря, не привязал ничего к 8080.

Мне нужны данные из заголовка IPv6 (не видно выше), поэтому мне нужны для прослушивания на уровне канала передачи данных, но поскольку мне также нужно отвечать на запросы UDP, все становится немного сложнее.

У меня есть вариант, но я не хочу, чтобы в отдельной горутине выполнялась стандартная net.ListenUDP, а затем блокируется после чтения данных до тех пор, пока заголовок IPv6 не будет прочитан из прослушивателя сокета syscall, а затем оттуда отвечает на соединение udp. Если это мой единственный вариант, я воспользуюсь им, но мне было бы интересно посмотреть, могу ли я сделать что-нибудь получше.

...