基于jrtplib的NAT穿透

4 基于jrtplib的NAT穿透

 

4.1 NAT穿透的基础只是

 

有关于NAT穿透的基础知识


4.2 rtp/rtcp传输涉及到的NAT穿透

 

    rtp/rtcp传输数据的时候,需要两个端口支持。即rtp端口用于传输rtp数据,即传输的多媒体数据;rtcp端口用于传输rtcp控制协议信息。rtp/rtcp协议默认的端口是rtcp port = rtp port + 1 。详细的说,比如A终端和B终端之间通过rtp/rtcp进行通信,

    基于jrtplib的NAT穿透_第1张图片

   

如上图,

                                                          本地IP:PORT                                                        NAT映射后IP:PORT

UACA RTP的发送和接收IP:PORT : 192.168.1.100:8000                                             61.144.174.230:1597

UACA RTCP的发送和接收IP:PORT:192.168.1.100:8001                                             61.144.174.230:1602

UACB RTP的发送和接收IP:PORT : 192.168.1.10:8000                                                61.144.174.12:8357

UACB RTCP的发送和接收IP:PORT:192.168.1.10:8001                                                61.144.174.12:8420

   

上图可以得到一下一些信息:

      (一) 本地端口 RTCP PORT = RTP PORT + 1;但是经过NAT映射之后也有可能满足这个等式,但是并不一定有这个关系。

    (二)在NAT设备后面的终端的本地IP:PORT并不被NAT外的设置可知,也就无法通过终端的本地IP:PORT与之通信。而必须通过NAT映射之后的公网IP:PORT作为目的地址进行通信。

    如上图的终端A如果要发送RTP数据到终端B,UACA发送的目的地址只能是:61.144.174.12:8357。同理,UACB发送RTP数据给UACA,目的地址只能是: 61.144.174.230:1597 。

    (三)也许看到这里,如何得到自己的外网IP:PORT呢?如何得到对方的外网IP:PORT呢?这就是NAT IP:PORT转换和穿孔(puncture),下回分解。

 

4.3 NAT 地址转换

 

如上所述,终端需要知道自己的外网IP:port,可以通过STUN、STUNT、TURN、Full Proxy等方式。这里介绍通过STUN方式实现NAT穿透。

 

STUN: Simple Traversal of UDP Through NAT。即通过UDP对NAT进行穿透。

STUNT:Simple Traversal of UDP Through NATs and TCP too.可以通过TCP对NAT进行穿透。

STUN是一个标准协议,具体的协议内容网络上很多。在此不累述了。

为了通过STUN实现NAT穿透,得到自己的公网IP:PORT,必须有一个公网STUN服务器,以及我们的客户端必须支持STUN Client功能。STUN Client 通过UDP发送一个request到STUN服务器,该请求通过NAT设备的时候会把数据报头中的本地IP:PORT换成该本地IP:PORT对应的公网IP:PORT,STUN服务器接收到该数据包后就可以把该公网IP:PORT 发送给STUN Client。这样我们就得到了自己的公网IP:PORT。这样别的终端就可以把该公网IP:PORT最为发送UDP数据的目的地址发送UDP数据。

 

推荐一款STUN client/server 程序代码,http://sourceforge.net/projects/stun/files/ 

这是一款开源软件。在客户端中的主要函数是下面这个:

NatType

stunNatType( StunAddress4& dest,       //in 公网STUN服务器地址,如stun.xten.net

             bool verbose,                              //in 调试时是否输出调试信息
             bool* preservePort=0,                //out  if set, is return for if NAT preservers ports or not
             bool* hairpin=0 ,                        //out  if set, is the return for if NAT will hairpin packetsNAT设备是否支持回环
             int port=0,                               // in 本地测试端口port to use for the test, 0 to choose random port
             StunAddress4* sAddr=0        // out NIC to use ,返回STUN返回的本地地址的公网IP:PORT
   );

             输入StunAddress和测试端口port,得到本地IP:PORT对应的公网IP:PORT.

            

4.4 对jrtplib  的改造

 

jrtplib中对rtp rtcp端口的处理关系是:rtcp port = rtp port + 1 。这就有问题,本地端口可以按照这个等式来设置端口,但是经过NAT映射之后的公网端口是随机的,有可能并不满足上述等式。

    int portbase = 6000;                        //设置本地rtp端口为6000

    transparams.SetPortbase(portbase);//默认的本地rtcp端口为6001.因为这里是本地设置,所一这样设置OK
    status = sess.Create(sessparams,&transparams);   
    checkerror(status);
  
    RTPIPv4Address addr(destip,destport); //设置目的地的rtp接收IP:PORT,公网传输的话就要设置为对方的rtp公网IP:PORT
    // AddDestination()的内部处理是把addr.ip和addr.port+1赋给rtcp。这样如果对方在公网上,就有问题了。因为对方的rtcp port 可能不等于rtp port +1;这就导致对方收不到rtcp数据包。

    status = sess.AddDestination(addr); 

   

    通过跟踪AddDestination()函数的实现,发现在class RTPIPv4Destination的构造函数中是这样构造一个发送目的地址的:

        RTPIPv4Destination(uint32_t ip,uint16_t rtpportbase)                   
    {
        memset(&rtpaddr,0,sizeof(struct sockaddr_in));
        memset(&rtcpaddr,0,sizeof(struct sockaddr_in));
       
        rtpaddr.sin_family = AF_INET;
        rtpaddr.sin_port = htons(rtpportbase);
        rtpaddr.sin_addr.s_addr = htonl(ip);
       
            rtcpaddr.sin_family = AF_INET;
            rtcpaddr.sin_port = htons(rtpportbase+1);//默认把rtp的端口+1赋给目的rtcp端口。
            rtcpaddr.sin_addr.s_addr = htonl(ip);
        RTPIPv4Destination::ip = ip;
    }

       

        为了实现:可以自定义目的IP地址和目的rtp port和rtcp port。为了实现这么目标,自己动手改造下面几个函数:构造函数RTPIPv4Destination() 、RTPSession::AddDestination(),思路是在目的地址设置相关函数中增加一个rtcp ip 和port参数。

        RTPIPv4Destination(uint32_t ip,uint16_t rtpportbase,uint32_t rtcpip,uint16_t rtcpport)                   
    {
        memset(&rtpaddr,0,sizeof(struct sockaddr_in));
        memset(&rtcpaddr,0,sizeof(struct sockaddr_in));
       
        rtpaddr.sin_family = AF_INET;
        rtpaddr.sin_port = htons(rtpportbase);
        rtpaddr.sin_addr.s_addr = htonl(ip);
       
        /**If rtcport has not been set separately, use the default rtcpport*/
        if ( 0 == rtcpport )
        {
            rtcpaddr.sin_family = AF_INET;
            rtcpaddr.sin_port = htons(rtpportbase+1);
            rtcpaddr.sin_addr.s_addr = htonl(ip);
        }else
        {
            rtcpaddr.sin_family = AF_INET;
            rtcpaddr.sin_port = htons(rtcpport);
            rtcpaddr.sin_addr.s_addr = htonl(ip);
        }
       
        RTPIPv4Destination::ip = ip;
    }

       

        int RTPSession::AddDestination(const RTPAddress &addr,const RTPIPv4Address &rtcpaddr)
{
    if (!created)
        return ERR_RTP_SESSION_NOTCREATED;
    return rtptrans->AddDestination(addr,rtcpaddr);
}

       在调用RTPSession::AddDestination、定义RTPIPv4Destination的时候实参也相应增加目的rtcp参数。

      

       这样改造之后就可以自定义独立的设置目的地址rtp ,rtcp端口了

你可能感兴趣的:(基于jrtplib的NAT穿透)