非 root 用户创建 raw socket 失败因内核限制,需 sudo 或 setcap cap_net_raw+ep;macOS 不支持 IPPROTO_RAW,须用 IPPROTO_TCP + IP_HDRINCL;IP_HDRINCL 必须在 bind 前设置;收包时需先解析 IP 头长度再定位 TCP 头。

Go 里用 syscall.Socket 创建 raw socket 失败,提示 “operation not permitted”
Linux/macOS 下非 root 用户默认不能创建 raw socket,这是内核强制限制,不是 Go 的 bug。即使你用了 syscall.SOCK_RAW 和 syscall.IPPROTO_RAW,也会卡在 syscall.Socket 返回 EPERM。
- 临时解决:用
sudo go run main.go(开发调试可用,但别上线) - 生产环境更稳妥的方式是给二进制加
CAP_NET_RAW能力:sudo setcap cap_net_raw+ep ./myprogram,之后普通用户就能运行 - macOS 还需额外注意:
IPPROTO_RAW不被支持,得用IPPROTO_TCP+IP_HDRINCL选项绕过,否则setsockopt会失败
syscall.SetsockoptInt 设置 IP_HDRINCL 没生效,发出去的包被内核重写 IP 头
没生效通常是因为调用时机错了——必须在 bind 之前设置,且 socket 类型得是 AF_INET + SOCK_RAW + IPPROTO_RAW(Linux)或 IPPROTO_TCP(macOS)。
- Linux 示例关键顺序:
fd := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)→syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)→syscall.Bind(...) - macOS 不支持
IPPROTO_RAW,得用IPPROTO_TCP,但此时IP_HDRINCL仍有效;不过你得自己填满整个 IP+TCP 头,内核只负责发包,不校验字段合法性 - 漏掉
IP_HDRINCL或设成 0,内核就会自动填充 IP 头(包括 ID、TTL、checksum),你构造的头全被覆盖
手动计算 TCP 校验和总出错,tcp.Checksum 字段发出去后被接收方丢弃
TCP 校验和不是只对 TCP 段算,而是“伪头部 + TCP 头 + TCP 数据”三段拼起来再按 16 位反码求和。伪头部包含源/目的 IP、协议号、TCP 段长度(不含 IP 头),且必须是网络字节序。
- 常见错误:IP 地址没转成大端(
binary.BigEndian.PutUint32),或伪头部里写了 0 填充而不是真实 IP - TCP 长度字段是 TCP 头长 + 数据长度(单位字节),不是整个 IP 包长;如果数据长度为奇数,末尾要补 0 字节(只参与校验和计算,不发出去)
- Go 标准库没有现成的校验和函数,别依赖
golang.org/x/net/ipv4的Checksum方法——它只适合 UDP;TCP 得自己写,或用github.com/mdlayher/packet这类专注 raw packet 的库
收到的原始 IP 包里 TCP 头偏移不对,tcp.DataOffset 解析出来是乱值
raw socket 收包时,内核默认不剥离 IP 头(除非你用 AF_PACKET),所以读出来的第一字节就是 IP 头起始。如果你直接当 TCP 头解析,肯定错。
立即学习“go语言免费学习笔记(深入)”;
- 先解析 IP 头:读前 20 字节(无 option 时),提取
IHL字段(第 0 字节低 4 位),乘以 4 得 IP 头长度,再跳过这段,才是 TCP 头起点 - 别硬编码跳 20 字节——有 IP options 时 IHL > 5,比如
IHL=6表示 IP 头 24 字节,跳少了就解析错 TCP 端口 - 接收时用
syscall.Recvfrom,缓冲区至少预留 65535 字节(最大 IP 包),否则截断后 TCP 头不完整,DataOffset就读歪了










