Resiprocate 如何使用STUN

STUN-Simple Traversal of User Datagram Protocol (UDP) Through NetworkAddress Translators (NATs),基於UDP,關於STUN介紹不再贅述,想要了解的話可以參考stun協議RFC 3489

 

Resiprocate可能用到STUN的場合

一、proxy架設在內網,需要獲得proxy外網IP(也可以用DNS實現);二、實現兩個內網客戶端SIP通信。客戶端支持Stun,路由器類型屬於完全圓錐NAT,地址受限NAT,或端口受限NAT,可以利用stun穿越。或者是通過路由器端口映射。

NAT

 

檢測NAT類型步驟


NAT的四種類型

Full cone NAT,亦即著名的一對一(one-to-one NAT

  • 一旦一個內部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發自iAddr:port1的包都經由eAddr:port2向外發送.任意外部主機都能通過給eAddr:port2發包到達iAddr:port1

 


Address-Restricted cone NAT

  • 一旦一個內部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發自iAddr:port1的包都經由eAddr:port2向外發送.任意外部主機(hostAddr:any)都能通過給eAddr:port2發包到達iAddr:port1的前提是:iAddr:port1之前發送過包到hostAddr:any. "any"也就是說端口不受限制


Port-Restricted cone NAT

類似受限制錐形NAT(Restricted cone NAT),但是還有端口限制。

  • 一旦一個內部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發自iAddr:port1的包都經由eAddr:port2向外發送.一個外部主機(hostAddr:port3)能夠發包到達iAddr:port1的前提是:iAddr:port1之前發送過包到hostAddr:port3.


Symmetric NAT(對稱NAT)

  • 每一個來自相同內部IP與port的請求到一個特定目的地的IP地址和端口,映射到一個獨特的外部來源的IP地址和端口。
    同一個內部主機發出一個信息包到不同的目的端,不同的映射使用
  • 只有曾經收到過內部主機封包的外部主機,才能夠把封包發回來


 

ResiprocateSTUN的支持

Resiprocate使用了Vovida的Stun庫ResiprocateStun的支持可以參考STUN support,說的比較詳盡。簡單來說,ResiprocateStun支持全在UdpTransport:

UdpTransport::process接收監聽端口數據封包包括了Stunresponse Stun request以及SIP message

UdpTransport::stunSendTest發送Stun請求。

UdpTransport::stunResult輸出外網地址。

如果作爲Stun Client,需要當添加transport的時候保存返回的UdpTransport指針。發送Stun請求,並獲得外網地址是異步的,如果需要同步的話,如下使用:

boolstunSendTest(const Tuple& dest);
bool stunResult(Tuple& mappedAddress);
 
voidSendStunTest()
{
     if (!m_pUdpTransport) return;
     hostent* h =gethostbyname(STUNServer);
     in_addr sin_addr = *(structin_addr*)h->h_addr;
     resip::Tuple tStunDest(sin_addr,STUNPort, UDP, Data::Empty);
     m_pUdpTransport->stunSendTest(tStunDest);
     mLastStunTestTime= GetTickCount();
}
 
resip::TupleGetStunAddress()
{
    resip::Tuple mMappedAddress;
    mMappedAddress.setPort(0);
    if(!m_pUdpTransport) return mMappedAddress;
    if(!m_pUdpTransport->stunResult(mMappedAddress))
    {
         // no valid result available,send another request
         SendStunTest();
    }
    else if((GetTickCount() - mLastStunTestTime) > 1000 * 60 * 3) 
    {
         // don't use a STUN responsethat is older than 3 minutes
         SendStunTest();
    }
    DWORDdwTmpLastStunTestTime = mLastStunTestTime;
    while((GetTickCount() - dwTmpLastStunTestTime) < 5 * 1000) // wait 5s forresult
    {
         if(m_pUdpTransport->stunResult(mMappedAddress))
              break;
         Sleep(200);
    }
    returnmMappedAddress;
}


 

附加我自己的用法,實現proxy假設在內網的情況下,獲得外網IP

  UdpTransport::process

   

//每30s向mStunServer發送stun請求,發送失敗的話,使用DNS(mStunServer爲域名)
   if(mUseStun)
   {
              static UInt64last=Timer::getTimeMs();
  UInt64 now=Timer::getTimeMs();
  if(now-last>30000)  //30s 執行一次
  {
            hostent* h =gethostbyname(mStunServer);
 if(h)
 {
            in_addr sin_addr = *(structin_addr*)h->h_addr;
resip::Tupledest(sin_addr, 3478, UDP,Data::Empty);                        
if(!stunSendTest(dest))
{
h= gethostbyname(mDomainName.c_str());
if(h)
{
mStunSuccess= true;         
sin_addr= *(struct in_addr*)h->h_addr;
mStunMappedAddress= Tuple(sin_addr,mStunMappedAddress.getPort(), UDP);
 
}
 
}
}
last=Timer::getTimeMs();
  }     
   }

 

獲得外網IP,外網IP存儲在mExternalIP

   

  resip::SdpContents*pSipSdp = dynamic_cast<resip::SdpContents*>(msg->getContents());
     if ( pSipSdp )
     {
 UdpTransport* temp =dynamic_cast<UdpTransport*>(target.transport);
if(pSipSdp->session().origin().getAddress()== temp->mDomainName ||
  pSipSdp->session().connection().getAddress() == temp->mDomainName)
{
Tupletuple;
boolbRet = temp->stunResult(tuple);
DataexternalIP = Tuple::inet_ntop(tuple);
ErrLog(<<"StunResultret "<<(bRet?"true. ":"false.")<<"local IP change to "<<externalIP);
if(bRet)
{
   externalIP = Tuple::inet_ntop(tuple);
  
   if (pSipSdp->session().connection().getAddress() == "0.0.0.0" )
   {
     //只修改                                                        
 pSipSdp->session().origin().setAddress(externalIP);
   }else
   {
              pSipSdp->session().origin().setAddress(externalIP);                                                        
 pSipSdp->session().connection().setAddress(externalIP);
  }                                  
   mExternalIp = externalIP;
}elseif(!mExternalIp.empty())
{
   if (pSipSdp->session().connection().getAddress() == "0.0.0.0" )
   {
     //只修改                                                        
 pSipSdp->session().origin().setAddress(mExternalIp);
   }else
   {
     pSipSdp->session().origin().setAddress(mExternalIp);                                                                        
 pSipSdp->session().connection().setAddress(mExternalIp);
  }                                  
}
 
}
    }   

     

 

 

 

 

 

 

 

發佈了65 篇原創文章 · 獲贊 32 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章