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