Issue
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.
Solution
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.
Answered By - Song Gao