WebRTC之P2P
分类专栏: WebRTC 文章标签: webrtc p2p turn nat stun
版权
文章目录
-
- SDP/STUN/TURN/ICE
-
- SDP
- STUN
-
- TURN
-
- TurnServer(TURN服务端)
- TurnPort(TURN客户端)
- ICE
- NAT类型
-
- 完全圆锥形NAT(Full cone NAT)
- 受限圆锥形NAT(Address-Restricted cone NAT)
- 端口受限圆锥形NAT(Port-Restricted cone NAT)
- 对称NAT(Symmetric NAT)
SDP/STUN/TURN/ICE
对这几种名称进行简单介绍如下:
- SDP是一种用于描述媒体信息的标准协议,例如分辨率、编码器、加密等
- Offer/Answer,我们要和对端交换的描述信息就称为Offer,对端发给我们的描述信息就称为Answer,不同客户端支持的编解码类型是不一样的,所以需要协商
- STUN是一种获取NAT公网IP,以及NAT类型的协议
- TURN是在STUN基础上增加转发功能的协议
- ICE就是把STUN和TURN的结合
SDP
以下SDP内容完全来自维基-Session Description Protocol,阅读原文获取更全面的信息。
SDP是用于描述流媒体通信参数的格式。IETF在1998年4月发布了原始规范作为拟议标,随后在2006年7月发布了修订的规范RFC4566。
SDP用于会话描述通告,会话邀请和参数协商等多媒体通信会话。SDP本身并不传递任何媒体,而是在端点之间用于协商媒体类型,格式和所有相关属性。属性和参数的集合通常称为会话配置文件。
SDP被设计为可扩展的,以支持新的媒体类型和格式。SDP最初是作为会话公告协议(SAP)的组成部分,但发现它与实时传输协议(RTP),实时流协议(RTSP),会话发起协议(SIP)和即使是用于描述多播会话的独立格式。
STUN
以下STUN内容完全来自维基-STUN,阅读原文获取更全面的信息。
STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信。该协议由RFC 5389定义。
一旦客户端得知了Internet端的UDP端口,通信就可以开始了。如果NAT是完全圆锥型的,那么双方中的任何一方都可以发起通信。如果NAT是受限圆锥型或端口受限圆锥型,双方必须一起开始传输。
STUN使用下列的算法(取自RFC 3489)来发现NAT gateways类型以及防火墙(firewalls)
一旦路经通过红色箱子的终点时,UDP的沟通是没有可能性的。一旦通过黄色或是绿色的箱子,就有连线的可能。
-
1.STUN客户端向STUN服务器发送请求,要求得到自身经NAT映射后的地址:
- a. 收不到服务器回复,则认为UDP被防火墙阻断,不能通信,网络类型:Blocked.
- b. 收到服务器回复,对比本地地址,如果相同,则认为无NAT设备,进入第2步,否则认为有NAT设备,进入3步.
-
2.(已确认无NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:
- a. 收不到服务器从其他IP地址的回复,认为包被前置防火墙阻断,网络类型:Symmetric UDP Firewall.
- b. 收到则认为客户端处在一个开放的网络上,网络类型:Opened.
-
3.(已确认存在NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:
- a. 收不到服务器从其他IP地址的回复,认为包被前置NAT设备阻断,进入第4步.
- b. 收到则认为NAT设备类型为Full Cone,即网络类型:Full Cone NAT.
-
4.STUN客户端向STUN服务器的另外一个IP地址发送请求,要求得到自身经NAT映射后的地址,并对比之:
- a. 地址不相同,则网络类型:Symmetric NAT.
- b. 相同则认为是Restricted NAT,进入第5步,进一步确认类型.
-
5.(已确认Restricted NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从相同IP的其他PORT向客户端回复包:
- a. 收不到服务器从其他PORT地址的回复,认为包被前置NAT设备阻断,网络类型:Port Restricted cone NAT.
- b. 收到则认为网络类型: Restricted cone NAT.
有了上面的理论以后,我们来看看WebRTC的代码。WebRTC实现了STUN的功能,包括了客户端和服务端以及NAT探测,它使用的是RFC 5389协议。
服务端实现
WebRTC的STUN实现stunserver.cc很简单,收到stun客户端的请求,然后把客户端的最外层地址返回给用户。
// 判断是不是stun格式,如果是拍判断消息类型,目前仅仅支持STUN_BINDING_REQUEST消息
void StunServer::OnPacket(
rtc::AsyncPacketSocket* socket, const char* buf, size_t size,
const rtc::SocketAddress& remote_addr,
const rtc::PacketTime& packet_time) {
// Parse the STUN message; eat any messages that fail to parse.
rtc::ByteBufferReader bbuf(buf, size);
StunMessage msg;
if (!msg.Read(&bbuf)) {
return;
}
// Send the message to the appropriate handler function.
switch (msg.type()) {
case STUN_BINDING_REQUEST:
OnBindingRequest(&msg, remote_addr);
break;
default:
SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
}
}
// 构建一个stun的response消息,并写入stun客户的外网地址
void StunServer::OnBindingRequest(
StunMessage* msg, const rtc::SocketAddress& remote_addr) {
StunMessage response;
GetStunBindReqponse(msg, remote_addr, &response);
SendResponse(response, remote_addr);
}
// 返回response数据给客户端
void StunServer::SendResponse(
const StunMessage& msg, const rtc::SocketAddress& addr) {
rtc::ByteBufferWriter buf;
msg.Write(&buf);
rtc::PacketOptions options;
if (socket_->SendTo(buf.Data(), buf.Length(), addr, options) < 0)
LOG_ERR(LS_ERROR) << "sendto";
}
- 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
客户端实现
客户端的实现简单来说就是构建一个标准的STUN消息请求并发送给不同的STUN服务器(或许存在多个不同的STUN服务器,我还不能明白存在多个STUN服务器的意义),并收集STUN服务器返回的自身的外网IP,并放到candidates中。虽然代码看着不少,因为代码要考虑健壮性。
CreateStunPorts -> StunPort::Create -> new StunPort -> new UDPPort -> UDPPort::Init -> UDPPort::OnLocalAddressReady -> StunRequestManager::SendDelayed -> UDPPort::OnSendPacket -> UDPPort::OnReadPacket -> StunBindingRequest::OnResponse -> StunRequestManager::CheckResponse -> UDPPort::OnStunBindingRequestSucceeded
// 构建一个StunPort,StunPort是对见Port的简单封装
void AllocationSequence::CreateStunPorts() {
...
StunPort* port = StunPort::Create(
session_->network_thread(), session_->socket_factory(), network_,
session_->allocator()->min_port(), session_->allocator()->max_port(),
session_->username(), session_->password(), config_->StunServers(),
session_->allocator()->origin());
...
}
// 初始化StunPort
static StunPort* Create(rtc::Thread* thread, rtc::PacketSocketFactory* factory, rtc::Network* network, ...) {
StunPort* port = new StunPort(thread, factory, network, min_port, max_port, username, password, servers, origin);
if (!port->Init()) { delete port; port = NULL; }
}
// StunPort是对UDPPort的子类
StunPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, rtc::Network* network, uint16_t min_port, uint16_t max_port, ...)
: UDPPort(thread, factory, network, min_port, max_port, username, password, origin, false) {
// UDPPort will set these to local udp, updating these to STUN.
set_type(STUN_PORT_TYPE);
set_server_addresses(servers);
}
// 创建一个UDP Socket,这个Socket也是WebRTC的封装,不细说,当Socket的状态发生变化的时候会通过Socket的`Signal`信号槽回调出来
bool UDPPort::Init() {
...
socket_ = socket_factory()->CreateUdpSocket(rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
socket_->SignalSentPacket.connect(this, &UDPPort::OnSentPacket);
socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket);
return true;
}
// Socket地址可用回调此函数,在这里会先收集local candiate,然后调用MaybePrepareStunCandidate获取本机的外网地址
void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket, const rtc::SocketAddress& address) {
rtc::SocketAddress addr = address;
MaybeSetDefaultLocalAddress(&addr);
AddAddress(addr, addr, rtc::SocketAddress(), UDP_PROTOCOL_NAME, "", "", LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST, 0, "", false);
MaybePrepareStunCandidate();
}
// 判断是否需要向STUN服务器请求本机外网地址或者判断是否完成了获取外网地址的请求
void UDPPort::MaybePrepareStunCandidate() {
if (!server_addresses_.empty()) {
SendStunBindingRequests();
} else {
MaybeSetPortCompleteOrError();
}
}
// 依次向不同的服务器请求外网地址
void UDPPort::SendStunBindingRequests() {
for (ServerAddresses::const_iterator it = server_addresses_.begin(); it != server_addresses_.end(); ++it) {
SendStunBindingRequest(*it);
}
}
// 如果STUN服务器地址可用,那么向此服务器发送一个binding请求
void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) {
if (stun_addr.IsUnresolvedIP()) {
ResolveStunAddress(stun_addr);
} else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
if (IsCompatibleAddress(stun_addr)) {
requests_.Send(new StunBindingRequest(this, stun_addr, rtc::TimeMillis()));
} else {
OnStunBindingOrResolveRequestFailed(stun_addr);
}
}
}
// 进一步完成StunRequest的设定,并把此数据发送给stun服务器
void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
request->set_manager(this);
RTC_DCHECK(requests_.find(request->id()) == requests_.end());
request->set_origin(origin_);
request->Construct();
requests_[request->id()] = request;
if (delay > 0) {
thread_->PostDelayed(RTC_FROM_HERE, delay, request, MSG_STUN_SEND, NULL);
} else {
thread_->Send(RTC_FROM_HERE, request, MSG_STUN_SEND, NULL);
}
}
// 通过SignalSendPacket发送数据,紧接着判断已经发送的次数,以及超时情况
void StunRequest::OnMessage(rtc::Message* pmsg) {
tstamp_ = rtc::TimeMillis();
rtc::ByteBufferWriter buf;
msg_->Write(&buf);
manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
OnSent();
manager_->thread_->PostDelayed(RTC_FROM_HERE, resend_delay(), this, MSG_STUN_SEND, NULL);
}
// 调用udp socket把数据发出去
void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
StunBindingRequest* sreq = static_cast(req);
rtc::PacketOptions options(DefaultDscpValue());
if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0)
PLOG(LERROR, socket_->GetError()) << "sendto";
}
// 如果是stun服务器返回的消息,则调用CheckResponse检查是啥消息
void UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket, const char* data, size_t size, const rtc::SocketAddress& remote_addr, const rtc::PacketTime& packet_time) {
if (server_addresses_.find(remote_addr) != server_addresses_.end()) {
requests_.CheckResponse(data, size);
return;
}
if (Connection* conn = GetConnection(remote_addr)) {
conn->OnReadPacket(data, size, packet_time);
} else {
Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
}
}
virtual void StunBindingRequest::OnResponse(StunMessage* response) override {
const StunAddressAttribute* addr_attr = response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
port_->OnStunBindingRequestSucceeded(server_addr_, addr);
if (WithinLifetime(rtc::TimeMillis())) {
port_->requests_.SendDelayed(new StunBindingRequest(port_, server_addr_, start_time_), port_->stun_keepalive_delay());
}
}
bool StunRequestManager::CheckResponse(StunMessage* msg) {
…
if (msg->type() == GetStunSuccessResponseType(request->type())) {
request->OnResponse(msg);
} else if (msg->type() == GetStunErrorResponseType(request->type())) {
request->OnErrorResponse(msg);
}
}
void UDPPort::OnStunBindingRequestSucceeded(const rtc::SocketAddress& stun_server_addr, const rtc::SocketAddress& stun_reflected_addr) {
…
std::ostringstream url;
AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address, UDP_PROTOCOL_NAME, “”, “”, STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false);
}
MaybeSetPortCompleteOrError();
}
- 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
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
NAT类型判断
通过刚刚的代码,我们已经拿到了Peer在NAT内外的ip地址了,那么此时我们就应该把这些candidates发送给对端。
//...
TURN
以下TURN内容完全来自维基-TURN,阅读原文获取更全面的信息。
TURN(全名Traversal Using Relay NAT),是一种数据传输协议(data-transfer protocol)。允许在TCP或UDP的连在线跨越NAT或防火墙。
TURN是一个client-server协议。TURN的NAT穿透方法与STUN类似,都是通过获取应用层中的公有地址达到NAT穿透。但实现TURN client的终端必须在通信开始前与TURN server进行交互,并要求TURN server产生"relay port",也就是relayed-transport-address。这时TURN server会创建peer,即远程端点(remote endpoints),开始进行中继(relay)的动作,TURN client利用relay port将数据发送至peer,再由peer转传到另一方的TURN client。
名词 |
说明 |
realm |
域名,例如boyaa.com |
SOFTWARE |
代理所使用的软件的文本描述,例如boyaa media, version 1.01 |
TurnServer(TURN服务端)
简单来说TURN服务要做的事情就是把Peer A的数据通过服务器转发给Peer B。这里我们要引入TURN协议RFC5766,协议存在的意义是为了标准化,要不只能自己玩了。协议规定了通信的格式以及他们交互流程。
- TurnServer的工作流程如下:
- TurnClient向TurnServer发送一个STUN_ALLOCATE_REQUEST请求,TurnServer会在服务端上生成一个对应的TurnServerAllocation,并返回TurnClient的外网地址和TurnServerAllocation的转发地址,TurnServerAllocation包含了一个UDP Socket,用于转发TurnClient的非STUN/TURN数据给Peer,也用于监听Peer发过来的数据
- TurnClient向TurnServer发送一个TURN_CHANNEL_BIND_REQUEST请求,会在TurnServerAllocation上生成一个Channel,此Channel保护了TurnClient的channel_id和Peer的IP Address
- TurnClient向TurnServer发送channel message时,TurnServerAllocation会找到对应的Peer地址,并通过TurnServerAllocation内部的UDP Socket转发给Peer
- Peer向TurnServer发送数据,TurnServerAllocation会通过Peer地址找到对应的TurnClient,并通过TurnClient的Socket发送给TurnClient
- TurnServer支持TCP/UDP协议的数据输入,但是内部都会通过UDP转发给Peer;同样的Peer只能通过UDP发送给TurnServer,然后TurnServer会通过TurnClient原本的协议转发给TurnClient
- TurnClient如果要同时发送数据给Peer A和Peer B,TurnClient需要向TurnServerAllocation请求两次TURN_CHANNEL_BIND_REQUEST,用不同的channel_id分别对应Peer A和Peer B的IP AddreTurnClient需要向TurnServer发送两次数据,一份用channel_id_for_peer_a发送给Peer A,一份用channel_id_for_peer_b发送给Peer B,也就是说TurnServer真的只是负责转发而已
- Channel和Indication的区别:发送Channel是TurnClient和Peer存在隐射关系,TurnClient可以和Peer可以相互发送数据;Indication是只有Peer发送数据给TurnClient。因为TurnClient发送的数据格式只有ChannelMessage一种。
- TurnServer存在一个比较严重的问题:每一个TurnClient都需要一个在TurnServer对应一个Relay Transport(UDP Socket)
int main(int argc, char **argv) {
rtc::SocketAddress int_addr;
if (!int_addr.FromString(argv[1])) {
return 1;
}
rtc::IPAddress ext_addr;
if (!IPFromString(argv[2], &ext_addr)) {
return 1;
}
rtc::Thread* main = rtc::Thread::Current();
rtc::AsyncUDPSocket* int_socket = rtc::AsyncUDPSocket::Create(main->socketserver(), int_addr);
if (!int_socket) {
return 1;
}
cricket::TurnServer server(main);
TurnFileAuth auth(argv[4]);
server.set_realm(argv[3]);
server.set_software(kSoftware);
server.set_auth_hook(&auth);
// 这是一个UDP Socket用于接收所有TurnClient发送过来的数据
server.AddInternalSocket(int_socket, cricket::PROTO_UDP);
// 这是一个UDP SocketFactory,每一个TurnServerAllocation都需要一个新的UDP Socket,用于转发数据给Peer和接收Peer发送过来的数据
server.SetExternalSocketFactory(new rtc::BasicPacketSocketFactory(), rtc::SocketAddress(ext_addr, 0));
main->Run();
return 0;
}
- 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
- 通信第一步:TURN Client需要先向TURN Server请求分配一个Allocate
void TurnServer::HandleAllocateRequest(TurnServerConnection* conn, const TurnMessage* msg, const std::string& key) {
TurnServerAllocation* alloc = CreateAllocation(conn, proto, key);
}
TurnServerAllocation* TurnServer::CreateAllocation(TurnServerConnection* conn, int proto, const std::string& key) {
rtc::AsyncPacketSocket* external_socket = (external_socket_factory_) ? external_socket_factory_->CreateUdpSocket(external_addr_, 0, 0) : NULL;
// The Allocation takes ownership of the socket.
TurnServerAllocation* allocation = new TurnServerAllocation(this, thread_, *conn, external_socket, key);
allocation->SignalDestroyed.connect(this, &TurnServer::OnAllocationDestroyed);
allocations_[*conn].reset(allocation);
return allocation;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 通信第二步:TurnClient向TurnServer发送一个TURN_CHANNEL_BIND_REQUEST请求
void TurnServerAllocation::HandleChannelBindRequest(const TurnMessage* msg) {
// Check mandatory attributes.
const StunUInt32Attribute* channel_attr = msg->GetUInt32(STUN_ATTR_CHANNEL_NUMBER);
const StunAddressAttribute* peer_attr = msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
// Check that channel id is valid.
int channel_id = channel_attr->value() >> 16;
Channel* channel1 = new Channel(thread_, channel_id, peer_attr->GetAddress());
channel1->SignalDestroyed.connect(this, &TurnServerAllocation::OnChannelDestroyed);
channels_.push_back(channel1);
// Channel binds also refresh permissions.
AddPermission(peer_attr->GetAddress().ipaddr());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 通信第三步:TurnClient和Peer进行数据传输
// TURN Client发送数据给Peer
void TurnServerAllocation::HandleChannelData(const char* data, size_t size) {
// Extract the channel number from the data.
uint16_t channel_id = rtc::GetBE16(data);
Channel* channel = FindChannel(channel_id);
SendExternal(data + TURN_CHANNEL_HEADER_SIZE, size - TURN_CHANNEL_HEADER_SIZE, channel->peer());
}
// Peer发送数据给TurnClient
void TurnServerAllocation::OnExternalPacket(rtc::AsyncPacketSocket* socket, const char* data, size_t size, const rtc::SocketAddress& addr, const rtc::PacketTime& packet_time) {
RTC_DCHECK(external_socket_.get() == socket);
Channel* channel = FindChannel(addr);
if (channel) {
// There is a channel bound to this address. Send as a channel message.
rtc::ByteBufferWriter buf;
buf.WriteUInt16(channel->id());
buf.WriteUInt16(static_cast(size));
buf.WriteBytes(data, size);
server_->Send(&conn_, buf);
} else if (!server_->enable_permission_checks_ || HasPermission(addr.ipaddr())) {
// No channel, but a permission exists. Send as a data indication.
TurnMessage msg;
msg.SetType(TURN_DATA_INDICATION);
msg.SetTransactionID(rtc::CreateRandomString(kStunTransactionIdLength));
msg.AddAttribute(rtc::MakeUnique(STUN_ATTR_XOR_PEER_ADDRESS, addr));
msg.AddAttribute(rtc::MakeUnique(STUN_ATTR_DATA, data, size));
server_->SendStun(&conn_, &msg);
} else {
LOG_J(LS_WARNING, this) << "Received external packet without permission, " << "peer=" << addr;
}
}
- 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
TurnPort(TURN客户端)
ICE
以下ICE内容完全来自维基-交互式连接创建,阅读原文获取更全面的信息。
交互式连接创建(Interactive Connectivity Establishment),一种综合性的NAT穿越的技术。
交互式连接创建是由IETF的MMUSIC工作组开发出来的一种framework,可集成各种NAT穿透技术,如STUN、TURN(Traversal Using Relay NAT,中继NAT实现的穿透)、RSIP(Realm Specific IP,特定域IP)等。该framework可以让SIP的客户端利用各种NAT穿透方式打穿远程的防火墙。
NAT类型
以下NAT内容完全来自维基-网络地址转换,阅读原文获取更全面的信息。
网络地址转换(英语:Network Address Translation,缩写:NAT;又称网络掩蔽、IP掩蔽)在计算机网络中是一种在IP数据包通过路由器或防火墙时重写来源IP地址或目的IP地址的技术。这种技术被普遍使用在有多台主机但只通过一个公有IP地址访问互联网的私有网络中。它是一个方便且得到了广泛应用的技术。当然,NAT也让主机之间的通信变得复杂,导致了通信效率的降低。
完全圆锥形NAT(Full cone NAT)
- 一旦一个内部地址(iAddr:port)映射到外部地址(eAddr:port),所有发自iAddr:port的包都经由eAddr:port向外发送。任意外部主机都能通过给eAddr:port发包到达iAddr:port(注:port不需要一样)
受限圆锥形NAT(Address-Restricted cone NAT)
- 内部客户端必须首先发送数据包到对方(IP=X.X.X.X),然后才能接收来自X.X.X.X的数据包。在限制方面,唯一的要求是数据包是来自X.X.X.X。
- 内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。外部主机(hostAddr:any)能通过给eAddr:port2发包到达iAddr:port1。(注:any指外部主机源端口不受限制,但是目的端口必须是port2。只有外部主机数据包的目的IP 为 内部客户端的所映射的外部ip,且目的端口为port2时数据包才被放行。
端口受限圆锥形NAT(Port-Restricted cone NAT)
类似受限制锥形NAT(Restricted cone NAT),但是还有端口限制。
- 一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。
- 在受限圆锥型NAT基础上增加了外部主机源端口必须是固定的。
对称NAT(Symmetric NAT)
- 每一个来自相同内部IP与端口,到一个特定目的地地址和端口的请求,都映射到一个独特的外部IP地址和端口。同一内部IP与端口发到不同的目的地和端口的信息包,都使用不同的映射
- 只有曾经收到过内部主机数据的外部主机,才能够把数据包发回
WebRTC -- P2P及NAT穿越技术介绍
while(1) { smile(); }
11-15 8万+
P2P是一个“点对点传输技术”,也就是2台计算机之间不需要第三台机器作为服务端就能实现数据的传输。每台计算机即是客户端又是服务端。如果每台计算机都有自己的独立的公网IP,那么P2P技术就比较好实现了,但是现实中由于NAT的存在,使得P2P技术最大的难点就在于穿越NAT的限制,俗称“打洞”。
二、 NAT(Network Address Transl…