Go 如何获取UDP连接的真实本地地址
我想要一个基于linux的udp服务器。该主机上设置了一些IP(例如1.1.1.1,1.1.1.22001::1:1:1:1),我希望服务器在所有IP上侦听,如下所示(示例为9090)Go 如何获取UDP连接的真实本地地址,go,Go,我想要一个基于linux的udp服务器。该主机上设置了一些IP(例如1.1.1.1,1.1.1.22001::1:1:1:1),我希望服务器在所有IP上侦听,如下所示(示例为9090) udp6 0:::9090:::* 服务器代码如下所示 package main import ( "fmt" "net" ) func main() { udpAddr, err := net.ResolveUDPAddr("udp
udp6 0:::9090:::*
服务器代码如下所示
package main
import (
"fmt"
"net"
)
func main() {
udpAddr, err := net.ResolveUDPAddr("udp", ":9090")
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
fmt.Println(err)
return
}
var data [1024]byte
n, addr, err := conn.ReadFromUDP(data[:])
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
fmt.Println(addr)
// this is not my wanted result. it will print [::]:9090
fmt.Println(conn.LocalAddr())
}
root@test-VirtualBox:/home/test/mygo/src/udp# go run s2.go
{[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 0}
[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
22:20:47.009712 IP6 ::1.43305 > ::1.1025: UDP, length 6
当客户端拨号到此服务器时(dst_字符串为1.1.1.1:9090)
实际结果:
服务器将使用
[:]:9090
例外结果:
服务器应该打印
1.1.1.1:9090
如何做到这一点
顺便说一句:我知道udp服务器是否只监听1.1.1.1:9090就能做到这一点。但是服务器有很多ip,我希望服务器侦听所有ip和LocalAddr()可以打印1.1.1.1:9090
监听所有地址有两种方法,其中一种是枚举所有接口,获取它们的所有IP地址,并绑定到所有接口。大量的工作,以及不可移动的工作。我们真的不想那样做。您还需要监视到达的新地址
其次,只需绑定到0.0.0.0
和:
!这对于TCP和其他面向连接的协议非常有效,但对于UDP和其他无连接的协议可能会以静默方式失败。怎么会?当数据包在0.0.0.0
上传入时,我们不知道它发送到哪个IP地址。在回复这样的数据包时,这是一个问题——正确的源地址是什么?因为我们是无连接的(因此是无状态的),内核不知道该做什么
因此,它选择了最合适的地址,而这可能是错误的地址。有一些启发式方法可以使一些内核更可靠地做正确的事情,但没有保证
在数据报套接字上接收数据包时,我们通常使用recvfrom(2)
,但这并不提供丢失的数据位:数据包实际发送到哪个IP地址。没有recvfromto()
。输入非常强大的recvmsg(2)
Recvmsg()
允许根据setsockopt()
的请求,获取每个数据报的大量参数
我们可以请求的参数之一是数据包的原始目标IP地址。IPv4 对于Linux,使用名为
IP\u PKTINFO
的setsockopt()
,它将在名为IP\u PKTINFO
的recvmsg()上获得一个参数,该参数在ipi\u addr
字段中隐藏一个4字节的IP地址
看起来,net
包中最接近recvmsg()
的东西是,它的文档说明
这些包和可用于操作oob
中的IP级别套接字选项
因此,这似乎是一条前进的道路
我还要明确以下几点(从上面的文本中看不明显):
- 看来,Go的stdlib的
net
软件包并没有一种标准且易于使用的方式来满足您的需求
- 当在通配符地址上接收数据报时,获取数据报目标地址的方法似乎并没有真正标准化,因此不同内核的实现不同
- 虽然看起来
net.IPConn.ReadMsgIP
包装了recvmsg(2)
,但我首先要在Go标准库的源代码中验证这一点。请注意,stdlib包含它所支持的所有平台的代码,因此请确保您了解它们是什么
- 也许会有帮助。
syscall
包也是如此,如果库存不足
非常感谢kostix的回复。
根据IP_PKTINFO提示,我发现下面的代码可以直接解决我的ipv4问题
但是对于ipv6,结果仍然不例外
package main
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"syscall"
)
func main() {
serverAddr, _ := net.ResolveUDPAddr("udp", ":9999")
sConn, _ := net.ListenUDP("udp", serverAddr)
file, _ := sConn.File()
syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IP_PKTINFO, 1)
data := make([]byte, 1024)
oob := make([]byte, 2048)
sConn.ReadMsgUDP(data, oob)
oob_buffer := bytes.NewBuffer(oob)
msg := syscall.Cmsghdr{}
binary.Read(oob_buffer, binary.LittleEndian, &msg)
if msg.Level == syscall.IPPROTO_IPV6 && msg.Type == syscall.IP_PKTINFO {
packet_info := syscall.Inet6Pktinfo{}
binary.Read(oob_buffer, binary.LittleEndian, &packet_info)
fmt.Println(packet_info)
// the ipv6 address is not my wanted result
fmt.Println(packet_info.Addr)
}
}
结果如下
package main
import (
"fmt"
"net"
)
func main() {
udpAddr, err := net.ResolveUDPAddr("udp", ":9090")
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
fmt.Println(err)
return
}
var data [1024]byte
n, addr, err := conn.ReadFromUDP(data[:])
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
fmt.Println(addr)
// this is not my wanted result. it will print [::]:9090
fmt.Println(conn.LocalAddr())
}
root@test-VirtualBox:/home/test/mygo/src/udp# go run s2.go
{[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 0}
[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
22:20:47.009712 IP6 ::1.43305 > ::1.1025: UDP, length 6
tcpdump嗅探器如下所示
package main
import (
"fmt"
"net"
)
func main() {
udpAddr, err := net.ResolveUDPAddr("udp", ":9090")
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
fmt.Println(err)
return
}
var data [1024]byte
n, addr, err := conn.ReadFromUDP(data[:])
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
fmt.Println(addr)
// this is not my wanted result. it will print [::]:9090
fmt.Println(conn.LocalAddr())
}
root@test-VirtualBox:/home/test/mygo/src/udp# go run s2.go
{[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 0}
[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
22:20:47.009712 IP6 ::1.43305 > ::1.1025: UDP, length 6
我通过设置ipv6选项来解决此ipv6问题
err=syscall.SetsockoptInt(int(fd),syscall.IPPROTO\u IPV6,syscall.IPV6\u RECVPKTINFO,1)
谢谢大家你的意思是?:RemoteAddr()
拼写错误。它是LocalAddr()