本文主要是介绍Turn Client 和 Server交互简单流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 1简介
- 2 报文结构
- 2.1 Message Header
- 2.1.1 头部2bits
- 2.1.2 Stun Message Type
- 2.1.3 Message Length
- 2.1.4 Magic Cookie
- 2.1.5 Transaction ID (96bits)
- 2.2 Stun Attributes
- 2.2.1 Type
- 2.2.2 Length
- 2.2.3 Value
- 2.2.3.1 MAPPED-ADDRESS
- 2.2.3.2 XOR-MAPPED-ADDRESS
- 2.2.3.3 USERNAME
- 2.2.3.4 MESSAGE-INTEGRITY
- 2.2.3.5 REALM
- 2.2.3.6 NONCE
- 2.2.3.7 ERROR-CODE
- 3 分配
- 3.1 Client ---> Server Allocate
- 3.2 Server ---> Client Error
- 3.3 Client ---> Server Allocate
- 3.4 Server ---> Client Success
- 4 刷新
- 4.1 Client ---> Server Refresh
- 4.2 Server ---> Client Success
- 5 CreatePermission
- 5.1 Client ---> Server CreatePermission
- 5.2 Server ---> Client Success
- 6 连接Peer
- 6.1 Server ---> Client ConnectionAttempt
- 6.2 Client ---> Server ConnectionBind
- 6.3 Server --->Client Error
- 6.4 Client ---> Server ConnectionBind
- 6.5 Server --->Client Success
- 7 一些记录
- 7.1 多路复用时候,如何判断是Turn包?
参考文章:
1.RFC3489
https://www.rfc-editor.org/info/rfc3489
2.RFC5389
https://www.rfc-editor.org/info/rfc5389
3.RFC5766
https://www.rfc-editor.org/rfc/rfc5766.html
4.RFC6062
https://www.rfc-editor.org/rfc/rfc6062.html
5.P2P通信标准协议(一)之Stun
https://www.cnblogs.com/pannengzhi/p/5041546.html
6.P2P通信标准协议(二)之Turn
https://www.cnblogs.com/pannengzhi/p/5048965.html
1简介
前面的参考文章详细介绍了Turn的定义、结构以及流程。
Turn Client和Turn Server简单流程描述如下:Turn Client的内网IP:PORT(A1),通过路由的公网IP:PORT(A2), 通过Turn命令向Turn Server IP:PORT(B1) 创建ALLOCATION。Turn serve随后会给Turn Client分配一个中继地址IP:PORT(B2)。若Peer要向这个Turn Client发送数据,只要直接向这个Turn Client的中继地址B2发送数据即可,然后Turn Server会把发给B2的数据转发给A2。
Turn Client和Turn Server通信以及通过中继转发数据时候可以使用UDP/TCP/TLS over TCP。但是,Turn Server和Peer通信是基于UDP的,所以若Turn Client和Turn Server通过TCP进行交互,会在Turn Server端进行协议转换,RFC5766中画图如下:
2 报文结构
在开始分析Client 和 Server 交互流程之前,需要先对Turn的报文结构做个简单梳理,Turn是Stun的一个扩展,所以Turn的报文结构遵循Stun的报文结构规范。
2.1 Message Header
所有的Turn 报文都有20个字节的头部信息,并且采用大端模式。
在RFC3489中,头部结构如下:
在RFC5389中,为了兼容RFC3489,将头部结构定义如下:
2.1.1 头部2bits
在上面头部的两个字节必须为0。
2.1.2 Stun Message Type
关于Message Type,在RFC8389中描述如下:
消息类型定义了Stun消息的消息类别(请求,成功响应,失败响应或指示)和消息方法(主要功能)。 尽管有四种消息类别,但Stun中只有两种事务类型:请求/响应事务(由请求消息和响应消息组成)和指示事务(由单个指示消息组成)。 响应类别分为错误响应和成功响应,以帮助快速处理Stun消息。
这个字段又可以分为如下结构:
可以看到上面有M0 ~ M11 C0 ~ C1这两种类型。
M0 ~ M11代表 message type,而C0 ~ C11代表message class。
message class有如下定义:
0b00: Requeset
0b01: Indication
0b10: Success response
0b11: Error response
message type 有如下定义:
0x003 : Allocate (only request/response semantics defined)
0x004 : Refresh (only request/response semantics defined)
0x006 : Send (only indication semantics defined)
0x007 : Data (only indication semantics defined)
0x008 : CreatePermission (only request/response semantics defined
0x009 : ChannelBind (only request/response semantics defined)
2.1.3 Message Length
message length 表示信息的长度,不包含20字节的头部。另外,Stun的属性都为4字节对齐,因此最后两位始终为0。
2.1.4 Magic Cookie
固定为0x2112A442。
2.1.5 Transaction ID (96bits)
是一个96个字节的标识符,用于区分Stun传输事务。详细介绍见RFC5389。
2.2 Stun Attributes
在头部之后,会有0个或多个Stun属性。每一个Stun属性必须是TLV结构。
T:Type 16bit L:Length 16bit V:Value 32bit对齐, 结构图如下:
2.2.1 Type
属性的类型,0x0000-0x7FFF之间的属性被指定为强制理解,意思是Stun终端必须要理解此属性,否则将返回错误信息;而0x8000-0xFFFF之间的属性为选择性理解,即如果Stun终端不识别此属性则将其忽略。
Type部分取值如下:
0x0000: (Reserved)
0x0001: MAPPED-ADDRESS
0x0002: (Reserved; was RESPONSE-ADDRESS)
0x0003: (Reserved; was CHANGE-ADDRESS)
0x0004: (Reserved; was SOURCE-ADDRESS)
0x0005: (Reserved; was CHANGED-ADDRESS)
0x0006: USERNAME
0x0007: (Reserved; was PASSWORD)
0x0008: MESSAGE-INTEGRITY
0x0009: ERROR-CODE
0x000A: UNKNOWN-ATTRIBUTES
0x000B: (Reserved; was REFLECTED-FROM)
0x0014: REALM
0x0015: NONCE
0x0020: XOR-MAPPED-ADDRESS
0x8022: SOFTWARE
0x8023: ALTERNATE-Server
0x8028: FINGERPRINT
2.2.2 Length
value的长度,因为是4字节对齐,所以若属性的长度不是4字节,会被补数据,而长度也会加上补数据的长度,所以最终长度一定是4字节的倍数。
2.2.3 Value
下面几个是比较常见的(不做详细介绍,详细介绍见RFC5389):
2.2.3.1 MAPPED-ADDRESS
结构如下:
Family表示使用的是IPv4(0x01)或者IPv6(0x02)
Port表示端口号
Address表示IP地址
2.2.3.2 XOR-MAPPED-ADDRESS
结构如下:
和上面的MAPPED-ADDRESS类似,只不过将Port和Address进行了异或。
2.2.3.3 USERNAME
用于message integrity,它标识在message integrity检查中使用的用户名和密码组合。
2.2.3.4 MESSAGE-INTEGRITY
message integrity可以出现在任何Stun消息类型中。它是由 username:realm:password进行MD5加密得到的数据。
2.2.3.5 REALM
可以出现在请求和响应中,在请求中存在REALM属性表示正在使用长期凭证进行身份验证。
2.2.3.6 NONCE
可以出现在请求和响应中,是由服务器创建的。
2.2.3.7 ERROR-CODE
用于error response报文中。其包含了300-699表示的错误代码,以及一个UTF-8格式的文字出错信息,部分错误类型如下:
300: 尝试替代
400: 错误请求
401: 未授权
420: 未知属性
438: 过期Nonce
500: 服务器错误
下面对Turn Server和Turn Client整个交互过程做个简单分析。
3 分配
Client会向Server发送allocate请求,但是没有携带验证信息,Server会返回error信息。然后,Client会解析error信息中的有效信息,添加进验证信息中,再次给Server发送allocate请求,Server解析到验证信息并进行验证,会返回成功信息。所以, Client会向Server发送两次allocate请求。
流程如下:
抓取的包如下:
3.1 Client —> Server Allocate
报文如下:
Client使用TCP和Server通信,从上面可以看到,Client第一次向Server发送allocate请求,没有携带任何的Stun属性,只是携带了头部信息,并且type值为0x03。
3.2 Server —> Client Error
报文如下:
Server返回401错误,并返回了几个Turn属性,其中我们需要使用NONCE再次向Server发送allocate请求。
同时,需要注意服务器返回的头部信息中的Transaction ID和Client发送出去的一致。
3.3 Client —> Server Allocate
报文如下:
Client再次向Server发送allocate请求,并携带一些Stun属性信息。NONCE为Server上次传递过来的。
3.4 Server —> Client Success
报文如下:
在Server返回的报文信息中,XOR-RELAYED-ADDRESS表示的Client的路由公网IP:PORT(A2),XOR-MAPPED-ADDRESS表示的是Server为Client分配的中继地址IP:PORT(B2)。
至此,Client已经申请到了中继地址。
4 刷新
在上面的3.3 和 3.4中,我们可以看到有个字段:LIFETIME。
这个属性标识在没有刷新情况下Server将维持分配的持续时间,单位为秒。若在这个时间之内,Client没有向Server发送refresh请求,那么Client被分配的IP:PORT将会被回收,因此我们需要定时在LIFETIME之内向Server发送refresh请求。
流程如下:
报文如下:
4.1 Client —> Server Refresh
报文如下:
在上面的报文中,Client向Server发出了refersh请求,刷新时间是600s。
4.2 Server —> Client Success
报文如下:
Server返回成功,并且给出LIFETIME为300s。
5 CreatePermission
在RFC5766中,Send Mechanism章节介绍到,当Client发送数据给Server时候,如果Client的Send indications没有被验证, 会被Server当做攻击,这些数据会被丢弃。因此在向Server发送数据前,先向Server发送CreatePermission 请求,请求中包含了要发送的Peer的IP地址。
如下,如果Client没有向Server发送Peer B的CreatePermission 请求,数据就会被丢弃。
报文如下:
5.1 Client —> Server CreatePermission
报文如下:
上面报文的XOR-Peer-ADDRESS便是Client要发送Peer的IP地址。
5.2 Server —> Client Success
报文如下:
Server返回成功。
6 连接Peer
当Client向Server 发送allocate请求成功后,并且定时进行refresh。此时,Client就会等待Server发送Peer的信息,然后Client会向Server发送ConnectionBind请求,和Peer建立连接,然后就可以正常和Peer进行通信。
注意:以下的流程,只是根据测试现象得出的,可能有些服务器因为功能不同或者参考的RFC不同导致交互和下面的不一致,下面的仅供参考
在RFC6062中,这样描述:
Server成功处理了分配请求后,当一个Peer和Client的中继地址建立连接时,Client都会接收Server发送的ConnectionAttempt indication。这个indication包含了CONNECTION-ID和XOR-Peer-ADDRESS属性。如果Client希望接收这个连接,就会重新创建一个新的socket,和Server建立新的连接,并使用新的socket向Server发送ConnectionBind请求,这个请求包含了Server发送ConnectionAttempt 时候的CONNECTION-ID。
后续Client和Peer通信都会使用新的socket,而原有的socket则会继续等待新的Peer连接,等到Server的ConnectionAttempt indication。
报文如下:
6.1 Server —> Client ConnectionAttempt
报文如下:
Server发送过来的请求携带了CONNECTION-ID和XOR-Peer-ADDRESS这两个重要的属性。
6.2 Client —> Server ConnectionBind
报文如下:
上面的报文中,SRC PORT是8507,和以前的端口不一致,说明Client已经使用新的socket向Server发送ConnectionBind请求,并且携带了一些必要的属性。
6.3 Server —>Client Error
由于是新的连接,使用的NONCE为原有的NONCE,服务器可能会返回401,并且携带新的NONCE。
6.4 Client —> Server ConnectionBind
报文如下:
Client使用新的NONCE重新向Server发送ConnectionBind请求。
6.5 Server —>Client Success
报文如下:
服务器返回success请求。
至此,Client已经和Peer建立了连接,后续就可以使用这个新的socket进行数据收发。
7 一些记录
7.1 多路复用时候,如何判断是Turn包?
可以通过以下进行判断
Turn头部最高两位必需是0;
Turn头部的Magic Cookie 为固定值;
Turn头部的Message Length以字节为单位,因为所有的Stun是4字节对齐的,所以这个字段的最后两位恒等于0。
这篇关于Turn Client 和 Server交互简单流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!