青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

S.l.e!ep.¢%

像打了激速一樣,以四倍的速度運轉,開心的工作
簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理
TCP實現P2P通信、TCP穿越NAT的方法、TCP打洞
2007-12-17 18:00

下載源代碼

Internet的迅速發展以及IPv4 地址數量的限制使得網絡地址翻譯(NAT,Network Address Trans2lation)設備得到廣泛應用。NAT設備允許處于同一NAT后的多臺主機共享一個公網(本文將處于同一NAT后的網絡稱為私網,處于NAT前的網絡稱為公網) IP 地址。一個私網IP 地址通過NAT設備與公網的其他主機通信。公網和私網IP地址域,如下圖所示:

廣域網與私網示意圖

一般來說都是由私網內主機(例如上圖中“電腦A-01”)主動發起連接,數據包經過NAT地址轉換后送給公網上的服務器(例如上圖中的“Server”),連接建立以后可雙向傳送數據,NAT設備允許私網內主機主動向公網內主機發送數據,但卻禁止反方向的主動傳遞,但在一些特殊的場合需要不同私網內的主機進行互聯(例如P2P軟件、網絡會議、視頻傳輸等),TCP穿越NAT的問題必須解決。網上關于UDP穿越NAT的文章很多,而且還有配套源代碼,但是我個人認為UDP數據雖然速度快,但是沒有保障,而且NAT為UDP準備的臨時端口號有生命周期的限制,使用起來不夠方便,在需要保證傳輸質量的應用上TCP連接還是首選(例如:文件傳輸)。
網上也有不少關于TCP穿越NAT(即TCP打洞)的介紹文章,但不幸我還沒找到相關的源代碼可以參考,我利用空余時間寫了一個可以實現TCP穿越NAT,讓不同的私網內主機建立直接的TCP通信的源代碼。

這里需要介紹一下NAT的類型:
NAT設備的類型對于TCP穿越NAT,有著十分重要的影響,根據端口映射方式,NAT可分為如下4類,前3種NAT類型可統稱為cone類型。
(1)全克隆( Full Cone) : NAT把所有來自相同內部IP地址和端口的請求映射到相同的外部IP地址和端口。任何一個外部主機均可通過該映射發送IP包到該內部主機。
(2)限制性克隆(Restricted Cone) : NAT把所有來自相同內部IP地址和端口的請求映射到相同的外部IP地址和端口。但是,只有當內部主機先給IP地址為X的外部主機發送IP包,該外部主機才能向該內部主機發送IP包。
(3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆與限制性克隆類似,只是多了端口號的限制,即只有內部主機先向IP地址為X,端口號為P的外部主機發送1個IP包,該外部主機才能夠把源端口號為P的IP包發送給該內部主機。
(4)對稱式NAT ( Symmetric NAT) :這種類型的NAT與上述3種類型的不同,在于當同一內部主機使用相同的端口與不同地址的外部主機進行通信時, NAT對該內部主機的映射會有所不同。對稱式NAT不保證所有會話中的私有地址和公開IP之間綁定的一致性。相反,它為每個新的會話分配一個新的端口號。

我們先假設一下:有一個服務器S在公網上有一個IP,兩個私網分別由NAT-A和NAT-B連接到公網,NAT-A后面有一臺客戶端A,NAT-B后面有一臺客戶端B,現在,我們需要借助S將A和B建立直接的TCP連接,即由B向A打一個洞,讓A可以沿這個洞直接連接到B主機,就好像NAT-B不存在一樣。
實現過程如下(請參照源代碼):
1、 S啟動兩個網絡偵聽,一個叫【主連接】偵聽,一個叫【協助打洞】的偵聽。
2、 A和B分別與S的【主連接】保持聯系。
3、 當A需要和B建立直接的TCP連接時,首先連接S的【協助打洞】端口,并發送協助連接申請。同時在該端口號上啟動偵聽。注意由于要在相同的網絡終端上綁定到不同的套接字上,所以必須為這些套接字設置 SO_REUSEADDR 屬性(即允許重用),否則偵聽會失敗。
4、 S的【協助打洞】連接收到A的申請后通過【主連接】通知B,并將A經過NAT-A轉換后的公網IP地址和端口等信息告訴B。
5、 B收到S的連接通知后首先與S的【協助打洞】端口連接,隨便發送一些數據后立即斷開,這樣做的目的是讓S能知道B經過NAT-B轉換后的公網IP和端口號。
6、 B嘗試與A的經過NAT-A轉換后的公網IP地址和端口進行connect,根據不同的路由器會有不同的結果,有些路由器在這個操作就能建立連接(例如我用的TPLink R402),大多數路由器對于不請自到的SYN請求包直接丟棄而導致connect失敗,但NAT-A會紀錄此次連接的源地址和端口號,為接下來真正的連接做好了準備,這就是所謂的打洞,即B向A打了一個洞,下次A就能直接連接到B剛才使用的端口號了。
7、 客戶端B打洞的同時在相同的端口上啟動偵聽。B在一切準備就緒以后通過與S的【主連接】回復消息“我已經準備好”,S在收到以后將B經過NAT-B轉換后的公網IP和端口號告訴給A。
8、 A收到S回復的B的公網IP和端口號等信息以后,開始連接到B公網IP和端口號,由于在步驟6中B曾經嘗試連接過A的公網IP地址和端口,NAT-A紀錄了此次連接的信息,所以當A主動連接B時,NAT-B會認為是合法的SYN數據,并允許通過,從而直接的TCP連接建立起來了。

整個實現過程靠文字恐怕很難講清楚,再加上我的語言表達能力很差(高考語文才考75分,總分150分,慚愧),所以只好用代碼來說明問題了。

服務器地址和端口號定義#define SRV_TCP_MAIN_PORT  4000
服務器主連接的端口號#define SRV_TCP_HOLE_PORT  8000
服務器響應客戶端打洞申請的端口號

這兩個端口是固定的,服務器S啟動時就開始偵聽這兩個端口了。

將新客戶端登錄信息發送給所有已登錄的客戶端,但不發送給自己//BOOL SendNewUserLoginNotifyToAll ( LPCTSTR lpszClientIP, UINT nClientPort, DWORD dwID ){ ASSERT ( lpszClientIP && nClientPort > 0 ); g_CSFor_PtrAry_SockClient.Lock(); for ( int i=0; i<g_PtrAry_SockClient.GetSize(); i++ ) {  CSockClient *pSockClient = (CSockClient*)g_PtrAry_SockClient.GetAt(i);  if ( pSockClient && pSockClient->m_bMainConn && pSockClient->m_dwID > 0 && pSockClient->m_dwID != dwID )  {   if ( !pSockClient->SendNewUserLoginNotify ( lpszClientIP, nClientPort, dwID ) )   {    g_CSFor_PtrAry_SockClient.Unlock();    return FALSE;   }  } } g_CSFor_PtrAry_SockClient.Unlock (); return TRUE;}

當有新的客戶端連接到服務器時,服務器負責將該客戶端的信息(IP地址、端口號)發送給其他客戶端。

//// 執行者:客戶端A// 有新客戶端B登錄了,我(客戶端A)連接服務器端口 SRV_TCP_HOLE_PORT ,申請與客戶端B建立直接的TCP連接//BOOL Handle_NewUserLogin ( CSocket &MainSock, t_NewUserLoginPkt *pNewUserLoginPkt ){ printf ( "New user ( %s:%u:%u ) login server\n", pNewUserLoginPkt->szClientIP,  pNewUserLoginPkt->nClientPort, pNewUserLoginPkt->dwID ); BOOL bRet = FALSE; DWORD dwThreadID = 0; t_ReqConnClientPkt ReqConnClientPkt; CSocket Sock; CString csSocketAddress; char szRecvBuffer[NET_BUFFER_SIZE] = {0}; int nRecvBytes = 0; // 創建打洞Socket,連接服務器協助打洞的端口號 SRV_TCP_HOLE_PORT try {  if ( !Sock.Socket () )  {   printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );   goto finished;  }  UINT nOptValue = 1;  if ( !Sock.SetSockOpt ( SO_REUSEADDR, &nOptValue , sizeof(UINT) ) )  {   printf ( "SetSockOpt socket failed : %s\n", hwFormatMessage(GetLastError()) );   goto finished;  }  if ( !Sock.Bind ( 0 ) )  {   printf ( "Bind socket failed : %s\n", hwFormatMessage(GetLastError()) );   goto finished;  }  if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )  {   printf ( "Connect to [%s:%d] failed : %s\n", g_pServerAddess,     SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) );   goto finished;  } } catch ( CException e ) {  char szError[255] = {0};  e.GetErrorMessage( szError, sizeof(szError) );  printf ( "Exception occur, %s\n", szError );  goto finished; } g_pSock_MakeHole = &Sock; ASSERT ( g_nHolePort == 0 ); VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) ); // 創建一個線程來偵聽端口 g_nHolePort 的連接請求 dwThreadID = 0; g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &dwThreadID ); if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE; Sleep ( 3000 ); // 我(客戶端A)向服務器協助打洞的端口號 SRV_TCP_HOLE_PORT 發送申請,希望與新登錄的客戶端B建立連接 // 服務器會將我的打洞用的外部IP和端口號告訴客戶端B ASSERT ( g_WelcomePkt.dwID > 0 ); ReqConnClientPkt.dwInviterID = g_WelcomePkt.dwID; ReqConnClientPkt.dwInvitedID = pNewUserLoginPkt->dwID; if ( Sock.Send ( &ReqConnClientPkt, sizeof(t_ReqConnClientPkt) ) != sizeof(t_ReqConnClientPkt) )  goto finished; // 等待服務器回應,將客戶端B的外部IP地址和端口號告訴我(客戶端A) nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) ); if ( nRecvBytes > 0 ) {  ASSERT ( nRecvBytes == sizeof(t_SrvReqDirectConnectPkt) );  PACKET_TYPE *pePacketType = (PACKET_TYPE*)szRecvBuffer;  ASSERT ( pePacketType && *pePacketType == PACKET_TYPE_TCP_DIRECT_CONNECT );  Sleep ( 1000 );  Handle_SrvReqDirectConnect ( (t_SrvReqDirectConnectPkt*)szRecvBuffer );  printf ( "Handle_SrvReqDirectConnect end\n" ); } // 對方斷開連接了 else {  goto finished; }  bRet = TRUE;finished: g_pSock_MakeHole = NULL; return bRet;}

這里假設客戶端A先啟動,當客戶端B啟動后客戶端A將收到服務器S的新客戶端登錄的通知,并得到客戶端B的公網IP和端口,客戶端A啟動線程連接S的【協助打洞】端口(本地端口號可以用GetSocketName()函數取得,假設為M),請求S協助TCP打洞,然后啟動線程偵聽該本地端口(前面假設的M)上的連接請求,然后等待服務器的回應。

//// 客戶端A請求我(服務器)協助連接客戶端B,這個包應該在打洞Socket中收到//BOOL CSockClient::Handle_ReqConnClientPkt(t_ReqConnClientPkt *pReqConnClientPkt){ ASSERT ( !m_bMainConn ); CSockClient *pSockClient_B = FindSocketClient ( pReqConnClientPkt->dwInvitedID ); if ( !pSockClient_B ) return FALSE; printf ( "%s:%u:%u invite %s:%u:%u connection\n", m_csPeerAddress, m_nPeerPort, m_dwID,  pSockClient_B->m_csPeerAddress, pSockClient_B->m_nPeerPort, pSockClient_B->m_dwID ); // 客戶端A想要和客戶端B建立直接的TCP連接,服務器負責將A的外部IP和端口號告訴給B t_SrvReqMakeHolePkt SrvReqMakeHolePkt; SrvReqMakeHolePkt.dwInviterID = pReqConnClientPkt->dwInviterID; SrvReqMakeHolePkt.dwInviterHoleID = m_dwID; SrvReqMakeHolePkt.dwInvitedID = pReqConnClientPkt->dwInvitedID; STRNCPY_CS ( SrvReqMakeHolePkt.szClientHoleIP, m_csPeerAddress ); SrvReqMakeHolePkt.nClientHolePort = m_nPeerPort; if ( pSockClient_B->SendChunk ( &SrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt), 0 ) != sizeof(t_SrvReqMakeHolePkt) )  return FALSE; // 等待客戶端B打洞完成,完成以后通知客戶端A直接連接客戶端外部IP和端口號 if ( !HANDLE_IS_VALID(m_hEvtWaitClientBHole) )  return FALSE; if ( WaitForSingleObject ( m_hEvtWaitClientBHole, 6000*1000 ) == WAIT_OBJECT_0 ) {  if ( SendChunk ( &m_SrvReqDirectConnectPkt, sizeof(t_SrvReqDirectConnectPkt), 0 )     == sizeof(t_SrvReqDirectConnectPkt) )   return TRUE; } return FALSE;}

服務器S收到客戶端A的協助打洞請求后通知客戶端B,要求客戶端B向客戶端A打洞,即讓客戶端B嘗試與客戶端A的公網IP和端口進行connect。

//// 執行者:客戶端B// 處理服務器要我(客戶端B)向另外一個客戶端(A)打洞,打洞操作在線程中進行。// 先連接服務器協助打洞的端口號 SRV_TCP_HOLE_PORT ,通過服務器告訴客戶端A我(客戶端B)的外部IP地址和端口號,然后啟動線程進行打洞,// 客戶端A在收到這些信息以后會發起對我(客戶端B)的外部IP地址和端口號的連接(這個連接在客戶端B打洞完成以后進行,所以// 客戶端B的NAT不會丟棄這個SYN包,從而連接能建立)//BOOL Handle_SrvReqMakeHole ( CSocket &MainSock, t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt ){ ASSERT ( pSrvReqMakeHolePkt ); // 創建Socket,連接服務器協助打洞的端口號 SRV_TCP_HOLE_PORT,連接建立以后發送一個斷開連接的請求給服務器,然后連接斷開 // 這里連接的目的是讓服務器知道我(客戶端B)的外部IP地址和端口號,以通知客戶端A CSocket Sock; try {  if ( !Sock.Create () )  {   printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );   return FALSE;  }  if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )  {   printf ( "Connect to [%s:%d] failed : %s\n", g_pServerAddess,     SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) );   return FALSE;  } } catch ( CException e ) {  char szError[255] = {0};  e.GetErrorMessage( szError, sizeof(szError) );  printf ( "Exception occur, %s\n", szError );  return FALSE; } CString csSocketAddress; ASSERT ( g_nHolePort == 0 ); VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) ); // 連接服務器協助打洞的端口號 SRV_TCP_HOLE_PORT,發送一個斷開連接的請求,然后將連接斷開,服務器在收到這個包的時候也會將 // 連接斷開 t_ReqSrvDisconnectPkt ReqSrvDisconnectPkt; ReqSrvDisconnectPkt.dwInviterID = pSrvReqMakeHolePkt->dwInvitedID; ReqSrvDisconnectPkt.dwInviterHoleID = pSrvReqMakeHolePkt->dwInviterHoleID; ReqSrvDisconnectPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID; ASSERT ( ReqSrvDisconnectPkt.dwInvitedID == g_WelcomePkt.dwID ); if ( Sock.Send ( &ReqSrvDisconnectPkt, sizeof(t_ReqSrvDisconnectPkt) ) != sizeof(t_ReqSrvDisconnectPkt) )  return FALSE; Sleep ( 100 ); Sock.Close (); // 創建一個線程來向客戶端A的外部IP地址、端口號打洞 t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt_New = new t_SrvReqMakeHolePkt; if ( !pSrvReqMakeHolePkt_New ) return FALSE; memcpy ( pSrvReqMakeHolePkt_New, pSrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt) ); DWORD dwThreadID = 0; g_hThread_MakeHole = ::CreateThread ( NULL, 0, ::ThreadProc_MakeHole,   LPVOID(pSrvReqMakeHolePkt_New), 0, &dwThreadID ); if (!HANDLE_IS_VALID(g_hThread_MakeHole) ) return FALSE; // 創建一個線程來偵聽端口 g_nHolePort 的連接請求 dwThreadID = 0; g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &dwThreadID ); if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE; // 等待打洞和偵聽完成 HANDLE hEvtAry[] = { g_hEvt_ListenFinished, g_hEvt_MakeHoleFinished }; if ( ::WaitForMultipleObjects ( LENGTH(hEvtAry), hEvtAry, TRUE, 30*1000 ) == WAIT_TIMEOUT )  return FALSE; t_HoleListenReadyPkt HoleListenReadyPkt; HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID; HoleListenReadyPkt.dwInviterHoleID = pSrvReqMakeHolePkt->dwInviterHoleID; HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID; if ( MainSock.Send ( &HoleListenReadyPkt, sizeof(t_HoleListenReadyPkt) ) != sizeof(t_HoleListenReadyPkt) ) {  printf ( "Send HoleListenReadyPkt to %s:%u failed : %s\n",   g_WelcomePkt.szClientIP, g_WelcomePkt.nClientPort,   hwFormatMessage(GetLastError()) );  return FALSE; }  return TRUE;}

客戶端B收到服務器S的打洞通知后,先連接S的【協助打洞】端口號(本地端口號可以用GetSocketName()函數取得,假設為X),啟動線程嘗試連接客戶端A的公網IP和端口號,根據路由器不同,連接情況各異,如果運氣好直接連接就成功了,即使連接失敗,但打洞便完成了。同時還要啟動線程在相同的端口(即與S的【協助打洞】端口號建立連接的本地端口號X)上偵聽到來的連接,等待客戶端A直接連接該端口號。

//// 執行者:客戶端A// 服務器要求主動端(客戶端A)直接連接被動端(客戶端B)的外部IP和端口號//BOOL Handle_SrvReqDirectConnect ( t_SrvReqDirectConnectPkt *pSrvReqDirectConnectPkt ){ ASSERT ( pSrvReqDirectConnectPkt ); printf ( "You can connect direct to ( IP:%s  PORT:%d  ID:%u )\n", pSrvReqDirectConnectPkt->szInvitedIP,  pSrvReqDirectConnectPkt->nInvitedPort, pSrvReqDirectConnectPkt->dwInvitedID ); // 直接與客戶端B建立TCP連接,如果連接成功說明TCP打洞已經成功了。 CSocket Sock; try {  if ( !Sock.Socket () )  {   printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );   return FALSE;  }  UINT nOptValue = 1;  if ( !Sock.SetSockOpt ( SO_REUSEADDR, &nOptValue , sizeof(UINT) ) )  {   printf ( "SetSockOpt socket failed : %s\n", hwFormatMessage(GetLastError()) );   return FALSE;  }  if ( !Sock.Bind ( g_nHolePort ) )  {   printf ( "Bind socket failed : %s\n", hwFormatMessage(GetLastError()) );   return FALSE;  }  for ( int ii=0; ii<100; ii++ )  {   if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) == WAIT_OBJECT_0 )    break;   DWORD dwArg = 1;   if ( !Sock.IOCtl ( FIONBIO, &dwArg ) )   {    printf ( "IOCtl failed : %s\n", hwFormatMessage(GetLastError()) );   }   if ( !Sock.Connect ( pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort ) )   {    printf ( "Connect to [%s:%d] failed : %s\n",      pSrvReqDirectConnectPkt->szInvitedIP,      pSrvReqDirectConnectPkt->nInvitedPort,      hwFormatMessage(GetLastError()) );     Sleep (100);   }   else break;  }  if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) != WAIT_OBJECT_0 )  {   if ( HANDLE_IS_VALID ( g_hEvt_ConnectOK ) ) SetEvent ( g_hEvt_ConnectOK );   printf ( "Connect to [%s:%d] successfully !!!\n",    pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort );      // 接收測試數據   printf ( "Receiving data ...\n" );   char szRecvBuffer[NET_BUFFER_SIZE] = {0};   int nRecvBytes = 0;   for ( int i=0; i<1000; i++ )   {    nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) );    if ( nRecvBytes > 0 )    {     printf ( "-->>> Received Data : %s\n", szRecvBuffer );     memset ( szRecvBuffer, 0, sizeof(szRecvBuffer) );     SLEEP_BREAK ( 1 );    }    else    {     SLEEP_BREAK ( 300 );    }   }  } } catch ( CException e ) {  char szError[255] = {0};  e.GetErrorMessage( szError, sizeof(szError) );  printf ( "Exception occur, %s\n", szError );  return FALSE; } return TRUE;}

在客戶端B打洞和偵聽準備好以后,服務器S回復客戶端A,客戶端A便直接與客戶端B的公網IP和端口進行連接,收發數據可以正常進行,為了測試是否真正地直接TCP連接,在數據收發過程中可以將服務器S強行終止,看是否數據收發還正常進行著。

程序執行步驟和方法:

  1. 要準備好環境,如果要真實測試的話需要用2個連到公網上的局域網,1臺具有公網地址的電腦(為了協助我測試,小曹、小妞可費了不少心,我還霸占了他們家的電腦,在此表示感謝)。如果不是這樣的環境,程序執行可能會不正常,因為我暫時未做相同局域網的處理。
  2. 在具有公網地址的電腦上執行“TcpHoleSrv.exe”程序,假設這臺電腦的公網IP地址是“129.208.12.38”。
  3. 在局域網A中的一臺電腦上執行“TcpHoleClt-A.exe 129.208.12.38”
  4. 在局域網B中的一臺電腦上執行“TcpHoleClt-B.exe 129.208.12.38”

程序執行成功后的界面:客戶端出現“Send Data”或者“Received Data”表示穿越NAT的TCP連接已經建立起來,數據收發已經OK。



服務器S


客戶端A


客戶端B

本代碼在Windows XP、一個天威局域網、一個電信局域網、一個電話撥號網絡中測試通過。
由于時間和水平的關系,代碼和文章寫得都不咋的,但愿能起到拋磚引玉的作用。代碼部分只是實現了不同局域網之間的客戶端相互連接的問題,至于相同局域網內的主機或者其中一臺客戶端本身就具有公網IP的問題這里暫時未做考慮(因為那些處理實在太簡單了,比較一下掩碼或者公網IP就能判斷出來的);另外程序的防錯性代碼重用性也做得不好,只是實現了功能,我想



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1750938

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲一区三区电影在线观看| 欧美一区二区三区四区在线观看| 欧美国产亚洲视频| 中日韩视频在线观看| 欧美电影免费观看高清完整版| 国产一区二区三区免费观看| 亚洲欧美一区二区精品久久久| 最新亚洲视频| 亚洲国产精品一区制服丝袜 | 国产精品免费看久久久香蕉| 精品电影一区| 亚洲欧美国产高清| 亚洲在线观看免费视频| 国产精品久久久久久模特| 精品99一区二区| 亚洲人精品午夜| 久久永久免费| 久久成人18免费网站| 国产精品视频内| 午夜精品一区二区三区四区| 亚洲丝袜av一区| 国产精品视频导航| 新狼窝色av性久久久久久| 亚洲午夜精品视频| 国产欧美日韩在线播放| 欧美在线免费观看| 欧美一区二区三区男人的天堂| 国产精品影音先锋| 久久久中精品2020中文| 久久一二三区| 一本一本久久| 亚洲免费影视第一页| 国产日韩一区二区三区在线播放| 久久久激情视频| 久久精品国产91精品亚洲| 亚洲国产精品悠悠久久琪琪| 亚洲大片在线观看| 欧美天堂亚洲电影院在线播放| 亚洲欧美激情视频| 欧美在线亚洲综合一区| 亚洲动漫精品| 日韩亚洲在线观看| 狠狠色狠狠色综合日日五| 亚洲国产精品成人一区二区| 国产精品国产精品国产专区不蜜| 欧美在线视频播放| 欧美刺激午夜性久久久久久久| 亚洲少妇诱惑| 亚洲欧洲一区| 国产精品专区第二| 亚洲国产精品黑人久久久| 国产酒店精品激情| 亚洲国产精品一区二区第一页 | 在线成人激情黄色| 夜夜嗨av一区二区三区| 在线观看不卡| 一区二区三欧美| 在线观看精品一区| 亚洲欧美日韩国产精品| 亚洲激情女人| 久久av免费一区| 亚洲一区欧美激情| 国产亚洲网站| 亚洲精品中文字幕女同| 亚洲一区二区三区激情| 亚洲精品网站在线播放gif| 午夜精品久久一牛影视| 在线亚洲精品福利网址导航| 久久久人成影片一区二区三区 | 精品51国产黑色丝袜高跟鞋| 亚洲精品综合在线| 亚洲电影有码| 欧美资源在线观看| 午夜老司机精品| 欧美精品九九| 欧美黄色aaaa| 亚洲高清精品中出| 久久精品日产第一区二区| 欧美一区二区三区在线免费观看| 欧美日韩一区国产| 亚洲欧洲视频| 亚洲精品五月天| 欧美成人按摩| 亚洲成人直播| 亚洲精品国偷自产在线99热| 狼人社综合社区| 欧美a一区二区| 亚洲国产成人精品女人久久久| 久久久久9999亚洲精品| 久久精品国产第一区二区三区| 国产精品一区久久久| 亚洲夜间福利| 久久国产精品免费一区| 国产性色一区二区| 久久精品国产亚洲5555| 久久天天躁狠狠躁夜夜av| 国产午夜精品久久久久久免费视| 亚洲一区日韩在线| 久久av一区| 黄色日韩精品| 老鸭窝毛片一区二区三区| 欧美成人情趣视频| 最近看过的日韩成人| 欧美精品一区二区在线观看| 99这里只有久久精品视频| 午夜精品久久久久久99热| 国产精品视频一区二区高潮| 午夜精品久久久久久久99樱桃| 久久久之久亚州精品露出| 亚洲第一精品夜夜躁人人爽| 欧美成人激情在线| 一区二区三欧美| 久久精品日韩| 最新国产拍偷乱拍精品| 欧美日韩一区成人| 欧美在线啊v一区| 亚洲激情影视| 午夜电影亚洲| 亚洲国产高清视频| 国产精品草莓在线免费观看| 亚洲欧美一区二区三区极速播放| 另类成人小视频在线| 一区二区欧美在线观看| 国产欧美一区二区精品仙草咪| 久久久久综合网| 亚洲精品一区中文| 久久不射中文字幕| 亚洲裸体在线观看| 西西裸体人体做爰大胆久久久| 亚洲电影免费观看高清完整版在线| 欧美成人午夜激情在线| 亚洲视频你懂的| 老司机67194精品线观看| 99精品视频一区二区三区| 国产色产综合产在线视频| 欧美国产精品| 久久成人久久爱| 一区二区国产日产| 欧美chengren| 欧美伊人久久久久久午夜久久久久 | 国产欧美视频一区二区| 久热精品在线| 亚洲网站啪啪| 亚洲国产精品电影| 久久久99久久精品女同性| 日韩一二三区视频| 国产日韩亚洲欧美| 国产精品高潮粉嫩av| 美女视频网站黄色亚洲| 亚洲免费综合| 亚洲免费观看高清完整版在线观看熊| 久久精品久久99精品久久| 亚洲一区在线观看视频| 亚洲肉体裸体xxxx137| 国产亚洲一区二区精品| 欧美午夜www高清视频| 久久综合狠狠综合久久综青草| 亚洲欧美日韩一区在线观看| 日韩视频不卡中文| 亚洲国产欧美在线| 免费看av成人| 久久夜色精品国产噜噜av| 亚洲免费视频网站| 亚洲视频观看| 99综合电影在线视频| 亚洲人成人一区二区三区| 在线播放日韩专区| 尤物精品在线| 亚洲第一久久影院| 亚洲电影毛片| 亚洲国产视频a| 亚洲第一福利社区| 亚洲国产天堂久久国产91| 亚洲国产日韩在线| 亚洲精品欧美专区| 日韩视频中文字幕| 一本一本久久a久久精品综合麻豆 一本一本久久a久久精品牛牛影视 | 亚洲综合第一页| 亚洲视频精选在线| 中文一区二区| 亚洲一区二区三区在线播放| 在线视频一区二区| 亚洲欧美精品| 午夜精品久久久久久99热| 欧美夜福利tv在线| 久久久久国内| 欧美激情第二页| 亚洲精品久久久一区二区三区| 亚洲精品国产精品国自产观看浪潮| 亚洲福利视频网| 亚洲精品色图| 亚洲在线视频一区| 欧美一区二区精美| 久久国产直播| 欧美成人一区二区三区在线观看 | 亚洲国产一区二区a毛片| 亚洲国产成人av在线| 亚洲福利视频一区| 亚洲视频日本| 久久精品国产欧美激情|