go标准库不支持raw socket,需用syscall/cgo或第三方库如mdlayher/raw(linux专属);跨平台应调用系统ping命令。

Go 里没法直接发 Raw Socket,别试 net.Dial("ip4:icmp", ...)
Go 标准库的 net 包压根不支持构造和发送原始 IP 包(比如自定义 ICMP、TCP 头),调用类似 net.Dial("ip4:icmp", ...) 会直接 panic 或返回 "protocol not supported"。这不是权限问题,是设计如此——标准库只封装到传输层(TCP/UDP),IP 层以下交给系统或第三方库。
常见错误现象:listen ip4:icmp: socket: operation not permitted(Linux 下即使 root 也报这个)、unknown network ip4:icmp(Windows/macOS 更常见)。
- 真正能发 Raw Socket 的路径只有两条:调用系统 syscall(如
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP))或用 cgo 封装 C; - 更现实的选择是用成熟封装库,比如
gopacket+pcap(需 root/admin 权限)或github.com/mdlayher/raw(纯 Go,Linux-only,依赖AF_PACKET); - 注意:macOS 和 Windows 对 Raw Socket 限制极严,
AF_PACKET在 macOS 不可用,Windows 上得走 NDIS 或 WinPCAP/Libpcap 兼容层。
github.com/mdlayher/raw 发 ICMP Echo Request 最简路径
这是目前最轻量、无 cgo、纯 Go 的方案,但只支持 Linux(内核 ≥ 2.6.27),且必须用 AF_PACKET 绕过内核协议栈——意味着你要自己填 MAC 头、IP 头、ICMP 头,还要知道目标 MAC(通常得先发 ARP)。
使用场景:局域网内探测设备存活、定制化网络诊断工具、教学演示协议组装过程。
立即学习“go语言免费学习笔记(深入)”;
- 必须用 root 运行(
sudo go run main.go),否则raw.ListenPacket返回operation not permitted; - 不能跨网段直发,因为没路由逻辑,得手动处理二层转发;
- 示例关键步骤:
conn, err := raw.ListenPacket("eth0", &raw.Config{Protocol: layers.EthernetTypeIPv4})
// 自己拼 Ethernet + IPv4 + ICMPv4 头,用 gopacket.SerializeLayers
buf, _ := gopacket.SerializeLayers(buf, opts,
&layers.Ethernet{SrcMAC: srcMAC, DstMAC: dstMAC},
&layers.IPv4{SrcIP: srcIP, DstIP: dstIP, Protocol: layers.IPProtocolICMPv4},
&layers.ICMPv4{TypeCode: layers.ICMPv4EchoRequest, Id: id, Seq: seq},
)
_, err = conn.Write(buf)
为什么不用 gopacket 单独发包?它不负责发送
gopacket 是个封包/解包库,不是发包引擎。它的 SerializeLayers 只生成字节切片,不提供发送能力——你得自己找地方把这坨 bytes 塞进网卡。
常见误解:看到文档里有 Handle.WritePacket 就以为能直接发,其实那只是 pcap.Handle(来自 libpcap)的写入接口,而 libpcap 默认只读不写;开启写模式需要特殊编译和权限,且行为不稳定。
- Linux 下推荐组合:
gopacket拼包 +github.com/mdlayher/raw发送; - 想跨平台?基本没得选——老实用
exec.Command("ping", "-c1", ip)调系统命令,或者接受 macOS/Windows 上只能做 UDP/TCP 层的“伪 raw”; - 性能影响:每次发包都要 malloc 新 buffer、序列化多层结构体,高频发包(>1000pps)建议复用
gopacket.SerializeBuffer和预分配 slice。
权限、CAP_NET_RAW 和 Docker 里的坑
Linux 下即使 root,容器里跑 raw socket 也常失败,因为默认丢掉了 CAP_NET_RAW 能力。错误信息通常是 operation not permitted,而不是更具体的 socket 错误。
容易被忽略的地方:
- Docker 启动时得加
--cap-add=NET_RAW,光--privileged不够稳; - systemd 服务要显式配置
CapabilityBoundingSet=CAP_NET_RAW和AmbientCapabilities=CAP_NET_RAW; - 某些云主机(AWS EC2、GCP VM)禁用了
AF_PACKET,检查/proc/sys/net/core/bpf_jit_enable或直接strace -e socket go run main.go看系统调用是否被拒绝。
真要跨平台又不想碰权限,就别硬刚 raw socket——UDP/TCP 已经能满足绝大多数“发数据”的需求,所谓“原始”,很多时候只是心理预期而已。










