7

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.

Song Gao
  • 2,008
  • 15
  • 18
user1634494
  • 609
  • 1
  • 7
  • 23
  • Why is this tagged as C? This isn't C code. Is this language Go? – Gabriel Staples May 31 '20 at 21:31
  • @GabrielStaples It should be tagged with cgo not c, I will update it. – user1634494 May 31 '20 at 21:41
  • your code is not reproducible.. I wanted to suggest to perform a listen instead of dial in the client, but could not validate upfront... –  Jun 02 '20 at 08:18
  • @mh-cbon Does this mean you were successful in sending the ethernet frame or was there a separate issue you encountered. I am able to listen to ethernet frames but sending them is the challenging piece for me. – user1634494 Jun 02 '20 at 15:41
  • no i could not compile the server, some pieces are missing, i did not try to figure out (tbh). so i was not able to validate my idea. –  Jun 02 '20 at 17:25
  • @user1634494 Are you sure you can listen on a udp dialer? That error didn't happen when I tried using https://golang.org/pkg/net/#ListenUDP, so may be create a new connection using this? – Kashyap Jun 03 '20 at 07:22
  • Can you use a regular UDP socket to perform the send? Or, did you intend to attempt MITM to spoof a different source IP than is assigned to the interface? – jxh Jun 25 '20 at 22:50
  • @user1634494 This makes syscalls directly and has nothing todo with `cgo`. – Song Gao Sep 04 '21 at 05:42

1 Answers1

1

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.

Song Gao
  • 2,008
  • 15
  • 18