1

我正在设置一个基于 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 无法接收任何数据包。

4

0 回答 0