使用gopacket发送UDP包至127.0.0.1,Wireshark可见但netcat未接收
为什么用gopacket发送的环回UDP包能在Wireshark看到但无法到达netcat?
问题描述
我尝试用gopacket向127.0.0.1发送UDP包,代码如下:
package main import ( "fmt" "net" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" ) func main() { handle, err := pcap.OpenLive("lo", 1500, false, pcap.BlockForever) if err != nil { fmt.Printf("%s\n", err.Error()) return } eth := layers.Ethernet{ EthernetType: layers.EthernetTypeIPv4, SrcMAC: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, DstMAC: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, } ip := layers.IPv4{ Version: 4, TTL: 64, SrcIP: net.IP{127, 0, 0, 1}, DstIP: net.IP{127, 0, 0, 1}, Protocol: layers.IPProtocolUDP, } udp := layers.UDP{ SrcPort: 62003, DstPort: 8080, } udp.SetNetworkLayerForChecksum(&ip) payload := []byte{'a', 'b', 'c', '\n'} options := gopacket.SerializeOptions{ ComputeChecksums: true, FixLengths: true, } buffer := gopacket.NewSerializeBuffer() err = gopacket.SerializeLayers(buffer, options, ð, &ip, &udp, gopacket.Payload(payload), ) if err != nil { fmt.Printf("[-] Serialize error: %s\n", err.Error()) return } outgoingPacket := buffer.Bytes() err = handle.WritePacketData(outgoingPacket) if err != nil { fmt.Printf("[-] Error while sending: %s\n", err.Error()) return } }
我在终端用netcat监听:nc -ulp 8080 -s 127.0.0.1,运行代码后,Wireshark能在环回接口看到生成的数据包且校验和正确,但数据包从未到达netcat。请问这是为什么?
原因分析
其实问题出在环回接口的特殊处理逻辑上:
- 当你用
pcap直接往环回接口写入完整的以太网帧时,系统内核的网络栈根本不会处理这些帧。环回接口本来就是绕过物理链路层设计的,内核期望收到的是IP层及以上的数据包,而不是你手动构造的以太网帧。 - Wireshark能抓到这些帧是因为它是在链路层做捕获的,但内核不会去解析你手动塞进去的以太网帧,自然也就不会把数据包往上传递给netcat这类应用程序。
解决方案
这里有两种可行的解决方式:
方案1:跳过以太网层,直接发送IP数据包
修改代码,去掉以太网层的构造,只序列化IP、UDP和Payload部分,这样内核就能正确处理数据包了:
package main import ( "fmt" "net" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" ) func main() { handle, err := pcap.OpenLive("lo", 1500, false, pcap.BlockForever) if err != nil { fmt.Printf("%s\n", err.Error()) return } defer handle.Close() // 构造IP层 ip := layers.IPv4{ Version: 4, TTL: 64, SrcIP: net.IP{127, 0, 0, 1}, DstIP: net.IP{127, 0, 0, 1}, Protocol: layers.IPProtocolUDP, } // 构造UDP层并计算校验和 udp := layers.UDP{ SrcPort: 62003, DstPort: 8080, } udp.SetNetworkLayerForChecksum(&ip) payload := []byte{'a', 'b', 'c', '\n'} options := gopacket.SerializeOptions{ ComputeChecksums: true, FixLengths: true, } buffer := gopacket.NewSerializeBuffer() // 只序列化IP、UDP和Payload,跳过以太网层 err = gopacket.SerializeLayers(buffer, options, &ip, &udp, gopacket.Payload(payload)) if err != nil { fmt.Printf("[-] Serialize error: %s\n", err.Error()) return } outgoingPacket := buffer.Bytes() err = handle.WritePacketData(outgoingPacket) if err != nil { fmt.Printf("[-] Error while sending: %s\n", err.Error()) return } fmt.Println("[+] Packet sent successfully") }
方案2:使用Go标准库直接发送(更简单可靠)
如果你不需要手动构造底层数据包的需求,直接用Go标准库的net包发送UDP包会更省心,因为它会通过系统网络栈正常传递数据包:
package main import ( "fmt" "net" ) func main() { // 建立UDP连接 conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 62003}, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8080}) if err != nil { fmt.Printf("[-] Dial error: %s\n", err.Error()) return } defer conn.Close() // 发送数据 _, err = conn.Write([]byte{'a', 'b', 'c', '\n'}) if err != nil { fmt.Printf("[-] Send error: %s\n", err.Error()) return } fmt.Println("[+] Data sent successfully") }
验证
运行修改后的任意一个方案代码,你就能看到netcat成功接收到abc\n的内容了。
内容的提问来源于stack exchange,提问作者paddlesteamer




