P2P技术的发展,产生了各种网络共享下载的应用,如Napster、Gnutella;音视频编码算法的进步、硬件性能的提升以及移动互联网的发展,在我们身边诞生出形形色色的网络应用,如网络电话、网络会议、网络直播等,同时也带来了各种商业上的成功。
- Skype 2003年诞生的一款即时通信应用软件,可通过互联网进行语音和视频通话,2011年微软85亿美元收购。
- FaceTime 2010年苹果公司的一种视频通话应用软件,采用多种开放协议,ACC与H.264的音视频编码。
- WebRTC 2011年Google发起的开源实时通讯解决方案,支持浏览器集成。
作为互联网从业的研发人员,我们有必要了解一下,这一切是怎么发生的,其内部是如何实现的。
网络
因为互联网的快速发展,联网设备数量远超设计者的最初设想,整个IPv4的地址范围不够用,因此引入了NAT技术。NAT(Net Address Translate)网络地址转换,简单来说就是在路由层记录私有网络和公有网络的IP映射关系,并进行IP数据包的修改,实现了公网IP复用的技术。
根据路由器对私网IP端口到公网IP端口的映射方式,RFC3489中将NAT分为四种类型
- Full Cone NAT(全锥型),同一内部IP端口发出的请求,被映射到相同的外部IP端口;任何外部主机通过向映射的地址发送报文,实现与内部主机通信。
- Restricted Cone NAT(受限锥型),发出同上;当且仅当内部主机之前向公网主机发送过报文,公网主机才能与其通信。
- Port Restricted Cone NAT(端口受限锥型)发出同上;限制条件包括端口。
- Symmetric NAT(对称型)所有内部IP端口向特定目的IP端口发出的请求,都被映射成同一个IP和端口。只有接收到数据的公网主机才能反向与内部主机通信。
类型检测
要检测当前主机所在的NAT类型,需要在云端部署一个服务,包含两个IP和端口
- 客户端向服务器发送请求,服务器将观察到的地址响应给客户端
- 如果响应中的地址与发送地址一致,说明客户端在公网,没有NAT。
- 客户端向服务端第二个IP地址再发送一个请求
- 如果两次响应中的公网地址不一致,说明是对称型
- 客户端向服务器第一个IP地址发送请求,服务端用另一个IP地址返回
- 如果可以收到,说明是全锥型
- 客户端向服务器再发送一个请求,服务器用同一IP的第二个端口响应
- 如果可以收到,说明是受限锥型;否则为端口受限锥型。
NAT穿透
NAT技术解决了IPv4不够用的问题,同时也导致TCP/IP通信困难,外部主机无法主动与内部主机通信,所有服务不得不采用C/S架构,导致高额的带宽成本。为了节约网络带宽成本,将客户端也作为资源提供方减少对服务的访问,因此产生了P2P技术。即Peer-to-Peer,点对点技术,其中最关键的技术之一,就是NAT设备穿透,俗称打洞。
反向链接
通信双方只有一个节点位于NAT中
- A请求B时,可以直接发起
- B请求A时,通过S转发给A一个连接请求,A再发起请求
简单场景
双方位于同一个NAT设备之后
- A与B分别与S建立连接,S记录下双方的内网信息,以及自己观察到的公网信息;
- A向S发起连接B的请求;S分别向双方发送对方的公网和内网地址;
- A和B收到信息后,同时向对方发起连接;推荐优先使用内网地址,直接通过交换机通信,速度快。也可以使用公网地址,需要网关支持Hairpin技术。
普遍场景
双方位于不同的NAT设备之后
- A与B分别与S建立连接,S记录下双方的内网信息,以及自己观察到的公网信息;
- A向S发起连接B的请求;S分别向双方发送对方的公网和内网地址;
- A和B的同时使用对方的公网地址发送数据包
双方为锥型
- A向B发送打洞包建立映射
- B向A发送打洞包建立映射
- 打洞成功
对称型和普通锥形(全锥型、受限锥型)
- A使用PA2向B发送打洞包,建立映射
- B使用PB1向PA1发送打洞包,建立映射
- B通过PB1收到A的打洞包,根据对方的NAT类型,后续使用PA2与A连接
- 打洞成功
对称型和端口受限型
- 流程同上
- 由于端口受限,B无法在PB1上接收PA2发来的打洞包
- 打洞失败
双方都是对称型
- 场景更严格,原理同上,无法打洞
1. Full Cone
2. Restricted Cone
3. Port Restricted Cone
4. Symmetric
若两端的NAT类型序号之和<=6,可以穿透;否则不可穿透。
典型场景
双方位于多层NAT设备之后,上层设备由网络提供商(ISP)提供,下层是家用路由器。
这里,只有服务器S和NAT C拥有真正的公网地址,而NAT A和B的公网地址实际为ISP设定的“伪公网”地址。由于S无法观察到这些“伪公网”地址,所以A和B无法通过NAT A直接与NAT B连接,即使通过特殊手段可以拿到也不建议使用,因为极易造成地址域重复冲突。A和B只能通过NAT C进行打洞转发。
STUN服务
- RFC3489 - Simple Traversal of UDP Through NAT
- RFC5389 / RFC8489 / RFC5780 - Session Traversal Utilities for NAT
上述NAT穿透的S就是STUN服务,它允许客户端发现与公网之间的NAT类型,以及NAT给其分配的公网地址和端口,默认端口为3478。
测试一下
$ stunclient stun1.l.google.com
Binding test: success
Local address: 10.4.39.11:59092
Mapped address: 139.177.225.224:59092
TURN服务
RFC5766 - Traversal Using Relays around NAT:STUN的中继扩展
通过STUN服务,A和B可以发现对方,但因为NAT类型或防火墙的原因不一定能够连接成功,这时就需要引入TURN服务。在云端假设一台中继服务,它负责双方数据的转发。当他们进入到同一个房间之后,TURN就会对房间里的所有人进行转发。
- 反射地址 Server-Reflexive Transport Address,即NAT分配的公网地址
- 中继地址 Relayed Transport Address,即TURN服务为客户端分配的代理地址
分配事务
客户端向中继服务发送一个Allocate请求,服务器为客户端分配一个公网的中继地址。 客户端定时向服务发送Refresh请求,用于保活。
TURN TURN Peer Peer
client server A B
|-- Allocate request --------------->| | |
| | | |
|<--------------- Allocate failure --| | |
| (401 Unauthorized) | | |
| | | |
|-- Allocate request --------------->| | |
| | | |
|<---------- Allocate success resp --| | |
| (192.0.2.15:50000) | | |
// // // //
| | | |
|-- Refresh request ---------------->| | |
| | | |
|<----------- Refresh success resp --| | |
| | | |
发送数据
开启中继地址后,客户端首先完成鉴权,检查是否有发送权限,之后就可以发送数据了。 客户端使用Send信令发送数据到服务端,服务端接收解包后,将内容数据转发给对端。Send数据包包含
- XOR-PEER-ADDRESS 对端地址(对方的公网地址)
- DATA 内容数据
为了避免攻击,使用Send/Data发送数据前需要完成许可验证,否则数据包将被丢弃
TURN TURN Peer Peer
client server A B
| | | |
|-- CreatePermission req (Peer A) -->| | |
|<-- CreatePermission success resp --| | |
| | | |
|--- Send ind (Peer A)-------------->| | |
| |=== data ===>| |
| | | |
| |<== data ====| |
|<-------------- Data ind (Peer A) --| | |
| | | |
| | | |
|--- Send ind (Peer B)-------------->| | |
| | dropped | |
| | | |
| |<== data ==================|
| dropped | | |
| | | |
当传输数据量非常大时(比如流媒体)每个数据包都包含30多个字节头对带宽影响很大。这是可以通过ChannelBind命令创建一个Channel,之后使用一个仅包含Channel Id的4字节头部。信道号可用范围为0x4000-0x7FFF,一共可以有16838个。
TURN TURN Peer Peer
client server A B
| | | |
|-- ChannelBind req ---------------->| | |
| (Peer A to 0x4001) | | |
| | | |
|<---------- ChannelBind succ resp --| | |
| | | |
|-- [0x4001] data ------------------>| | |
| |=== data ===>| |
| | | |
| |<== data ====| |
|<------------------ [0x4001] data --| | |
| | | |
|--- Send ind (Peer A)-------------->| | |
| |=== data ===>| |
| | | |
| |<== data ====| |
|<------------------ [0x4001] data --| | |
| | | |
ICE框架
Interactive Connectivity Establishment 互动式连接建立,它不是一种协议,而是一个框架,整合了STUN和TURN,使各种穿透技术实现统一。通过收集所有通路,将所有的连接可能性都罗列出来,根据优先级算法排序后找到一条最优路径。
- 通过STUN绑定,客户端拿到它的地址信息
- 通过TURN服务Allocate一个中继地址,用于接收数据
- 发起方通过信令主机将自己的媒体、网络等基本信息使用SDP(会话描述协议)发送给被调用方
- 被调用方接收到连接请求后回应一个Answer请求,完成数据交换
- 通过ICE框架,首先尝试P2P连接,一个端口或者某个网卡不通时,选其他的。如果都通,选择性能最好的。
- 如果P2P不通则使用TURN中转,并且支持多节点选择,若上海的节点不通,就用东京的节点。
ICE Candidate定义一个可用地址,包括协议、IP、端口和类型,双方各取一个Candidate形成通路。类型有
- 主机候选者 Host Candidate 本地主机的IP和端口
- 反射候选者 Reflexive Candidate 通过向STUN服务请求NAT映射之后的公网IP地址和端口(非伪公网)
- 中继候选者 Relay Candidate 通过向TURN服务发送Allocate请求,分配的中继IP地址和端口
可以通过 Trickle ICE测试
SDP描述
Session Description Protocol 会话描述协议,不是传输协议,只是一种格式定义;用于通信双方交换信息,包括会话描述、时间信息、多媒体信息等,如:
v=0 // 版本
o=jdoe 2890844526 2890842807 IN IP4 203.0.113.141 // Owner标识
s= // 会话名
c=IN IP4 192.0.2.3 // 连接信息
t=0 0 // 信息信息
a=ice-options:ice2 // 附加属性
a=ice-pacing:50
a=ice-pwd:asd88fgpdd777uzjYhagZg
a=ice-ufrag:8hhY
m=audio 45664 RTP/AVP 0 // 多媒体信息
b=RS:0 // 带宽信息
b=RR:0
a=rtpmap:0 PCMU/8000 // 编码方式
a=candidate:1 1 UDP 2130706431 203.0.113.141 8998 typ host
a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr
203.0.113.141 rport 8998
其中最后两条提供了Candidate的候选通路
- 第一个是UDP类型,类型是host
- 第二个是UDP类型,类型是穿越NAT的映射地址
这里面没有relay类型的地址,说明不需要中继传输。可能在同一个局域网中,或者穿越NAT使用P2P连通。
SIP信令
Session Initiation Protocol 会话初始协议,是一个应用层协议,用于建立、更改和终止多媒体会话,是通信的基础。
- 用户代理 UA, User Agent 即SIP终端,包含客户端(UAC,User Agent Client)和服务端(UAS,User Agent Server)两种类型。
- 注册服务器(Registrar Server)接收用户注册,注册内容存放在位置服务器,供后续查询。
- 位置服务器(Location Server)为代理服务器和重定向服务器提供UA信息。
- 代理服务器 Proxy Server 用于传递主角方(Caller)发送的会话请求到被叫方(Callee)
- 重定向服务器 Redirect Server 用于指示客户端连接新的地址,新地址可能是被叫方的UA地址,也可以是另一个代理服务地址。
注册
Bob SIP Server
| |
| REGISTER F1 |
|------------------------------>|
| 401 Unauthorized F2 |
|<------------------------------|
| REGISTER F3 |
|------------------------------>|
| 200 OK F4 |
|<------------------------------|
REGISTER sips:ss2.biloxi.example.com SIP/2.0
Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7
Max-Forwards: 70
From: Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl
To: Bob <sips:bob@biloxi.example.com>
Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
CSeq: 1 REGISTER
Contact: <sips:bob@client.biloxi.example.com>
Content-Length: 0
- Via 由服务器生成,用于记录请求路径,可以使响应找到回路
- Max-Forwards 消息最大转发次数,每转发一次减1,为0时响应483(Too Many Hops response)
- From 请求发送方标识
- To 请求接收方标识,因为这里是注册请求,所以与From一致
- Call-ID 标识一个特定的会话或注册消息,随机生成,全球唯一
- Cseq 请求序列号,区分同一会话中的不同请求
- Contact 用户代理UA的通信地址
呼叫
以最基本的会话流程为例
Alice Proxy Bob
| F1 INVITE | | // Alice向代理发起会话请求
|------------------->| F2 INVITE | // Proxy查找到Bob的地址并转发呼叫
| F3 100 Trying |------------------->| // Proxy向Alice响应,避免重复呼叫
|<-------------------| F4 180 Ringing | // Bob响铃响应
| F5 180 Ringing |<-------------------|
|<-------------------| |
| | F6 200 OK | // Bob接起电话
| F7 200 OK |<-------------------|
|<-------------------| |
| F8 ACK | // Alice回应OK,会话建立
|---------------------------------------->|
| Both Way RTP Media Established | // 双方开始数据传输
|<=======================================>|
| F9 BYE | // Alice方挂断会话
|---------------------------------------->|
| F10 200 OK | // Bob收到并响应,会话结束
|<----------------------------------------|
F1 INVITE Alice -> Proxy
INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>
Call-ID: 2xTb9vxSit55XU7p8@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:alice@client.atlanta.example.com>
Content-Type: application/sdp
Content-Length: 151
v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
F4 180 Ringing Bob -> Proxy
SIP/2.0 180 Ringing
Via: SIP/2.0/UDP ss2.biloxi.example.com:5060;branch=z9hG4bK2d4790.1;received=192.0.2.222
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=314159
Call-ID: 2xTb9vxSit55XU7p8@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com>
Content-Length: 0
信令通过两个Via找到回路
F6 200 OK Bob -> Proxy
SIP/2.0 200 OK
Via: SIP/2.0/UDP ss2.biloxi.example.com:5060;branch=z9hG4bK2d4790.1;received=192.0.2.222
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=314159
Call-ID: 2xTb9vxSit55XU7p8@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com>
Content-Type: application/sdp
Content-Length: 147
v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000
同一个Call-ID会话中的第二个请求,序号CSeq增加
F9 BYE Alice -> Bob
BYE sip:bob@client.chicago.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bo4
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=314159
Call-ID: 2xTb9vxSit55XU7p8@atlanta.example.com
CSeq: 2 BYE
Content-Length: 0
PBX
通过上述的P2P打洞技术,实现了客户端之间的数据包传输;通过SIP指令和SDP,实现了会话的管理。实际场景中,将这些技术统一构建为IP PBX服务(Private Branch Exchange)即网络电话交换机。IP PBX不仅可以通过网络连接企业内部电话,还可以通过PSTN(Public Switched Telephone Network)公共交换电话网连接传统电话。
FreeSwitch 是一个开源、免费、跨平台、支持多协议的电话软交换平台,最初由Asterisk项目的的开发者Anthony Minessal创建。
- 采用模块化设计,允许用户根据需要,定制化解决方案
- 支持灵活的脚本定制,如Lua、Javascript、Python,方便与各种系统集成
- 支持多种协议,可以与各种开源的PBX系统集成,如sipXecs、Call Weaver、Asterisk等
使其成为构建语音、视频和消息应用的理想选择,广泛应用于电话系统、呼叫中心、会议系统等。由于采用高性能的C语言开发,许多ISP和运营商把它作为关键软交换设备处理成千万上万路的并发通话,广泛应用于金融、电力、石油、煤炭等领域的呼叫中心、企业通信以及应急指挥调度平台等。在移动互联、物联网、大数据、云计算盛行的时代,许多创业者也把它用于通信领域的云平台。
用portsip测试一下(1000-1019 可用,密码4321,华为云,1个月有效)
9664 保持音乐
9195 回音测试,延迟5秒
9182 彩铃(应答前)
9178/9179 收发传真
5000 交互式语音应答(IVR)
4000 语音信箱
3300-3399 电话会议,48kHz
3200-3299 电话会议,32kHz
3100-3199 电话会议,16kHz
3000-3099 电话会议,8kHz
2000-2002 呼叫组
参考
- Peer-to-Peer Communication Across Network Address Translators
- datatracker.ietf.org/doc/html/rf… SIP: Session Initiation Protocol
- datatracker.ietf.org/doc/html/rf… SIP: Session Initiation Protocol
- datatracker.ietf.org/doc/html/rf… STUN - Simple Traversal of UDP
- datatracker.ietf.org/doc/html/rf… SIP Basic Call Flow Examples
- datatracker.ietf.org/doc/html/rf… Traversal Using Relays around NAT (TURN)
- datatracker.ietf.org/doc/html/rf… NAT Behavior Discovery Using STUN
- datatracker.ietf.org/doc/html/rf… Session Traversal Utilities for NAT (STUN)
- datatracker.ietf.org/doc/html/rf… Interactive Connectivity Establishment (ICE)
- datatracker.ietf.org/doc/html/rf… Session Traversal Utilities for NAT (STUN)
- www.gnu.org/software/ba… GNU telephony server
- www.gnu.org/software/si… GNU VoIP server
- github.com/yatevoip/ya… Yate - Yet Another Telephony Engine