NAT 种类
NAT有4个类型,它们分别是:NAT1、NAT2、NAT3、NAT4。从 NAT1 至 NAT4 限制越来越多。
NAT1 完全圆锥形NAT(Full Cone NAT)
完全圆锥型NAT把一个来自内部IP地址和端口的所有请求,始终映射到相同的外网IP地址和端口;同时,任意外部主机向该映射的外网IP地址和端口发送报文,都可以实现和内网主机进行通信,就像一个向外开口的圆锥形一样,故得名。
这种模式很宽松,限制小,只要内网主机的IP地址和端口与公网IP地址和端口建立映射关系,所有互联网上的主机都可以访问该NAT之后的内网主机。
- 映射特性:内网地址端口唯一映射为固定公网地址端口
- 访问规则:允许任意外部主机通过该映射地址发起通信
- 网络宽容度:高
NAT2 地址限制式圆锥形NAT(Address Restricted Cone NAT)
地址限制式圆锥形NAT同样把一个来自内部IP地址和端口的所有请求,始终映射到相同的外网IP地址和端口;与完全圆锥型NAT不同的是,当内网主机向某公网主机发送过报文后,只有该公网主机才能向内网主机发送报文,故得名。相比NAT1,NAT2 增加了地址限制,也就是IP受限,而端口不受限。
- 映射特性:保持固定地址端口映射
- 访问规则:仅允许曾接收过本端流量的外部IP发起通信,需要基于源IP地址进行访问控制
- 网络宽容度:中
NAT3 端口限制式圆锥形NAT(Port Restricted Cone NAT)
端口限制式圆锥形NAT更加严格,在上述条件下,只有该公网主机该端口才能向内网主机发送报文,故得名。相比NAT2,NAT3 又增加了端口限制,也就是说IP、端口都受限。
- 映射特性:维持固定地址端口绑定
- 访问规则:仅允许特定IP地址和端口的组合进行通信
- 网络宽容度:中
NAT4 对称式NAT(Symmetric NAT)
对称式NAT把内网IP和端口到相同目的地址和端口的所有请求,都映射到同一个公网地址和端口;同一个内网主机,用相同的内网IP和端口向另外一个目的地址发送报文,则会用不同的映射(比如映射到不同的端口)。
- 动态映射:针对不同目标生成独立映射关系
- 会话特性:每个外部连接使用独立端口映射,需要端口预测
- 网络宽容度:低
NAT打洞
所谓NAT打洞就是在通信双方都处于NAT后面时候,使用某种方式使得通信双方可以正常端到端通信。
基于TCP的NAT打洞基本放弃,TCP协议本是有状态的,NAT设备一般只允许outbound发起syn,所以 NAT 打洞技术基本都是基于UDP协议的。
STUN
STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间建立UDP通信。
主要作用于获取公网地址以及NAT类型
打洞策略
基础型穿透(NAT1/NAT2/NAT3):利用STUN获取对端公网地址后直接建立连接
对称型应对(NAT4):采用端口预测算法(如±2规则)尝试建立连接
混合场景处理:不同类型NAT组合需要采用差异化连接策略
NAT1/NAT2/NAT3 打洞
-
NAT1: 对回包没有安全限制,所以其NAT会话中只包含转换后的源IP和源端口,且转换后端口仅和内网源IP+源端口有关;
-
NAT2: 需要注意回包仅限制IP,所以其NAT会话中只包含转换后的源IP、源端口、目的IP,且转换后端口仅和内网源IP+源端口有关;
-
NAT3: 需要注意回包仅限制IP和端口,所以其NAT会话中只包含转换后的源IP、源端口、目的IP、目的端口,且转换后端口仅和内网源IP+源端口有关;
通过 STUN 获取地址端口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
use std::sync::Arc;
use stun::{
agent::TransactionId,
client::ClientBuilder,
message::{BINDING_REQUEST, Getter, Message},
xoraddr::XorMappedAddress,
};
use tokio::net::UdpSocket;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await?);
socket.connect("stun.miwifi.com:3478").await?;
let mut client = ClientBuilder::new().with_conn(socket).build()?;
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let mut msg = Message::new();
msg.build(&[Box::<TransactionId>::default(), Box::new(BINDING_REQUEST)])?;
client.send(&msg, Some(Arc::new(tx))).await?;
let event = rx.recv().await.ok_or("rx recv error")?;
let msg = event.event_body?;
let mut xor_addr = XorMappedAddress::default();
xor_addr.get_from(&msg)?;
println!("{}:{}", xor_addr.ip, xor_addr.port);
Ok(())
}
|
连接到对端:
1
|
socket.connect(peer_addr).await?;
|
发送以及读取消息:
1
2
|
socket.send(msg)
socket.recv_from(buf)
|
NAT4 打洞
- NAT4: 需要注意回包仅限制IP和端口,所以其NAT会话中只包含转换后的源IP、源端口、目的IP、目的端口,转换后端口仅和上述均有关;
例如如果是 NAT1 与 NAT4 打洞,得到NAT4方的地址后,需要预测端口,例如有一种很简单的规则是,使用上一次使用的端口号+2或-2。
如果是 NAT4对NAT4 则双方都需要预测,成功率极低。
中继服务
中继一般用于交换客户端之间的信令,一般就是公网地址+NAT类型以及某些附带信息,可以服务器直接获取也可以通过 STUN 获取,并且在打洞失败的情况下提供中转。
交换地址信令的简单例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket = UdpSocket::bind("0.0.0.0:7899").await?;
let clients = Arc::new(Mutex::new(Vec::new()));
let mut buf = [0u8; 1024];
loop {
let (len, addr) = socket.recv_from(&mut buf).await?;
println!("registr from: {}", addr);
let data = String::from_utf8_lossy(&buf[..len]);
let peer_addr = SocketAddr::from_str(&data)?;
let mut clients = clients.lock().unwrap();
clients.push((addr, peer_addr));
if clients.len() == 2 {
let (addr1, peer1) = clients[0];
let (addr2, peer2) = clients[1];
tokio::time::sleep(Duration::from_secs(2)).await;
socket.send_to(&peer2.to_string().as_bytes(), addr1).await?;
socket.send_to(&peer1.to_string().as_bytes(), addr2).await?;
println!("{addr1}={peer1} <-------> {addr2}={peer2}");
clients.clear();
}
}
}
|
NAT1-NAT1 的 Demo
双方需要手动输入对方地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
use std::{
net::SocketAddr,
sync::Arc,
time::{Duration, Instant},
};
use tokio::{
io::{self, AsyncBufReadExt, BufReader},
net::UdpSocket,
sync::Mutex,
};
struct Peer {
addr: SocketAddr,
last_heartbeat: Instant,
}
use stun::{
agent::TransactionId,
client::ClientBuilder,
message::{BINDING_REQUEST, Getter, Message},
xoraddr::XorMappedAddress,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await?);
socket.connect("stun.miwifi.com:3478").await?;
let mut client = ClientBuilder::new().with_conn(socket.clone()).build()?;
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let mut msg = Message::new();
msg.build(&[Box::<TransactionId>::default(), Box::new(BINDING_REQUEST)])?;
client.send(&msg, Some(Arc::new(tx))).await?;
let event = rx.recv().await.ok_or("rx recv error")?;
let msg = event.event_body?;
let mut xor_addr = XorMappedAddress::default();
xor_addr.get_from(&msg)?;
println!("本地公网地址: {xor_addr}");
println!("请输入对方公网地址 (格式: IP:Port):");
let mut peer_addr_str = String::new();
BufReader::new(io::stdin())
.read_line(&mut peer_addr_str)
.await?;
let peer_addr: SocketAddr = peer_addr_str.trim().parse()?;
let peer = Arc::new(Mutex::new(Peer {
addr: peer_addr,
last_heartbeat: Instant::now(),
}));
socket.connect(peer_addr).await?;
let socket = Arc::new(socket);
let socket_heartbeat = socket.clone();
let peer_heartbeat = peer.clone();
let socket_receive = socket.clone();
let peer_receive = peer.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(10)).await;
let addr = peer_heartbeat.lock().await.addr;
if let Err(e) = socket_heartbeat.send(b"PING").await {
eprintln!("心跳发送失败: {}", e);
} else {
println!("心跳已发送至 {}", addr);
}
}
});
tokio::spawn(async move {
let mut buf = [0u8; 1024];
loop {
match socket_receive.recv_from(&mut buf).await {
Ok((size, src)) => {
let msg = String::from_utf8_lossy(&buf[..size]);
println!("收到来自 {} 的消息: {}", src, msg);
if msg == "PING" {
let mut peer = peer_receive.lock().await;
if peer.addr == src {
peer.last_heartbeat = Instant::now();
}
}
}
Err(e) => eprintln!("接收错误: {}", e),
}
}
});
println!("P2P 连接已启动,输入消息发送(输入 'exit' 退出):");
let stdin = BufReader::new(io::stdin());
let mut lines = stdin.lines();
while let Some(line) = lines.next_line().await? {
let msg = line.trim();
if msg == "exit" {
break;
}
if let Err(e) = socket.send(msg.as_bytes()).await {
eprintln!("消息发送失败: {}", e);
} else {
println!("消息已发送: {}", msg);
}
}
Ok(())
}
|