Netcat 在 UDP 响应时退出

Netcat exits on UDP response

你能解释一下这种“奇怪”的行为吗? 我在 Linux 上 运行ning netcat,作为 UDP 回显服务器:

ncat -4 --exec /bin/cat -u --listen 2000

接下来,运行客户:

$ ncat -s 192.168.1.2 -u 127.0.0.1 2000

机器有一个真实的网络适配器,地址为:192.168.1.2。 在我输入一些东西后,服务器就退出了:

$ ncat -4 --exec /bin/cat -u --listen 2000 ; x=$?; echo $x                                                                                                                                                                                                                   
0

为什么?

这是一个 ncat 限制。

让我们使用 strace 来了解一下 ncat 的目的。方便的是,strace-e 选项来过滤系统调用,例如-e %net 用于记录 network-related 系统调用。让我们先启动服务器:

$ strace -e %net ncat -4 --exec /bin/cat -u --listen 2000
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0

注意 bind 调用。套接字绑定到 0.0.0.0,这意味着它将接收发送到 127.0.0.1:2000 和 10.0.2.15:2000(我的 eth0 地址)以及属于该机器的任何其他地址的数据包.

现在让我们启动一个客户端:

$ strace -e %net  ncat -s 10.0.2.15 -u 127.0.0.1 2000
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("10.0.2.15")}, 16) = 0
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0

此时客户端正在等待输入

注意 connect 调用。 UDP 是无连接的,因此调用只记住远程地址。这很方便,因为 send 将使用记录的地址。否则,有必要在每个 send 调用中指定远程地址。 我们很快就会看到另一个含义。

现在让我们输入一些内容。客户:

sendto(3, "Hello, world!\n", 14, 0, NULL, 0) = 14

服务器:

recvfrom(3, "Hello, world!\n", 131072, MSG_PEEK, {sa_family=AF_INET, sin_port=htons(35197), sin_addr=inet_addr("10.0.2.15")}, [128->16]) = 14
connect(3, {sa_family=AF_INET, sin_port=htons(35197), sin_addr=inet_addr("10.0.2.15")}, 16) = 0
recvfrom(3, "Hello, world!\n", 8192, 0, NULL, NULL) = 14
sendto(3, "Hello, world!\n", 14, 0, NULL, 0) = 14
recvfrom(3, 0x7fff8fcc9d70, 8192, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)

服务器看到来自 10.0.2.15 的消息。第一个 recvfrom 调用是用 MSG_PEEK 标志完成的,它允许在不实际使用消息的情况下查看消息。然后 connect(为方便起见),另一个 recvfrom 消费消息,sendto 发送回复。

现在记住服务器套接字绑定到 0.0.0.0。客户端向 127.0.0.1:2000 发送了一条消息。回复必须包括服务器套接字的地址作为来源。但是系统不能使用 0.0.0.0,因为它在网络上通常没有意义。 127.0.0.1 和 10.0.2.15 都可以,但是 ncat 没有明确说明使用哪一个。所以系统最终使用 10.0.2.15 作为源。

现在回到客户端。它向 127.0.0.1:2000 发送消息,但响应来自 10.0.2.15:2000。 connect 对 UDP 套接字的另一个影响是只接受来自指定 address:port 的数据报。如果源不匹配,系统将响应 ICMP 端口不可达。这在服务器中被报告为 ECONNREFUSED 错误。

可能的修复

使用 IP_PKTINFO/IPV6_PKTINFO 辅助消息指定服务器中的预期源地址。