NAT网关功能总结
1. 数据流向
在NAT网关中,数据流通过以下步骤进行处理:
图示颜色说明
- 红色箭头:表示加密数据的流向。
- 蓝色箭头:表示解密数据从
RoutineSequentialReceiver
流向wg0
接口。 - 绿色箭头:表示解密数据从
wg0
接口流向Local Network Stack
。
详细说明
- Remote Peer: 另一台服务器或设备发送加密数据到本地NAT网关。
- RoutineSequentialReceiver: 接收并解密数据,然后将其发送到本地虚拟网卡(
wg0
)。 - wg0: TUN接口,负责在本地虚拟网卡与本地网络堆栈之间传输数据。
- Local Network Stack: 本地应用程序和系统内核处理从
wg0
接收到的解密数据。 - RoutineReadFromTUN: 读取本地数据,将其加密并发送回Remote Peer。
2. NAT实现原理细节
NAT网关通过修改数据包中的IP地址(只修改了源 ip)来实现内部网络与外部网络的连接。
- IPv4 和 IPv6 处理
- IPv4 NAT: 修改IPv4包的源IP地址(入站)来实现。更新IP层和传输层(如TCP/UDP/ICMP)的校验和。
- IPv6 NAT: 类似于IPv4 NAT,需要处理更长的地址(128位)以及ICMPv6包的校验和。
- 分片与非分片处理
- 非分片包: 包含完整的IP和传输层数据。NAT处理时,修改IP地址和校验和,并更新传输层的校验和。
- 分片包: 分片包只要首片携带传输层。对于第一个分片,除了修改IP地址外,还需要更新传输层校验和。对于后续的分片,只需要更新IP层校验和(因为后续分片没有传输层头部)。
- 处理的协议
- TCP/UDP: 处理传输层的校验和更新。使用增量校验和算法更新传输层校验和,以降低计算开销。
- ICMP/ICMPv6: 处理网络层的校验和。ICMP/ICMPv6的校验和需要重新计算整个包的内容。
3. 校验和算法
详细的校验和计算方法和原理:
- IP层校验和计算: IPv4头部包含一个16位的校验和字段,用于检测数据包头部的传输错误。IPv6头部没有校验和字段,依赖于更高层的协议校验。
- 传输层校验和(TCP/UDP)计算: TCP和UDP都包含校验和字段,用于保护整个数据包(包括头部和数据)。校验和计算的范围包括伪头(伪头包括源IP地址、目标IP地址、协议号和TCP/UDP长度)、实际头部以及数据。
- ICMP/ICMPv6校验和计算: ICMP和ICMPv6的校验和是其头部和数据的16位校验和。不同于TCP和UDP,ICMP/ICMPv6的校验和不考虑IP地址的变化,因为ICMP的校验和只计算ICMP数据部分。
以下是Go语言实现的校验和计算函数:
// 计算数据包的校验和(通用算法)
func calculateChecksum(data []byte) uint16 {
var sum uint32
for i := 0; i < len(data)-1; i += 2 {
sum += uint32(data[i])<<8 | uint32(data[i+1])
}
if len(data)%2 == 1 {
sum += uint32(data[len(data)-1]) << 8
}
for (sum >> 16) > 0 {
sum = (sum >> 16) + (sum & 0xffff)
}
return ^uint16(sum)
}
// 增量更新校验和函数 (基于RFC 1624的增量校验和算法)
func (device *Device) csumReplace4(sum uint16, from, to uint32) uint16 {
// 将 sum 展开为 32 位并取反
csum := ^uint32(sum)
// 减去旧 IP 的贡献
csum -= (from >> 16) & 0xFFFF
csum -= from & 0xFFFF
// 加上新 IP 的贡献
csum += (to >> 16) & 0xFFFF
csum += to & 0xFFFF
// 折叠进位
csum = (csum & 0xFFFF) + (csum >> 16)
csum = (csum & 0xFFFF) + (csum >> 16)
return ^uint16(csum)
}
// 传输层校验和更新函数 (IPv4)
func (device *Device) updateIPv4TransportChecksum(packet []byte, protocol byte, oldIP, newIP uint32) {
ipHeaderLen := ipv4.HeaderLen
checksumOffset := 0
switch protocol {
case TCP:
checksumOffset = TCPoffsetChecksum
case UDP:
checksumOffset = UDPoffsetChecksum
default:
return
}
oldL4Checksum := binary.BigEndian.Uint16(packet[ipHeaderLen+checksumOffset : ipHeaderLen+checksumOffset+2])
newL4Checksum := device.csumReplace4(oldL4Checksum, oldIP, newIP)
binary.BigEndian.PutUint16(packet[ipHeaderLen+checksumOffset:ipHeaderLen+checksumOffset+2], newL4Checksum)
}
if protocol == ICMP {
device.updateICMPv4Checksum(packet) // 只处理ICMP数据本身
}
// ICMP校验和更新函数
func (device *Device) updateICMPv4Checksum(packet []byte) {
ipv4HeaderLen := ipv4.HeaderLen
icmpv4Offset := ipv4HeaderLen // ICMP偏移量紧随IPv4头部
// 重新计算整个ICMP数据包的校验和
packet[icmpv4Offset+ICMPoffsetChecksum] = 0
packet[icmpv4Offset+ICMPoffsetChecksum+1] = 0
icmpv4Checksum := device.calculateChecksum(packet[icmpv4Offset:])
binary.BigEndian.PutUint16(packet[icmpv4Offset+ICMPoffsetChecksum:icmpv4Offset+ICMPoffsetChecksum+2], icmpv4Checksum)
}
4. 校验和计算的重点
- IP层校验和计算
- 仅适用于IPv4。IPv4头部校验和计算是基于整个头部的16位字的和,并对结果取反。
csumReplace4
从当前校验和中减去旧地址的贡献并添加新地址的贡献来高效地更新校验和。增量更新校验和函数 (based on RFC 1624 增量checksum算法) https://www.rfc-editor.org/rfc/rfc1624
- 传输层校验和计算
- TCP/UDP: 校验和计算涉及伪头(包括源IP地址、目标IP地址、协议号和TCP/UDP长度)以及实际数据,通用算法可以实现,但无法处理分片包的checksum(分片非首片不携带L4信息,整个包重组后才能计算),但是基于RFC 1624的增量校验和算法可以避免该问题,用于更新因IP地址变化而导致的传输层校验和变化。
- ICMP/ICMPv6: 是基于其头部和数据部分的16位校验和。不同于TCP/UDP,不使用伪头。在处理NAT时,ICMP和ICMPv6的校验和计算仅基于ICMP/ICMPv6的数据部分,不考虑IP地址的变化。在NAT过程中,当IP地址变化时,传输层校验和的更新不影响ICMP校验和。
校验和计算算法解释
IP层和传输层校验和的区别与计算:
- IP层校验和 用于检测IP头部的传输错误,而不涉及数据部分。IPv4使用校验和,IPv6则没有IP头部校验和。
- 传输层校验和(TCP/UDP)保护整个数据包,包括伪头、实际头部和数据部分。它确保数据包从源到目的的完整性。
实现校验和更新的原因:
在处理NAT时,IP地址的改变会影响基于IP地址的校验和字段,如TCP/UDP的传输层校验和。为避免重新计算整个数据包的校验和,可以使用增量校验和算法(csumReplace4
),只更新地址变化的部分,大大降低了计算开销,提高了效率。
5. NAT网关的流程图和UML图
- NAT处理数据包的流程图