TCP-in-IP. 在日常使用中,TCP 段几乎总是直接放置在互联网数据报中,IP 和 TCP 头部之间没有 UDP 头部。这就是人们所说的“TCP/IP”。这实现起来稍微有点困难。Linux 提供了一个名为 TUN 设备的接口,允许应用程序提供整个互联网数据报,内核负责其余部分(写入以太网头部,实际上通过物理以太网卡发送等)。但现在应用程序必须自己构建完整的 IP 头部,而不仅仅是有效载荷。clash 和 v2rayn 等代理软件的实现就是基于此,在应用程序部分实现了一部分操作系统网络协议栈的部分。例如 clash.meta 内核里面的 fake ip 等功能。
TCP-in-IP-in-Ethernet. 在上述方法中,我们仍然依赖于 Linux 内核的部分网络栈。每次你的代码将 IP 数据报写入 TUN 设备时,Linux 都必须构建一个适当的链路层(以太网)帧,其中 IP 数据报作为有效载荷。这意味着 Linux 必须根据下一跳的 IP 地址确定下一跳的以太网目标地址。如果它不知道这个映射,Linux 会广播一个查询,询问“谁声明了以下 IP 地址?你的以太网地址是什么?”并等待响应。
//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface //! \param[in] ip_address IP (what ARP calls "protocol") address of the interface NetworkInterface::NetworkInterface( string_view name, shared_ptr<OutputPort> port, const EthernetAddress& ethernet_address, const Address& ip_address ) : name_( name ) , port_( notnull( "OutputPort", move( port ) ) ) , ethernet_address_( ethernet_address ) , ip_address_( ip_address ) { cerr << "DEBUG: Network interface has Ethernet address " << to_string( ethernet_address_ ) << " and IP address " << ip_address.ip() << "\n"; }
//! \param[in] dgram the IPv4 datagram to be sent //! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but //! may also be another host if directly connected to the same network as the destination) Note: the Address type //! can be converted to a uint32_t (raw 32-bit IP address) by using the Address::ipv4_numeric() method. voidNetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop ) { constuint32_t next_hop_ip = next_hop.ipv4_numeric(); constauto it = arp_table_.find(next_hop_ip); //先在对应的 arp_table 中查找,如果查找到直接发送 if (it != arp_table_.end() && it->second.expiry_time > current_time_) { EthernetFrame frame; frame.header.dst = it->second.eth_addr; frame.header.src = ethernet_address_; frame.header.type = EthernetHeader::TYPE_IPv4; //这里需要查看 util 中的 parser.hh 中定义的序列,相当于是封装了 ip 层到链路层帧的转换这一过程 Serializer serializer; dgram.serialize(serializer); frame.payload = serializer.finish();
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method voidNetworkInterface::tick( constsize_t ms_since_last_tick ) { current_time_ += ms_since_last_tick;
// 处理 ARP 表项过期 for (auto it = arp_table_.begin(); it != arp_table_.end(); ) { if (current_time_ >= it->second.expiry_time) { it = arp_table_.erase(it); } else { ++it; } }
// 处理 ARP 请求超时和重发 for (auto it = arp_request_time_.begin(); it != arp_request_time_.end(); ) { constuint32_t next_hop_ip = it->first; constuint64_t last_request_time = it->second;
// 如果自上次请求以来已超过 ARP_REQUEST_TIMEOUT_ if (current_time_ - last_request_time >= ARP_REQUEST_TIMEOUT_) { // 检查是否有尚未过期的待发送数据报 bool has_valid_pending_datagrams = false; auto pending_it = pending_dgram_info_.find(next_hop_ip);
if (pending_it != pending_dgram_info_.end()) { // 创建一个临时队列来保存未过期的数据报 std::queue<std::pair<uint64_t, InternetDatagram>> valid_datagrams;
// 检查所有待发送的数据报 while (!pending_it->second.empty()) { auto [timestamp, dgram] = pending_it->second.front(); pending_it->second.pop();