STUN协议

NAT墙

完全锥形

IP限制型锥形

端口限制型锥形

对称型

NAT穿透

STUN协议简介

STUN(Session Traversal Utilities for NAT)是一个用于帮助客户端发现其所在NAT之后的公网IP地址和端口号。通信的双方都能知道所在的公网IP,并且能够让公网的NAT帮助转发,从而实现NAT穿透。STUN协议广泛应用在WebRTC实时通信系统中。STUN交互过程主要有以下几个步骤:

  • 客户端向 STUN 服务器发送请求。
  • STUN 服务器处理并响应。
  • 客户端接收响应并解析公共IP和端口。
  • 客户端根据收到的公共 IP 和端口进行 NAT 穿越和路径选择。

绑定请求与响应

客户端发STUN请求

客户端发送STUN请求,包的类型为Binding Request。请求的目标是让STUN 服务器返回客户端的公网IP地址和端口。

STUN Message Header:
  - Message Type: 0x0001 (Binding Request) //类型
  - Message Length: 0x0000
  - Magic Cookie: 0x2112A442               //固定值,用于标识STUN
  - Transaction ID: 0x63f96d584f90a66d18b68202 //用于唯一标识该请求的ID,匹配响应

STUN Attributes (Optional):  //以下是附加信息
  - Username: <username>
  - Integrity: <HMAC-SHA1 signature>
  - Fingerprint: <CRC-32C of the message>

STUN服务端响应

STUN服务器收到客户端的请求后,会放binding response回复,内容中携带了客户端的公网IP和端口号。

STUN Message Header:
  - Message Type: 0x0101 (Binding Response)  //表明是一个 Binding Response
  - Message Length: 0x000C
  - Magic Cookie: 0x2112A442
  - Transaction ID: 0x63f96d584f90a66d18b68202

STUN Attributes:
  - Mapped Address: 
      Family: IPv4
      Address: 203.0.113.1     //公网的IP
      Port: 3478               //公网的端口
  - Username: <username>
  - Integrity: <HMAC-SHA1 signature>
  - Fingerprint: <CRC-32C of the message>

连接检查

以webrtc举例,当需要通信的双方,通过信令交互SDP(在libpeer文章中,是通过mqtt)中交互的双方各自的公网IP地址信息后,可以使用STUN的binding request和binding response检查P2P的连通性,以确保P2P链路可达。

首先一般由一端率先发起binding request。

STUN Message Header:
  - Message Type: 0x0001 (Binding Request)
  - Message Length: 0x0008
  - Magic Cookie: 0x2112A442
  - Transaction ID: 0x63f96d584f90a32d18b68202

STUN Attributes:
  - Username: <peer-username>
  - Message Integrity: <HMAC-SHA1 signature>

接着等待对端回复binding response

STUN Message Header:
  - Message Type: 0x0101 (Binding Response)
  - Message Length: 0x0008
  - Magic Cookie: 0x2112A442
  - Transaction ID: 0x63f96d584f90a32d18b68202

STUN Attributes:
  - Mapped Address: 
      Family: IPv4
      Address: 203.0.113.10
      Port: 3479

当发起方接受到binding response后,表明连通性正常,下面是代码的流程示例。

agent_connectivity_check()
{
    /*1. 发送 binding request*/
    agent_create_binding_request(agent, &msg)
        stun_msg_create(msg, STUN_CLASS_REQUEST | STUN_METHOD_BINDING);
        stun_msg_write_attr(msg, STUN_ATTR_TYPE_USERNAME, strlen(username), username);
        stun_msg_write_attr(msg, STUN_ATTR_TYPE_PRIORITY, 4, (char*)&agent->nominated_pair->priority);
        stun_msg_finish(msg, STUN_CREDENTIAL_SHORT_TERM, agent->remote_upwd, strlen(agent->remote_upwd));
    agent_socket_send(agent, &agent->nominated_pair->remote->addr, msg.buf, msg.size);//发送,这里目标的ip和端口就是对端的ip地址和端口号。

    /*2. 等待接受*/
    agent_recv
        switch (stun_msg.stunclass) {
            /*收到对端发送的binding request,构建一个response发送回去。*/
            case STUN_CLASS_REQUEST:
                agent_process_stun_request
                    agent_create_binding_response
                    stun_set_mapped_address
                    agent_socket_send
            /*收到对端回复的binding response,将state设置为success*/
            case STUN_CLASS_RESPONSE:
                agent_process_stun_response
                    agent->nominated_pair->state = ICE_CANDIDATE_STATE_SUCCEEDED;
        }
}

实践流程分析

本次实验使用的stun服务器是,有两个设备,设备A是一块开发板,设备B是电脑,分别连接到同一个路由器上。

Resolved stun.l.google.com -> 74.125.250.129,
Resolved stun/turn server 74.125.250.129:19302

设备A(开发板)的IP地址信息是

inet addr:192.168.51.127

设备B(电脑)的IP地址信息是

192.168.51.227

设备发送向STUN服务端发送请求

开发板发送binding request

这里是设备向stun.l.google.com:74.125.250.129:19302服务发送binding request请求。

电脑发送binding request

设备的电脑一下发了3个binding request,其中一个是发送给google stun服务器的,但是另外两个实际上是发给设备A的本地IP和NAT墙的IP,可能是此前连接过,浏览器缓存了信息。

服务端回复

设备A收到回复

stun.l.google.com回复了binding success response。并表明了设备的公网IP为120.236.240.177,其端口为8139。

电脑B收到回复

通过message transaction ID可以看到电脑收到了Google STUN回复的binding success response,告知其NAT地址为120.236.240.177,端口号是8140。因为连接的是同一个路由器,所以NAT是一样的。同时板子A设备192.168.51.127也对齐回复了binding success response,告知其地址为192.168.51.227:59777。

板子A请求检查

板子A发送了一个binding request,对端回复了一个binding success response,则表示联通已建立。

TURN协议

TURN协议是在STUN的基础上增加了中继功能,解决的是对称型NAT无法穿透的问题。如下图的结构体,需要注意的是,使用TURN作为中继转发,传输的设备一方式TURN Client,另一方是Peer A。即TURN Client需要与TURN Server建立TURN协议交互,而Peer A与TURN Server只需要做STUN协议交互。此前调试了两个通信设备,两端都是使用TURN协议发起连接,导致一端checking失败,最后将一端修改为STUN协议交互即可。

                                        Peer A
                                        Server-Reflexive    +---------+
                                        Transport Address   |         |
                                        192.0.2.150:32102   |         |
                                            |              /|         |
                          TURN              |            / ^|  Peer A |
    Client’s              Server            |           /  ||         |
    Host Transport        Transport         |         //   ||         |
    Address               Address           |       //     |+---------+
   10.1.1.2:49721       192.0.2.15:3478     |+-+  //     Peer A
            |               |               ||N| /       Host Transport
            |   +-+         |               ||A|/        Address
            |   | |         |               v|T|     192.168.100.2:49582
            |   | |         |               /+-+
 +---------+|   | |         |+---------+   /              +---------+
 |         ||   |N|         ||         | //               |         |
 | TURN    |v   | |         v| TURN    |/                 |         |
 | Client  |----|A|----------| Server  |------------------|  Peer B |
 |         |    | |^         |         |^                ^|         |
 |         |    |T||         |         ||                ||         |
 +---------+    | ||         +---------+|                |+---------+
                | ||                    |                |
                | ||                    |                |
                +-+|                    |                |
                   |                    |                |
                   |                    |                |
             Client’s                   |            Peer B
             Server-Reflexive    Relayed             Transport
             Transport Address   Transport Address   Address
             192.0.2.1:7000      192.0.2.15:50000     192.0.2.210:49191

Allocate

请求服务器,分配一个中继服务。中继建立连接后,客户端需要进行保活,定期发送refresh request给服务器端。

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 --|             |             |
  |                                    |             |             |

如上示例,客户端先发送不带验证信息的Allocate请求,此时STUN服务器会返回error response,客户端收到错误后加上验证信息再次请求。以IPC通信为例,摄像头端是Peer A(资源有限,不支持证书请求等协议),发起的是STUN协议,而手机预览端是TURN Client,发起的是TURN交互协议。

Send机制

客户端和peer直接通信可以通过TURN server进行转发信息,主要有两种方式,第一种是使用Send/data方式,第二种是使用channels方式。其主要目的是通过某种方式告知服务器,从client接收到的数据应该发给那个peer。下面是Send/data机制。

TURN Server在进行转发前,需要client先安装一个到对等端的许可(permission),许可证可以通过CreatePermission request/resp来进行交互。

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 |             |             |
  |                                    |             |             |

Chanel机制

等同于send/data机制。

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 --|             |             |
  |                                    |             |             |

参考:

  1. https://mp.weixin.qq.com/s/XMhSDABc74dpALIHrPPt7w

  2. https://www.ctyun.cn/developer/article/586260405821509

  3. https://www.cnblogs.com/pannengzhi/p/5048965.html