我是不是遇到了对称型 NAT

对于VoIP来说,NAT,特别是对称型NAT会产生很多问题。而WebRTC中就有解决这些问题的工具。

WebRTC可在web浏览器之间建立端对端的连接。为了做到这一点,WebRTC采用了ICE(交互式连接建立)这套技术。ICE允许某些执行NAT(网络地址转换)路由器所代表的客户端建立直接连接。(欲知详情,请参考WebRTC glossary entry。)其中最重要的是让客户端找到公共IP地址。为实现这一需求,客户端需向STUN服务器询问其IP地址。

NAT是连接我们本地个人网络和公共互联网的一个个盒子。NAT通过把我们使用的内部IP地址,转换为公共地址来实现连接。不同的NAT工作方式也不尽相同。这就使得WebRTC需要靠STUN和TURN来同时连接呼叫。有关这一点的详细背景,大家可以参考一些之前发布的相关文章,比如这篇这篇

在学习Tsahi Levent-Levi的WebRTC架构——NAT穿透一课时,我从下面这张ppt中(重新)理解了对称型NAT的含义。

如果你向两个不同的STUN服务器询问自己的公共IP地址,对称型NAT会返回两个相同的IP地址(理想情况),但实际上两个地址的端口不同。所以我们需要一个TURN服务器来解决这一问题。因为一般来说,对称型NAT不允许建立直接连接。

几年前,在Tokbox演讲时,我谈到了一些提高连接率的技术(详见演讲视频PPT)。Tsahi的PPT让我不禁思考,我们是否也能测试这样的对称型NAT场景,即每个STUN绑定时,都得到一个不同的返回端口呢?

经过一些测试,我得到了肯定的结论。是的,我们可以测试。

第一步是向两个STUN服务器询问我们的IP地址。我们需要将以下配置传送给服务器。

var pc = new RTCPeerConnection({iceServers: [

{urls: ‘stun:stun1.l.google.com:19302’},

{urls: ‘stun:stun2.l.google.com:19302’}

]})

然后,我们创建一条数据通道,使端对端连接只产生一个本地candidate。

pc.createDataChannel("webrtchacks")

之后,我们查看onicecandidate事件,并解析我们得出的candidate(需要用到我SDP模块的一个辅助函数)。如果candidate属于srflx类型,我们要注意两个端口——一个是NAT设备翻译后的端口,一个是翻译前的端口。

pc.onicecandidate = function(e) {

if (e.candidate && e.candidate.candidate.indexOf('srflx') !== -1) {

var cand = parseCandidate(e.candidate.candidate);

if (!candidates[cand.relatedPort]) candidates[cand.relatedPort] = [];

candidates[cand.relatedPort].push(cand.port);

} else if (!e.candidate) {

if (Object.keys(candidates).length === 1) {

var ports = candidates[Object.keys(candidates)[0]];

console.log(ports.length === 1 ? 'cool nat' : 'symmetric nat');

}

}

};

接下来,我们调用createOffer和setLocalDescription来开始收集:

pc.createOffer()

.then(offer => pc.setLocalDescription(offer))

在完成candidate收集后,我们就会得到一个没有设置event.candidate的icecandidate事件。针对我们得到的candidate,有3种选择:

  1. 如果我们只有一个candidate,浏览器不会再返回第二个STUN服务器的响应给我们,因为该服务器里包含了和第一个服务器一样端口。也就是,这里不是对称型的 NAT。

  2. 如果我们有两个相同relatedPort却不同端口的candidate,那我们就属于一个对称型NAT。

  3. 如果我们一个srflx candidate,就说明UDP被阻止了。这意味着我们需要一个TURN/TCP或TURN/TLS服务器(例如这种服务器)。

我们应该如何测试呢?iPhone的个人热点属于对称型NAT,这对我们帮助很大。点击此处,即可在fiddle中在线测试。

// parseCandidate from https://github.com/fippo/sdp

function parseCandidate(line) {

var parts;

// Parse both variants.

if (line.indexOf('a=candidate:') === 0) {

parts = line.substring(12).split(' ');

} else {

parts = line.substring(10).split(' ');

}

var candidate = {

foundation: parts[0],

component: parts[1],

protocol: parts[2].toLowerCase(),

priority: parseInt(parts[3], 10),

ip: parts[4],

port: parseInt(parts[5], 10),

// skip parts[6] == 'typ'

type: parts[7]

};

for (var i = 8; i < parts.length; i += 2) {

switch (parts[i]) {

case 'raddr':

candidate.relatedAddress = parts[i + 1];

break;

case 'rport':

candidate.relatedPort = parseInt(parts[i + 1], 10);

break;

case 'tcptype':

candidate.tcpType = parts[i + 1];

break;

default: // Unknown extensions are silently ignored.

break;

}

}

return candidate;

};

var candidates = {};

var pc = new RTCPeerConnection({iceServers: [

{urls: 'stun:stun1.l.google.com:19302'},

{urls: 'stun:stun2.l.google.com:19302'}

]});

pc.createDataChannel("foo");

pc.onicecandidate = function(e) {

if (e.candidate && e.candidate.candidate.indexOf('srflx') !== -1) {

var cand = parseCandidate(e.candidate.candidate);

if (!candidates[cand.relatedPort]) candidates[cand.relatedPort] = [];

candidates[cand.relatedPort].push(cand.port);

} else if (!e.candidate) {

if (Object.keys(candidates).length === 1) {

var ports = candidates[Object.keys(candidates)[0]];

console.log(ports.length === 1 ? 'normal nat' : 'symmetric nat');

}

}

};

pc.createOffer()

.then(offer => pc.setLocalDescription(offer))

那么,知晓自己是否遇到了对称型NAT有什么用吗?这不得而知。我们现在可以建立一个NAT类型检测器。我并不确定这有什么实际用途,毕竟 WebRTC的ICE机制是一种可以找到连接,且不需要你担心细节的实现。但谁知道呢。不说别的,这确实是一项有趣的知识,起码可以帮助你了解WebRTC中NAT穿透的奥秘。

文章地址:Am I behind a Symmetric NAT? - webrtcHacks

原文作者:Philipp Hancke

推荐阅读
相关专栏
开源技术
106 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。