UDP打洞、P2P組網方式研究


catalogue

1. NAT概念
2. P2P概念
3. UDP打洞
4. P2P DEMO
5. ZeroNet P2P

 

1. NAT概念

在STUN協議中,根據內部終端的地址(LocalIP:LocalPort)到NAT出口的公網地址(PublicIP:PublicPort)的影射方式,把NAT分為四種類型(rfc3489: http://www.ietf.org/rfc/rfc3489.txt)

1. Full Cone: 這種NAT內部的機器A連接過外網機器C后,NAT會打開一個端口,然后外網的任何發到這個打開的端口的UDP數據報都可以到達A。不管是不是C發過來的(NAT源端口映射)
2. Restricted Cone: 這種NAT內部的機器A連接過外網的機器C后,NAT打開一個端口,然后C可以用任何端口和A通信,其他的外網機器不行(目的IP映射) 
3. Port Restricted Cone: 這種NAT內部的機器A連接過外網的機器C后,NAT打開一個端口,然后C可以用原來的端口和A通信,其他的外網機器不行(NAT目的端口映射)
4. Symmetic: 對於這種NAT.連接不同的外部目標。原來NAT打開的端口會變化,而Cone NAT不會,雖然可以用端口猜測,但是成功的概率很小 

或者分為2類

1. 基本的NAT: 一個私有網絡(域)中的節點中只有很少的節點需要與外網連接。那么這個子網中其實只有少數的節點需要全球唯一的IP地址,其他的節點的IP地址應該是可以重用的。因此,基本的NAT實現的功能很簡單,在子網內使用一個保留的IP子網段,這些IP對外是不可見的。子網內只有少數一些IP地址可以對應到真正全球唯一的IP地址。如果這些節點需要訪問外部網絡,那么基本NAT就負責將這個節點的子網內IP轉化為一個全球唯一的IP然后發送出去(基本的NAT會改變IP包中的原IP地址,但是不會改變IP包中的端口)

2. NAPT(Network Address/Port Translator): NAPT不但會改變經過這個NAT設備的IP數據報的IP地址,還會改變IP數據報的TCP/UDP端口
    1) 如果Client A想向Client B發送信息,那么Client A發送命令給Server S,請求Server S命令Client B向Client A方向打洞
    2) 如果Client B想向Client A發送信息,那么Client B發送命令給Server S,請求Server S命令Client A向Client B方向打洞
總體來說,P2P(NAT環境下)的通信都是被動反向的,即誰想向誰發送數據,需要通知對方向自己"打洞"

 

2.  P2P概念

P2P是peer-to-peer的縮寫,peer在英語里有"(地位、能力等)同等者"、"同事"和"伙伴"等意義。這樣一來,P2P也就可以理解為"伙伴對伙伴"的意思,或稱為對等聯網,簡單的說,P2P直接將人們聯系起來,讓人們通過互聯網直接交互。P2P使得網絡上的溝通變得容易、更直接共享和交互,真正地消除中間商。P2P就是人可以直接連接到其他用戶的計算機、交換文件,而不是像過去那樣連接到服務器去瀏覽與下載。P2P另一個重要特點是改變互聯網現在的以大網站為中心的狀態、重返“非中心化”,並把權力交還給用戶
事實上,網絡上現有的許多服務可以歸入P2P的行列。即時訊息系統譬如ICQ、AOL Instant Messenger、Yahoo Pager、微軟的MSN Messenger以及國內的OICQ是最流行的P2P應用

0x1: 普通的直連式P2P實現

0x2: STUN方式的P2P實現

STUN是RFC3489規定的一種NAT穿透方式,STUN的探測過程需要有一個公網IP的STUN server,在NAT后面的UAC必須和此server配合,互相之間發送若干個UDP數據包。UDP包中包含有UAC需要了解的信息,比如NAT外網IP,PORT等等。UAC通過是否得到這個UDP包和包中的數據判斷自己的NAT類型,假設如下場景

1. UAC(B): UAC的IP為IPB
2. NAT(A): NAT的IP為IPA
3. SERVER(C): SERVER的IP為IPC1 、IPC2
//服務器C有兩個IP 

NAT的探測過程

0x3: P2P文件傳輸協議之BitTorrent協議

Bittorrent與其他傳統P2P軟件如Gnutella,Fasttrack不同,Bittorrent只是一個純粹的文件下載協議,並提供搜索功能,所以往往資源的獲取要跟其他一些應用結合起來,比如說發布Bittorrent種子信息的網站

1. Bittorrent工作原理

Bittorrent的工作原理其實很簡單,他就是將一份數據分隔成256K大小的數據分組,並在Bittorrent 網絡中一群用戶相互協作完成這些數據的分發,用戶參與數據分發的信息已文件的形式存儲,一般可以通過web網站獲取這些信息但是實際數據傳輸依靠的不是Http協議,而是由專門的P2P協議來完成,這些對於用戶都是透明的
普通的HTTP/FTP下載使用TCP/IP協議,BitTorrent協議是架構於TCP/IP協議之上的一個P2P文件傳輸協議,處於TCP/IP結構的應用層。 BitTorrent協議本身也包含了很多具體的內容協議和擴展協議,並在不斷擴充中
根據BitTorrent協議,文件發布者會根據要發布的文件生成提供一個.torrent文件,即種子文件,也簡稱為“種子"。 .torrent文件本質上是文本文件,包含Tracker信息和文件信息兩部分

1. Tracker信息主要是BT下載中需要用到的Tracker服務器(中間人服務器)的地址,以及針對Tracker服務器的設置
2. 文件信息是根據對目標文件的計算生成的計算結果根據BitTorrent協議內的B編碼規則進行編碼,它的主要原理是需要把提供下載的文件虛擬分成大小相等的塊,塊大小必須為2k的整數次方(由於是虛擬分塊,硬盤上並不產生各個塊文件),並把每個塊的索引信息和Hash驗證碼寫入.torrent文件中
3. 所以,.torrent文件就是被下載文件的"索引"

2. 種子文件結構

一個種子文件,通常是以.torrent后綴結尾。BitTorrent協議規定,torrent文件本身,內容必須是utf8編碼格式,並且其中的字段結構采用bencoding編碼格式
Torrent種子文件由兩部分組成:announce(tracker url)和文件信息,該種子文件的一部分如下,根據bencoding編碼格式,把這段字符解碼還原后

announce:http://www.chinahdtv.org/announce.php?passkey=6e7a1c7ca4164d87e9b0e00ec63aa749
created by:uTorrent/2040
creation date:1369699038
encoding:UTF-8
info:
{files:[
{length:158784,path:[Iron.Man.3.2013.HDSCR.ULTRA.EDiTiON.720p.x264.chn.srt]}, {length:107117,path:[Iron.Man.3.2013.HDSCR.ULTRA.EDiTiON.720p.x264.chn1.srt]}, {length:93644,path:[Iron.Man.3.2013.HDSCR.ULTRA.EDiTiON.720p.x264.chn2.srt]},
{length:4272200020,path:[Iron.Man.3.2013.HDSCR.ULTRA.EDiTiON.720p.x264.mkv]}],
name:鋼鐵俠3.Iron.Man.3.2013.HDSCR.ULTRA.EDiTiON.720p.x264,
piece length:4194304,
pieces:P1,P2,P3...P1019
private:1
source:[hd.gg] CNHD ChinaHDTV
}

一個torrent種子文件有點類似於XML格式的文件,包含如下組成部分

1. tracker地址,這里就是announce后面的url
2. 種子創建軟件及其版本號,這里是uTorrent軟件創建的,版本號為2040
3. 創建日期,這里是1369699038,這個數字顯示的是從UTC 1970-1-1 00:00:00到到現在所經歷的秒數 
4. 編碼格式,這里是UTF-8(codepage=936)
5. info區,這里指定的是該種子有幾個文件,文件有多長,目錄結構,以及目錄和文件的名字 
6. Name字段,指定頂層目錄名字
7. 每個段的大小,Bittorrent協議是把一個文件分成很多個小段,然后分段下載的,這個地方就是指定每個段的大小,單位是字節,這里每個段的大小大約為4MB(4194304)
8. 段哈希值,就是整個種子中,每個段的SHA1哈希值拼在一起,每個段的哈希長度是固定的,20個字符,所以pieces后面跟的那個數字20380其實是段數量*20,如果你用20380除以20,就會發現這個種子段數量為1019,乘上前面的段大小,這個種子大概有4GB大小,也就是說你把這個種子下載完后,占硬盤4GB空間
9. private值,這個屬性主要顯示這個種子是私有的,還是公有的。一般那些各大PT站就是私有的。私有的種子會禁掉DHT(distributed hash table),因為如果你的client開這個功能,那就會跳過tracker來和其他peer進行數據交換,在很多PT內站(CHDbits,CMCT,CNHD)把這種行為稱為作弊,會直接ban掉你在PT站上的帳號 
10. 源,顯示該種子的來源,這里是CNHD

以上的每個屬性並不是必須的,有的屬性屬於BitTorrent Enhancement Proposals (BEPs),就是BitTorrent協議的擴展,雖然不屬於正式標准的一部分,但是很多客戶端都支持這種格式

3. BitTorrent通信流程與網絡包結構

http://www.cnblogs.com/LittleHann/p/3837839.html

4. BitTorrent下載

下載者要下載文件內容,需要先得到相應的.torrent文件,然后使用BT客戶端軟件進行下載(讀取.torrent文件中的索引)

1. 下載時,BT客戶端首先解析.torrent文件得到Tracker地址,然后連接Tracker服務器
2. Tracker服務器回應下載者的請求,提供下載者其他下載者(包括發布者)的IP(相當於打洞過程)
3. 下載者再連接其他下載者
4. 根據.torrent文件,兩者分別對方告知自己已經有的塊,然后交換對方沒有的數據
5. 此時不需要其他服務器參與,分散了單個線路上的數據流量,因此減輕了服務器負擔(打洞完成后,不再需要中間人服務器的參與,通信雙方直接進行點對點通信)
6. 下載者每得到一個塊,需要算出下載塊的Hash驗證碼與.torrent文件中的對比,如果一樣則說明塊正確,不一樣則需要重新下載這個塊,這種規定是為了解決下載內容准確性的問題 

一般的HTTP/FTP下載,發布文件僅在某個或某幾個服務器,下載的人太多,服務器的帶寬很易不勝負荷,變得很慢,而BitTorrent協議下載的特點是,下載的人越多,提供的帶寬也越多,種子也會越來越多,下載速度就越快

Relevant Link:

http://www.2cto.com/net/200506/5494.html
http://www.cppblog.com/peakflys/archive/2013/01/25/197562.html
http://blog.chinaunix.net/uid-11572501-id-2868679.html
https://github.com/Martiusweb/p2p
http://network.51cto.com/art/201006/207932.htm
http://blog.csdn.net/wengpingbo/article/details/9174363
https://github.com/axeliux/P2P

 

3. UDP打洞

UDP打洞用於在兩個NAT內網之間進行網絡連接

0x1:  打洞基本概念

我們來看看一個P2P軟件的流程

1. 首先,Client A登錄服務器,NAT A為這次的Session分配了一個端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,這就是Client A的外網地址了
2. 同樣,Client B登錄Server S,NAT B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000 
3. 此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發送信息給Client B,那么他可以從Server S那兒獲得B的公網地址187.34.1.56:40000,但是Client A不能直接向這個地址發送信息,因為如果這樣發送信息,NAT B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數NAT都會執行丟棄動作)。現在我們需要的是在NAT B上打一個方向為202.187.45.3(即Client A的外網地址)的洞,那么Client A發送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰Server S來發出(Server S只是作為一個中轉代理)
4. 總結一下這個過程:如果Client A想向Client B發送信息,那么Client A發送命令給Server S,請求Server S命令Client B向Client A方向打洞。然后Client A就可以通過Client B的外網地址與Client B通信了

理解UDP NAT打洞,需要明白的是

1. 在NAT模式下,內網的IP是不能被外網直接正向訪問到的,只能又內網IP向外網IP主動發起連接,一旦連接成功建立起來,后續的交互通信是完全正常的
2. 而P2P要面對的問題就是兩個IP都在NAT內網中,誰也無法率先發起連接(NAT會拒絕"不請自來"的外網連接),所以需要事先在"中間人(Server S)"上進行登記,登記的內容為對應的NAT公網IP:PORT
3. 如果某個NAT內網IP需要向另一個NAT內網IP進行通信,需要"通知"中間人Server S,由它轉發來告知目標NAT IP主動向自己主動發起連接,這謂之"打洞""打洞"完成后,當前NAT就記錄了當前NAT IP和目標NAT外網IP的的會話記錄了,而此時對於對方的NAT來說,同樣也記錄一個會話,解決了"雞生蛋,蛋生雞"的問題后,此后,兩個NAT內網IP就可以互相開始正常通信了

0x2: Server端

#!/usr/bin/python
#coding:utf-8
import socket, sys, SocketServer, threading, thread, time 

SERVER_PORT = 1234
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
sock.bind(('', SERVER_PORT)) 
user_list = [] 

def server_handle(): 
    while True: 
        cli_date, cli_pub_add = sock.recvfrom(8192) 
        now_user = [] 
        headder = [] 
        cli_str = {} 
        headder = cli_date.split('\t') 
    for one_line in headder: 
        str = {} 
        str = one_line 
        args = str.split(':') 
        cli_str[args[0]] = args[1] 
    if cli_str['type'] == 'login' : 
        del cli_str['type'] 
        now_user = cli_str 
        now_user['cli_pub_ip'] = cli_pub_add[0] 
        now_user['cli_pub_port'] = cli_pub_add[1] 
        user_list.append(now_user) 
        toclient = 'info#%s login in successful , the info from server'%now_user['user_name'] 
        sock.sendto(toclient,cli_pub_add) 
        print'-'*100
        print"%s 已經登錄,公網IP:%s 端口:%d\n"%(now_user['user_name'],now_user['cli_pub_ip'],now_user['cli_pub_port']) 
        print"以下是已經登錄的用戶列表"
        for one_user in user_list: 
            print'用戶名:%s 公網ip:%s 公網端口:%s 私網ip:%s 私網端口:%s'%(one_user['user_name'],one_user['cli_pub_ip'],one_user['cli_pub_port'],one_user['private_ip'],one_user['private_port']) 
    elif cli_str['type'] == 'alive': 
        pass
    elif cli_str['type'] == 'logout' : 
        pass
    elif cli_str['type'] == 'getalluser' : 
        print'-'*100
        for one_user in user_list : 
            toclient = 'getalluser#username:%s pub_ip:%s pub_port:%s pri_ip:%s pri_port:%s'%(one_user['user_name'],one_user['cli_pub_ip'],one_user['cli_pub_port'],one_user['private_ip'],one_user['private_port']) 
            sock.sendto(toclient,cli_pub_add) 

if __name__ == '__main__': 
    thread.start_new_thread(server_handle, ()) 
    
print'服務器進程已啟動,等待客戶連接'
while True: 
    for one_user in user_list: 
        toclient = 'keepconnect#111'
        sock.sendto(toclient,(one_user['cli_pub_ip'],one_user['cli_pub_port']))  
        time.sleep(1) 

0x3: Client

#!/usr/bin/python
#coding:utf-8
import socket, SocketServer, threading, thread, time 
CLIENT_PORT = 4321
SERVER_IP = "114.55.36.222"
SERVER_PORT = 1234
user_list = {} 
local_ip = socket.gethostbyname(socket.gethostname()) 
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 

def server_handle(): 
    print'客戶端線程已經啟動 , 等待其它客戶端連接'
    while True: 
        data, addr = sock.recvfrom(8192) 
        data_str = data.split('#') 
        data_type = data_str[0] 
        data_info = data_str[1] 
        if data_type == 'info' : 
            del data_str[0] 
            print data_info      
        if data_type == 'getalluser' : 
            data_sp = data_info.split(' ') 
            user_name = data_sp[0].split(':')[1] 
            del data_sp[0] 
            user_list[user_name] = {} 
            for one_line in data_sp: 
                arg = one_line.split(':') 
                user_list[user_name][arg[0]] = arg[1] 
        if data_type == 'echo' : 
            print data_info 
        if data_type == 'keepconnect': 
            messeg = 'type:alive'
            sock.sendto(messeg, addr) 

if __name__ == '__main__': 
    thread.start_new_thread(server_handle, ()) 
    time.sleep(0.1) 
    cmd = raw_input('輸入指令>>') 
    while True: 
        args = cmd.split(' ') 
        if args[0] == 'login': 
            user_name = args[1] 
            local_uname = args[1] 
            address = "private_ip:%s private_port:%d" % (local_ip, CLIENT_PORT) 
            headder = "type:login\tuser_name:%s\tprivate_ip:%s\tprivate_port:%d" % (user_name,local_ip,CLIENT_PORT) 
            sock.sendto(headder, (SERVER_IP, SERVER_PORT)) 
        elif args[0] == 'getalluser': 
            headder = "type:getalluser\tuser_name:al"
            sock.sendto(headder,(SERVER_IP,SERVER_PORT)) 
            print '獲取用戶列表中...'
            time.sleep(1) 
            for one_user in user_list: 
                print'username:%s pub_ip:%s pub_port:%s pri_ip:%s pri_port:%s'%(one_user,user_list[one_user]['pub_ip'],user_list[one_user]['pub_port'],user_list[one_user]['pri_ip'],user_list[one_user]['pri_port']) 
        elif args[0] == 'connect': 
            user_name = args[1] 
            to_user_ip = user_list[user_name]['pub_ip'] 
            to_user_port = int(user_list[user_name]['pub_port']) 
        elif args[0] =='echo': 
            m = ' '.join(args[1:]) 
            messeg = 'echo#from %s:%s'%(local_uname,m) 
            sock.sendto(messeg, (to_user_ip, to_user_port)) 
        time.sleep(0.1)  
        cmd = raw_input('輸入指令>>') 

0x4: 測試過程

1. 登錄(NAT內網機器向Server S注冊)

2. NAT內網IP獲取Server S中注冊用戶,准備開始打洞

3. 用戶A(51_10)需要向用戶B(190_203)建立連接

完成打洞的建立和正常通信

Relevant Link:

http://www.cnblogs.com/yrh2847189/archive/2007/06/20/790013.html
http://lustlost.blog.51cto.com/2600869/1177494

 

4. P2P DEMO

A peer-to-peer file sharing server written in C, and client written in Java. Developed in collaboration with Justin Hill (https://github.com/justindhill).
p2p utilizes TCP server (https://github.com/mdlayher/tcpd) as well as the Apache Commons Codec (http://commons.apache.org/codec/) in order to facilitate the C server component
p2p uses a centralized directory server approach. Clients connect to the central server in order to retrieve a list of files which exist among peers in the network. Once a client requests to download a file, the connection is negotiated between peers, and the client can begin to download the file.

Relevant Link:

https://github.com/huangyingcai/p2p

 

5. ZeroNet P2P

ZeroNet是一個利用比特幣加密和BT技術提供不受審查的網絡與通信的BT平台,ZeroNet網絡功能已經得到完整的種子的支持和加密連接,保證用戶通信和文件共享的安全。使用ZeroNet,可以實現匿名上網,可以在任意一台機器上搭建網站,但即機器關閉,網站依然在全球存在,別人無法關閉這個網站
ZeroNet是一個去中心化的類似於Internet的網絡,由Python制作,完全開源。網站由特殊的"ZeroNet URL"可以被使用一般的瀏覽器通過ZeroNet程序瀏覽,就像訪問本地主機一樣。ZeroNet默認並"不"匿名,但是用戶可以通過內置的Tor功能進行匿名化。ZeroNet使用Bitcoin加密算法及BitTorrent網絡

0x1: 優勢

1. 防DMCA Take down
2. 基於p2p原理,只要建好並有人瀏覽過,即使服務器,網站依然在全球存在
3. 基於p2p原理,支付內網穿透
4. 基於比特幣原理,賬號很安全
5. 不需要域名,任何人訪問都使用http://127.0.0.1:43110/字符串

Relevant Link:

http://www.williamlong.info/archives/4574.html
https://github.com/HelloZeroNet/ZeroNet

 

Copyright (c) 2016 LittleHann All rights reserved

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM