我正在设置一个基于 TUN 设备的虚拟专用网络程序,其中最重要的部分之一是 NAT 穿越。编写完这个程序后,我使用同一个局域网中的两台设备进行调试。
但是我注意到一件奇怪的事情,假设 A 想向 B 发送数据包,并且有一个外部服务器 S 具有静态 IP。我使用最常见的方式在 UDP 中实现 NAT 穿越。
A 向 B 发送一个数据包,该数据包可能被 B 的 NAT 丢弃。然后 A 向 S 发送请求,S 向 B 发送请求,然后 B 向 A 发送数据包。之后,来自 A 的数据包理论上应该被 B 接收。
但是我遇到的情况是B没有收到A的包。
我首先检查了我的 NAT 类型,它是 Port Restricted Cone NAT,所以它应该可以工作。
这是网络结构。
我认为这很可能是我的代码错误,但我无法理解。
完整代码在https://github.com/timber3252/omelet上,以下是部分代码。
客户:
int main() {
...
while (true) {
nread = read(tun_fd, buf.buffer, kNLBufferSize);
...
union {
ipv4_address_t i;
uint8_t s[4];
} dest_ip_n;
dest_ip_n.s[0] = buf.buffer[16];
dest_ip_n.s[1] = buf.buffer[17];
dest_ip_n.s[2] = buf.buffer[18];
dest_ip_n.s[3] = buf.buffer[19];
buf.header.set(packet_id.add(), PACKET_SERVER,
PACKET_TYPE_HANDSHAKE_REQUEST | PACKET_NO_REPLY,
sizeof(buf.header) + 4, local_virtual_ip_n);
std::swap(buf.buffer[0], buf.buffer[16]);
std::swap(buf.buffer[1], buf.buffer[17]);
std::swap(buf.buffer[2], buf.buffer[18]);
std::swap(buf.buffer[3], buf.buffer[19]);
aes_encrypt(reinterpret_cast<const uint8_t *>(&buf), buf.header.length,
aes_key, dbuf);
omelet_send(sockfd, dbuf,
ceil(buf.header.length / (double)kAesBlockSize) * kAesBlockSize,
0, &server_addr, server_addr_len, buf.header);
std::swap(buf.buffer[0], buf.buffer[16]);
std::swap(buf.buffer[1], buf.buffer[17]);
std::swap(buf.buffer[2], buf.buffer[18]);
std::swap(buf.buffer[3], buf.buffer[19]);
auto res = router.query(dest_ip_n.i);
...
buf.header.set(packet_id.add(), PACKET_PEERS,
PACKET_TYPE_HANDSHAKE | PACKET_NO_REPLY,
sizeof(OmeletProtoHeader), local_virtual_ip_n);
aes_encrypt(reinterpret_cast<const uint8_t *>(&buf), buf.header.length,
aes_key, dbuf);
sockaddr_in peer_addr{};
...
socklen_t peer_addr_len = sizeof(sockaddr_in);
omelet_send(sockfd, dbuf,
ceil(buf.header.length / (double)kAesBlockSize) * kAesBlockSize,
0, &peer_addr, peer_addr_len, buf.header);
buf.header.set(packet_id.add(), PACKET_PEERS,
PACKET_TYPE_RAW_IP_PACKET | PACKET_NO_REPLY,
sizeof(OmeletProtoHeader) + nread, local_virtual_ip_n);
aes_encrypt(reinterpret_cast<const uint8_t *>(&buf), buf.header.length,
aes_key, dbuf);
omelet_send(sockfd, dbuf,
ceil(buf.header.length / (double)kAesBlockSize) * kAesBlockSize,
0, &peer_addr, peer_addr_len, buf.header);
}
...
}
void recv_thread() {
...
case PACKET_TYPE_RAW_IP_PACKET: {
int nwrite =
write(tun_fd, arg->first->data,
arg->first->header.length - sizeof(arg->first->header));
}
...
}
服务器:
case PACKET_TYPE_HANDSHAKE_REQUEST: {
union {
ipv4_address_t i;
uint8_t s[4];
} dest_ip_n;
memcpy(dest_ip_n.s, arg->first->data, 4);
Peer source_peer = al.query(arg->first->header.virtual_ip_n);
Peer dest_peer = al.query(dest_ip_n.i);
if (dest_peer.ip_n == 0 || dest_peer.port_n == 0) break;
arg->first->header.set(packet_id.add(), PACKET_SERVER,
PACKET_TYPE_HANDSHAKE_REQUEST | PACKET_NO_REPLY,
sizeof(arg->first->header) + sizeof(Peer), arg->first->header.virtual_ip_n);
sockaddr_in peer_addr{};
...
memcpy(arg->first->data, &source_peer, sizeof source_peer);
aes_encrypt(reinterpret_cast<const uint8_t *>(arg->first),
arg->first->header.length, aes_key, sendout);
omelet_send(
sockfd, sendout,
(size_t)ceil(arg->first->header.length / (double)kAesBlockSize) *
kAesBlockSize,
0, &(peer_addr), sizeof peer_addr, arg->first->header, false);
...
break;
}
我在机器 A 上使用 ping 到 B,但没有响应。数据包已成功发送到 B,但 B 无法接收任何数据包。