Go代码调用SO_ORIGINAL_DST在不同Ubuntu版本报错protocol not available求助
问题原因及解决方案
问题原因
- SO_ORIGINAL_DST的适用场景限制:
SO_ORIGINAL_DST是Linux内核提供的选项,仅用于获取经过iptables DNAT端口转发后的原始目标地址,仅对DNAT处理过的TCP连接有效。你的代码直接连接127.0.0.1:5432,未经过任何DNAT转发,5.15版本新内核会严格校验这种非法调用,直接返回protocol not available错误;而4.4版本旧内核校验逻辑较宽松,返回了一个无效但可解析的内存结构,导致你误以为功能正常。 - 系统调用参数不规范:你使用
syscall.GetsockoptIPv6Mreq获取SO_ORIGINAL_DST是错误的——IPv6Mreq是用于多播组管理的结构体,和SO_ORIGINAL_DST返回的sockaddr_in(IPv4地址结构)内存布局不匹配。旧内核可能因内存布局巧合让你解析到了目标地址,但新内核的严格校验直接触发错误。
解决方案
场景1:仅需获取当前连接的目标地址
如果需求只是获取连接的远程目标地址,完全不需要依赖SO_ORIGINAL_DST,直接使用Go标准库的RemoteAddr()方法即可,这是跨内核版本的通用方案:
package main import ( "fmt" "net" ) func main() { fmt.Println("aaaaaa") conn, err := net.Dial("tcp", "127.0.0.1:5432") if err != nil { panic(err) } defer conn.Close() // 直接获取远程地址 remoteAddr := conn.RemoteAddr().(*net.TCPAddr) fmt.Println(remoteAddr.IP, remoteAddr.Port) }
场景2:确实需要获取DNAT后的原始目标地址
如果业务场景确实需要处理DNAT转发后的连接,需先配置iptables DNAT规则(比如将外部端口转发到127.0.0.1:5432),再使用正确的系统调用方式获取SO_ORIGINAL_DST:
package main import ( "fmt" "net" "syscall" "unsafe" ) func main() { fmt.Println("aaaaaa") // 连接经过DNAT转发的端口(而非直接连5432) conn, err := net.Dial("tcp", "你的公网IP:转发端口") if err != nil { panic(err) } defer conn.Close() tcpConn := conn.(*net.TCPConn) f, err := tcpConn.File() if err != nil { panic(err) } defer f.Close() // 用正确的结构体获取SO_ORIGINAL_DST var addr syscall.RawSockaddrInet4 var addrLen uint32 = uint32(unsafe.Sizeof(addr)) err = syscall.Getsockopt(int(f.Fd()), syscall.IPPROTO_IP, syscall.SO_ORIGINAL_DST, unsafe.Pointer(&addr), &addrLen) if err != nil { panic(fmt.Sprintf("get addr err: %v", err)) } ip := net.IPv4(addr.Addr[0], addr.Addr[1], addr.Addr[2], addr.Addr[3]) // 端口是大端序,需要转换为小端 port := uint16(addr.Port>>8) | uint16(addr.Port&0xff) fmt.Println(ip, port) }
内容的提问来源于stack exchange,提问作者stackinmyheart
相关产品推荐
相关产品推荐

