catalogue
<span style="color: #800080;">1</span><span style="color: #000000;">. NAT概念</span>
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)
<span style="color: #800080;">1</span><span style="color: #000000;">. Full Cone: 这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口,然后外网的任何发到这个打开的端口的UDP数据报都可以到达A。不管是不是C发过来的(NAT源端口映射)</span>
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类
<span style="color: #800080;">1</span><span style="color: #000000;">. 基本的NAT: 一个私有网络(域)中的节点中只有很少的节点需要与外网连接。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去(基本的NAT会改变IP包中的原IP地址,但是不会改变IP包中的端口)</span>
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类型,假设如下场景
<span style="color: #800080;">1</span><span style="color: #000000;">. UAC(B): UAC的IP为IPB</span>
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信息和文件信息两部分
<span style="color: #800080;">1</span><span style="color: #000000;">. Tracker信息主要是BT下载中需要用到的Tracker服务器(中间人服务器)的地址,以及针对Tracker服务器的设置</span>
2. 文件信息是根据对目标文件的计算生成的计算结果根据BitTorrent协议内的B编码规则进行编码,它的主要原理是需要把提供下载的文件虚拟分成大小相等的块,块大小必须为2k的整数次方(由于是虚拟分块,硬盘上并不产生各个块文件),并把每个块的索引信息和Hash验证码写入.torrent文件中
3. 所以,.torrent文件就是被下载文件的“索引“
2. 种子文件结构
一个种子文件,通常是以.torrent后缀结尾。BitTorrent协议规定,torrent文件本身,内容必须是utf8编码格式,并且其中的字段结构采用bencoding编码格式
Torrent种子文件由两部分组成:announce(tracker url)和文件信息,该种子文件的一部分如下,根据bencoding编码格式,把这段字符解码还原后
announce:http:<span style="color: #008000;">//</span><span style="color: #008000;">www.chinahdtv.org/announce.php?passkey=6e7a1c7ca4164d87e9b0e00ec63aa749</span>
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格式的文件,包含如下组成部分
<span style="color: #800080;">1</span><span style="color: #000000;">. tracker地址,这里就是announce后面的url</span>
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:<span style="color: #008000;">//</span><span style="color: #008000;">www.cnblogs.com/LittleHann/p/3837839.html</span>
4. BitTorrent下载
下载者要下载文件内容,需要先得到相应的.torrent文件,然后使用BT客户端软件进行下载(读取.torrent文件中的索引)
<span style="color: #800080;">1</span><span style="color: #000000;">. 下载时,BT客户端首先解析.torrent文件得到Tracker地址,然后连接Tracker服务器</span>
2. Tracker服务器回应下载者的请求,提供下载者其他下载者(包括发布者)的IP(相当于打洞过程)
3. 下载者再连接其他下载者
4. 根据.torrent文件,两者分别对方告知自己已经有的块,然后交换对方没有的数据
5. 此时不需要其他服务器参与,分散了单个线路上的数据流量,因此减轻了服务器负担(打洞完成后,不再需要中间人服务器的参与,通信双方直接进行点对点通信)
6. 下载者每得到一个块,需要算出下载块的Hash验证码与.torrent文件中的对比,如果一样则说明块正确,不一样则需要重新下载这个块,这种规定是为了解决下载内容准确性的问题
一般的HTTP/FTP下载,发布文件仅在某个或某几个服务器,下载的人太多,服务器的带宽很易不胜负荷,变得很慢,而BitTorrent协议下载的特点是,下载的人越多,提供的带宽也越多,种子也会越来越多,下载速度就越快
Relevant Link:
http:<span style="color: #008000;">//</span><span style="color: #008000;">www.2cto.com/net/200506/5494.html</span>
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软件的流程
<span style="color: #800080;">1</span>. 首先,Client A登录服务器,NAT A为这次的Session分配了一个端口60000,那么Server S收到的Client A的地址是202.<span style="color: #800080;">187.45</span>.<span style="color: #800080;">3</span>:<span style="color: #800080;">60000</span><span style="color: #000000;">,这就是Client A的外网地址了</span>
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打洞,需要明白的是
<span style="color: #800080;">1</span><span style="color: #000000;">. 在NAT模式下,内网的IP是不能被外网直接正向访问到的,只能又内网IP向外网IP主动发起连接,一旦连接成功建立起来,后续的交互通信是完全正常的</span>
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/<span style="color: #000000;">python</span>
#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/<span style="color: #000000;">python</span>
#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:<span style="color: #008000;">//</span><span style="color: #008000;">www.cnblogs.com/yrh2847189/archive/2007/06/20/790013.html</span>
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:<span style="color: #008000;">//</span><span style="color: #008000;">github.com/huangyingcai/p2p</span>
5. ZeroNet P2P
ZeroNet是一个利用比特币加密和BT技术提供不受审查的网络与通信的BT平台,ZeroNet网络功能已经得到完整的种子的支持和加密连接,保证用户通信和文件共享的安全。使用ZeroNet,可以实现匿名上网,可以在任意一台机器上搭建网站,但即机器关闭,网站依然在全球存在,别人无法关闭这个网站
ZeroNet是一个去中心化的类似于Internet的网络,由Python制作,完全开源。网站由特殊的”ZeroNet URL”可以被使用一般的浏览器通过ZeroNet程序浏览,就像访问本地主机一样。ZeroNet默认并”不”匿名,但是用户可以通过内置的Tor功能进行匿名化。ZeroNet使用Bitcoin加密算法及BitTorrent网络
0x1: 优势
<span style="color: #800080;">1</span><span style="color: #000000;">. 防DMCA Take down</span>
2. 基于p2p原理,只要建好并有人浏览过,即使服务器,网站依然在全球存在
3. 基于p2p原理,支付内网穿透
4. 基于比特币原理,账号很安全
5. 不需要域名,任何人访问都使用http://127.0.0.1:43110/字符串
Relevant Link:
http:<span style="color: #008000;">//</span><span style="color: #008000;">www.williamlong.info/archives/4574.html</span>
https://github.com/HelloZeroNet/ZeroNet