"构成我们学习最大障碍的是已知的东西,不是未知的东西" ------现代医学奠基人贝尔纳
WebRTC 交互的流程大致如下:
图片来源网络:https://www.jianshu.com/p/a7e7cb4d6d64
1、进入房间;
2、获取媒体,交换SDP;
3、通过turnserver获取本机候选地址,交换candidate;
4、ICE进行候选地址进行连接,连通了,则可以进行音视频通话;
这次项目实施的环境是一个网络等级相对要求高的网络,客户要求Android手机在安全网络外头,PC客户端运行在安全网络里头,大概的拓扑结构图如下:
代理前置机 代理后置机 WebRTC服务器 TURN服务器
客户端(192段) 192.168.1.40 20.80.4.131 20.80.4.133 (8099) 20.80.4.133 (3478)
52652 3478 52652 3478
30008 30008
Android手机通过VPN接入到代理前置机网络,访问安全网络20.80.4.*的服务器,需要通过代理前置开反向代理才能进入,并且代理设备之间还有隔离交换设备;
首先在代理前置机上开了WebRTC服务器8099和Turnserver 3478 的代理端口
20.80.4.133/8099 - 192.168.1.40/8099 tcp
20.80.4.133/3478 - 192.168.1.40/3478 udp
20.80.4.133/30008-30208 - 192.168.1.40/30008-30208 udp 端口池
Android客户端配置的服务器地址信息统一调整为代理的ip和端口,信令很顺利的就调通了,媒体预知肯定是不通,需要修改几个地方:
1、Android端candidate收集的本机地址中,在开启stun配置后,能获取到20.80.4.133的ip和端口,并将candidate发送给服务器;
"candidate": {
"candidate": "0:0:candidate:4020260996 1 udp 2122260223 10.172.25.131 52652 typ host generation 0 ufrag 7CB0 network-id 3 network-cost 50: ",
"sdpMid": "0",
"sdpMLineIndex": 0
}
2、Android端收到服务器的candidate地址信息,需要将20.80.4.133地址修改为代理前置机的地址,这是因为所有20.80.4.133其实已经被代理前置机代理了,但服务器是没法感知;
经过上面的修改,以为ICE就能通了,其实还是不通,通过抓包分析,原来问题出在STUN打的洞上,客户端可以通过代理机的端口发送数据包到服务器,但服务器通过客户端的candidate地址和端口发送ice请求包,却出现icmp不可达的错误!原来,这个环境的代理机制是我们极少碰到的对称型NAT!我们说对于对称型NAT,是无法通过预先打洞的端口进行数据互通的。这种网络设备,对每个外部主机或端口的会话都会映射为不同的端口(洞)。只有来自相同的内部地址(IP:PORT)并且发送到相同外部地址(X:x)的请求,在NAT上才映射为相同的外网端口,即相同的映射。
打洞机制失效,怎么破?
修改思路:
1、所有数据包都经过TURN服务器转发?这个思路可行,但在这种网络条件下,如何实施貌似有些问题不明白,比方turnserver开的转发端口就需要对外做代理,服务器可能也需要开启turnserver,暂时放弃这个思路;
2、这种网络环境下,去掉STUN服务器,不需要stun做地址探测了,应用对网络环境是清晰的,并且需要去掉ICE的候选地址配对的相关流程,主要是ICE地址配置过程中也是STUN协议交互的过程:
客户端将本机的candidate发送给服务器时,服务器的地址配置信息为:
[20.80.4.133]:30008 --> [20.80.4.131]:52652
但经过了客户端和服务器的stun试探性连接后,服务器发现,客户端给服务器返回的公网地址是:192.168.1.40:30008,
出现了新的配对:
[192.168.1.40]:30008 --> [20.80.4.131]:52652
并且服务器给出了配对失败的错误,其实互联的Socket都是正常的话,ice的交互显得有些多此一举了,所以果断修改libnice的ice交互流程,去掉了ICE的地址配对完成后服务器的地址匹配判断逻辑,libnice的ice服务器这么修改:忽略客户端返回的stun response 中的服务器的公网地址;
代码语言:javascript复制static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *agent, NiceStream *stream, NiceComponent *component, CandidateCheckPair *p, NiceSocket *sockptr, struct sockaddr *mapped_sockaddr, NiceCandidate *local_candidate, NiceCandidate *remote_candidate)
{
CandidateCheckPair *new_pair = NULL;
NiceAddress mapped;
GSList *i, *j;
NiceCandidate *local_cand = NULL;
nice_address_set_from_sockaddr (&mapped, mapped_sockaddr);
for (j = component->local_candidates; j; j = j->next) {
NiceCandidate *cand = j->data;
//忽略客户端返回的服务器地址,直接返回成功
//if (nice_address_equal (&mapped, &cand->addr)) {
local_cand = cand;
/* We always need to select the peer-reflexive Candidate Pair in the case
* of a TCP-ACTIVE local candidate, so we find it even if an incoming
* check matched an existing pair because it could be the original
* ACTIVE-PASSIVE candidate pair which was retriggered */
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *pair = i->data;
if (pair->local == cand && remote_candidate == pair->remote) {
new_pair = pair;
break;
}
}
break;
//}
}
}
经过上面的修改,webrtc的音视频通话功能正常!