多网口UDP发包无法收到回包排查与解决

最近几周几乎都是单休,加班很多,也遇到了很多未知的问题,杂事也多时间比较紧张,也没有多少空余来进行一些总结积累。这点让我很是怀念起几年前的日子,任务安排周期长,做技术纯粹又专心。

前几天遇到了一个UDP发包无法收到回包的问题,趁着今天不加班来记录回顾一下。

网络环境

两台主机,每台主机上分别有两个网口。

主机1的Ip地址:

  • 管理口:192.168.1.2
  • 网口一:2006:470:8192:beef:6114:e3d6:a597:aea6
  • 网口二:2006:470:8192:beef:d45e:a0fa:9c9c:2e4b

主机2的Ip地址:

  • 管理口:192.168.1.3
  • 网口一:2005:470:8192:beef:9020:c015:a801:1301
  • 网口二:2005:470:8192:beef:68af:d2e6:a0e3:6fb1

如下图:
tp

udpServer

func udpServerStart() {
	srcAddr := &net.UDPAddr{
		IP:   net.IPv6zero,
		Port: 12345,
	}
	//监听不绑定IP
	conn, _ := net.ListenUDP("udp6", srcAddr)
	fmt.Printf("udp server bind on:[%s]\n", conn.LocalAddr())
	for {
		buffer := make([]byte, 1024)
		//将数据读到buffer中
		n, remoteAddr, _ := conn.ReadFromUDP(buffer)
		fmt.Printf("remote addr:[%s] \n data:[%v] str:[%s]\n", remoteAddr.String(), buffer[:n], string(buffer[:n]))
		//发送回包
		_, _ = conn.WriteToUDP([]byte("hello client"), remoteAddr)
	}
}

udpClient

再写个httpServer,在"udpTest"接口中实现个udpClient,用来发udp数据包

func httpServerStart() {
	udpTestHandler := func(writer http.ResponseWriter, request *http.Request) {
		ipStr := request.FormValue("ip")
		fmt.Printf("req param,ip:[%s]\n", net.ParseIP(ipStr))
		socket, err := net.DialUDP("udp6", nil,
			&net.UDPAddr{
				IP:   net.ParseIP(ipStr),
				Port: 12345,
			},
		)
		if err != nil {
			fmt.Println("连接UDP服务器失败,err: ", err)
			return
		}
		defer socket.Close()
		sendData := []byte("Hello Server")
		_, err = socket.Write(sendData) // 发送数据
		if err != nil {
			fmt.Println("发送数据失败,err: ", err)
			return
		}
		data := make([]byte, 4096)
		n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
		if err != nil {
			fmt.Println("接收数据失败, err: ", err)
			return
		}
		fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
	}

	handler := http.NewServeMux()
	handler.Handle("/udpTest", http.TimeoutHandler(http.HandlerFunc(udpTestHandler), time.Second*3, "timeout"))

	server := &http.Server{Addr: ":8096", Handler: handler}
	err := server.ListenAndServe()
	if nil != err {
		log.Fatal(err) //显示错误日志
	}
}

发包测试

在主机1上向主机2的两个端口发包

http://192.168.1.2:8096/udpTest?ip=2005:470:8192:beef:68af:d2e6:a0e3:6fb1
http://192.168.1.3:8096/udpTest?ip=2005:470:8192:beef:9020:c015:a801:1301

主机1没有日志打印,且http接口都显示超时了。

观察主机2的日志:

udp server bind on:[[::]:12345]

remote addr:[[2003:470:8192:beef:ca43:6a6a:9c4c:b1a0]:48406] 
 data:[[72 101 108 108 111 32 83 101 114 118 101 114]] str:[Hello Server]
reply addr:[[2003:470:8192:beef:ca43:6a6a:9c4c:b1a0]:48406] length:12 

remote addr:[[2003:470:8192:beef:ca43:6a6a:9c4c:b1a0]:35365] 
 data:[[72 101 108 108 111 32 83 101 114 118 101 114]] str:[Hello Server]
reply addr:[[2003:470:8192:beef:ca43:6a6a:9c4c:b1a0]:35365] length:12 

主机2收到了udp包,也发送了回包。
但主机1没有并没有收到,包走丢了?

主机2上用tcpdump抓个包看一下
host2pcap

看到主机2只收到了主机1的udp包,但没有回包发出去,那么回包发到哪去了?

问题定位

因为之前有试过主机1和主机2的tcp和http请求都是通的,但UDP却不通确实感到疑惑。
我们知道http是基于TCP的,UDP和TCP相比也是更加简单的,也无需握手。

查阅了一些资料后发现扯淡的文档居多,直到遇到了这篇好文《Linux路由应用-使用策略路由实现访问控制》,重点片段为:

UDP无连接,不可靠,只负责将数据尽力而为传到目的主机,它对源和目的IP地址的管理很松散,UDP数据流(更确切的并不能称为数据流)是单包的。在两端都没有显式bind到具体的IP地址的情况下,最终的数据包可以使用任意的本机地址,关键看路由的结果。数据到达对端之后,如果对端也没有显示bind到具体的IP地址,那么回复包的源地址也可能不再是初始包的目的地址。

解决办法

其解决办法在《Linux路由应用-使用策略路由实现访问控制》文章中也描述了可以使用配置系统策略路由的方式来解决,这里再总结一下。

方法一:配置策略路由

这部分参考《Linux路由应用-使用策略路由实现访问控制》文章中添加策略路由表部分即可。

方法二:配置系统路由

如果通信时很明确知道数据包是发往某个口的,则可以添加一条系统默认路由来解决。如:

ip -6 route add ::/0 dev ppp0 metric 60

这样当主机二再进行回包时,就会指定使用ppp0口进行回包了。

也可以通过udp进行socket通信时获取到包中的目的地址,再从UDP包中获取出源地址灵活添加系统路由配置。
常见的方式为,可以通过开启IPPROTO_IP进行获取。
如为go语言实现,可通过如下代码开启:

    syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1)

再调用ReadMsgUDP就可以获取到的具体的目的IP了。
详细代码可参考:
udp_ip_pktinfo.go

方法三:udp server绑定具体的IP

当然还有最后一种简单有用的方法,udp server进行监听时对每个网口的ip地址都进行一次绑定,简单方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水中加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值