Linux UDP 服务器始终显示 127.0.0.1 作为源 IP

Linux UDP 服务器始终显示 127.0.0.1 作为源 IP

我正在尝试从不同的环回 IP(例如 127.0.0.100)发送 UDP 数据包。

接收数据包没有问题,但总是显示源IP为127.0.0.1,并在那里回复。

有没有办法让它显示正确的源 IP?

Server side

$ nc -ul 0.0.0.0 27430
ACK
Ncat: Connection refused.

Client side

$ (echo "TEST" && sleep 1) | nc -u 127.0.0.100 27430

TCP Dump

$ sudo tcpdump -nn --interface lo -s 0  | grep 27430
21:46:37.311031 IP 127.0.0.1.46033 > 127.0.0.100.27430: UDP, length 5
21:46:39.834975 IP 127.0.0.1.27430 > 127.0.0.1.46033: UDP, length 4 <-- Replying from 127.0.0.1, instead of 127.0.0.100

而它在 macOS 上显示正确的 IP 127.0.0.100:

Server side

$ nc -ul 0.0.0.0 27430
TEST
ACK

Client side

$ (echo "TEST" && sleep 1) | nc -u 127.0.0.100 27430

TCP Dump

$ sudo tcpdump --interface lo0 -s 0 | grep 27430
14:44:28.982553 IP 127.0.0.100.60482 > 127.0.0.100.27430: UDP, length 5
14:44:31.101434 IP 127.0.0.100.27430 > 127.0.0.100.60482: UDP, length 4 <-- Replying from 127.0.0.100

答案1

网猫工具(其所有变体,包括nmap网猫) 没有针对 UDP 处理此问题的功能。

由于 BSD 套接字 API 的实现方式,与 TCP 相反,默认情况下UDP 在回复接收到的 UDP 数据报时不会在套接字上保留数据报的状态。具体来说,TCP 使用后accept(2)会获得一个处于连接状态的新套接字,并可以getsockname(2)在新套接字上使用它来了解初始连接的地址,并且无论如何都会使用正确的源地址(例如:127.0.0.100)进行回复,而getsockname(2)在唯一的 UDP 套接字上,它只会返回绑定的地址:这里是 0.0.0.0。

当收到UDP数据报时,默认情况下,无法知道它是在多个可能的地址中接收的。回复此源时,将查询路由堆栈以从“未设置”的 0.0.0.0 完成源 IP 地址。这将解析为环回接口上的 127.0.0.1,因为这是在其上设置的主地址。信息 127.0.0.100 已丢失。当多宿主系统通过未配置此地址的接口接收到 IP 地址的 UDP 数据报时,会发生相同的问题:通常会使用相同的接口发出回复(因为路由堆栈确定回复目标将使用此接口)并再次选择错误的源地址:接口上的地址,而不是初始 UDP 数据报发送到的地址。


为了使 UDP 能够正确处理这个问题,有两种方法:

  • 多次分别绑定到所有可能建立连接的地址

    然后,如果在其中一个套接字上接收到数据,则回复将使用绑定的地址(正如getsockname(2)所显示的),这只能是正确的源,因为这是对在插座。

    这是不切实际的,原因有二:这意味着这将需要多个网猫命令,每个绑定一个,更重要的是回送接口有大约 2^24 种可能性。无论如何,对于 OP 的具体示例,这将适用于服务器端(以下ncat是 nmap 的网猫,即 OP 的nc):

    $ echo ACK | ncat -ul 127.0.0.100 27430
    TEST
    
    $ (echo "TEST" && sleep 1) | ncat -u 127.0.0.100 27430
    ACK
    $ 
    

    这就是 DNS 服务器通常所做的绑定 9来自 ISC:它绑定到每个系统的地址(因此仅为环回接口使用 127.0.0.1,对 127.0.0.100:53 的查询将失败,ICMP 端口不可达,因为没有任何东西绑定到它INADDR_ANY)并且会在地址出现或消失时动态调整。

  • 使用IP_PKTINFOUDP 套接字上的套接字选项可以通过套接字辅助消息获取附加信息。

    (在 *BSD 上,或多或少等效的套接字选项是IP_RECVDSTADDR

    当设置了此选项,并且 UDP 套接字不与connect(2)+ read(2)/一起使用write(2)而是与recvmsg(2)/ sendmsg(2)/一起使用时cmsg(3),这将允许通过 访问其他信息msghdr->msg_controlIP_PKTINFO将允许知道接收 UDP 数据报的本地(目标)地址和接口,并允许指定回复时要使用的本地(源)地址和/或接口,而不是让路由堆栈选择。

    这就是 DNS 服务器的作用未绑定启用该选项时来自 NLnet Labs interface-automatic


工具socat,具有比网猫有一个内置机制可供使用IP_PKTINFO

代替:

nc -ul 0.0.0.0 27430

可以使用:

socat -u udp4-recvfrom:27430,ip-pktinfo,reuseport,fork \
    system:'socat -u exec\:\"echo ACK\" udp4-datagram\:$SOCAT_PEERADDR\:$SOCAT_PEERPORT\,bind=$SOCAT_IP_LOCADDR\:27430\,reuseport; cat'

它非常复杂,因为命令的第二部分必须是一个程序,它socat在接收到 UDP 数据报(包含SOCAT_IP_LOCADDR重要信息)时通过第一部分导出的环境变量获取辅助消息信息。 在这里,脚本再次运行socat以发出回复,实际上使用与命令第一部分中使用的第一个套接字共享端口的第二个套接字(因此需要),而不是使用同一个套接字执行所有操作。 然后,外壳、内壳和内部SO_REUSEPORT需要多层转义。 执行第二部分的专用脚本将避免一些转义。 这种构造不能(轻易?)允许在交互模式下使用。socatsocat

人们应该真正使用专用的工具和/或比 shell 更适合的语言,例如Python,以避免这种额外的复杂性,并使用单个插座处理所有事情。

最后,客户端请求成功了(如下ncat图所示)nmap的实施网猫,即:OP的nc):

  $ (echo "TEST" && sleep 1) | ncat -u 127.0.0.100 27430
  ACK
  $ (echo "TEST" && sleep 1) | ncat -u 127.100.100.100 27430
  ACK

相关内容