浅谈WebRTC TURN的实现以及优化方案

1,多层命名空间

按照rfc建议,turn以5元组标记每个client session,单个node为一个realm,每个node在49152 – 65535之间为client分配port,所以在这种情况下,每个node只能分配16383个port。

Node/Realm(Local)/Port(49152 - 65535) 

在大型视频会试的场景下,每个client都需要为当前room内的所有peer分配port,这时候很容易出现端口号耗尽的情况,归根结底我觉得是因为rfc设计之初没有考虑过turn大型集群的问题,这时候引入多层命名空间划分就非常重要,将每个会话绑定一个group id,如果group id为U32,单个realm下面将可以分配U32::MAX * 16383个port。

Node/Realm(Local)/Group(Number)/Port(49152 - 65535) 

2,内部地址映射表

在turn协议中,client发送AllocateRequest要求turn server分配端口,目前部分turn server(如janus)实现都为直接分配真实udp port,这种方式的优点就是实现简单,但是带来的问题就是,主机上会存在大量开放的udp port,这对安全性和系统运维都是恶梦,其实这里有另一种解决方案,只分配单个端口就可以正确处理所有会话的payload reflect和relay,turn server对外只开放一个真实udp port,每个会话的allocate port存储在server内部的port alloc table(HashMap)上,映射关系可以通过后续CreatePermissionRequest来记录peer session之间的绑定关系,这样不用消耗物理机的端口,单个物理机可以承载多个服务器实例:

base: HashMap<SocketAddr, Node>
peers: HashMap<Group, (Port, Port)>

3,多线程优化

现代计算机系统普遍采用多核心架构,网络编程也广泛使用多线程处理net handle来提高性能和系统利用率,但是udp不同于tcp,因为udp使用syscall来保证多线程调用的安全性,通过atomic来控制竞争读写的问题,udp handle可以在多个线程中没有负担得无锁读写,不过,同时也因为syscall的存在,多线程读写可能并不会提高udp的吞吐效率,多线程处理udp handle可能也不会带来显著的性能提升,不过因为实际业务需要处理udp payload,所以我的意见是使用cpu core number创建线程池,过多线程反倒会因为线程切换和唤醒带来更严重的负担。

4,跨网段无缝切换

现在移动端发展日新月异,移动设备在整个互联网中比重越来越大,网络环境也越来越严峻,移动端经常存在跨网段,丢失网络连接等情况,丢包和IP地址变更也是家常便饭,在这种情况下,当切换网段的时候,因为IP地址变更,需要客户端重新注册并分配会话端口,这在实时音视频通话中是无法接受的,严重的更可能导致通话中断,用户体验非常差,怎么在频繁切换网络环境的情况下保持流畅的网络连接变得举足轻重。

当跨网发生时,client socketaddr更变,因为turn依靠5元组记录会话信息,新的网络包会被turn server认为是未注册的会话包而拒绝掉,这时候就需要更改rfc中的5元组定义,unique id应该由socketaddr切换为与网络地址无关的其他信息,比如以client session authorize的username做为unique id,当跨网发生时,username是不会变更的,可以正常保持对等映射关系。

不过这种实现方式存在一定安全性问题,当会话以新socketaddr地址来发送数据包时,通过变更内部注册表地址信息来刷新成新的地址,这个问题的解决方式也很简单,只不过需要变更客户端实现,客户端在跨网的情况下,下个数据包附带username和message integrity发送到服务器,服务器验证会话信息的有效性,验证为同一用户之后再刷新内部地址表,防止中间人伪造信息刷新掉正常对等会话。

Client                                                        TURN Server
192.168.1.1:8080  ----------------------------------------->  Handler Payload
192.168.3.1:8080(Change)  --{Username, MessageIntegrity}--->  Assert MessageIntegrity
192.168.3.1:8080  ----------------------------------------->  Handler Payload

小结

作者在自己实现TURN服务器的时候发现上述几点问题以及改进方案,如果某些部分存在问题,也请指出问题一起讨论,最后欢迎star我的项目