Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen at the data link layer (ethernet) and respond at the transport layer

Tags:

unix

go

sockets

What I am trying to do is listen to ethernet frames for IPv6 and respond to UDP calls on a specific port.

I am able to capture the ethernet frames I care about and parse out the UDP payload, but when I attempt to echo that payload back is where I have a problem. Here is my "server" code:

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

And then my client code which is running on the same machine:

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

The problem from the client side is a connection refused read udp6 [::1]:56346->[::1]:8080: recvfrom: connection refused which my guess would be coming from the linux kernel since I have not bound anything to 8080 strictly speaking.

There is data I need from the IPv6 header (not seen above) which is why I need to listen on the data link layer, but since I also need to respond to UDP requests things get a little tricky.

An option I have but don't like would be to in a separate goroutine do a standard net.ListenUDP and then block after reading data until the IPv6 header is read from the syscall socket listener, then from there responding on the udp connection. If this is my only option I will take it but I would interested to see if there is something better I could do.

like image 810
user1634494 Avatar asked May 28 '20 18:05

user1634494


1 Answers

I think you still need to listen on the UDP port even though you are responding by constructing a link layer frame. Otherwise the system's networking stack will respond with an ICMP message, which is what caused the "connection refused" error.

I haven't tried this but I think if you remove the IP address from the interface, it'd prevent the kernel IP stack from running on it. But then there might be ARP messages you need to deal with.

Alternatively you might try using a TUN/TAP interface, so that you have full control over what happens on it from user space.

like image 197
Song Gao Avatar answered Oct 08 '22 12:10

Song Gao