Giter Club home page Giter Club logo

pypunchp2p's Introduction

PyPunchP2P

THIS PROJECT IS FOR STUDYING AND VERIFICATION, DON'T USE IT IN PRODUCTION.

Python p2p chat client/server with built-in NAT traversal (UDP hole punching).
I've written an article about the detailed implementation (in Chinese).

Based on
koenbollen's gist
pystun
Peer-to-Peer Communication Across Network Address Translators

Python edition: py2.6+ but no Python 3 support
Platform: Linux/Windows

Usage

Suppose you run server.py on a VPS with ip 1.2.3.4, listening on port 5678

$ server.py 5678

On client A and client B (run this on both clients):

$ client.py 1.2.3.4 5678 100  

The number 100 is used to match clients, you can choose any number you like but only clients with the same number will be linked by server. If two clients get linked, two people can chat by typing in terminal, and once you hit <ENTER> your partner will see your message in his terminal.
Encoding is a known issue since I didn't pay much effort on making this tool perfect, but as long as you type English it will be fine.

Test Mode

You could do simulation testing by specifying a fourth parameter of client.py, it will assume that your client is behind a specific type of NAT device.

Here are the corresponding NAT type and number:

FullCone         0  
RestrictNAT      1  
RestrictPortNAT  2  
SymmetricNAT     3   

So you might run

$ client.py 1.2.3.4 5678 100 1

pretending your client is behind RestrictNAT.
You can test the relay server functionality by making 3 as the forth parameter, since if one client is behind symmetric NAT, there will be no direct connection but server forwaring.

License

MIT

pypunchp2p's People

Contributors

laike9m avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pypunchp2p's Issues

"Always : "UDP punching package xx sent "

Hello!

I try to make a connection with UDP Hole punching. I tried differents python script before yours (more simple : https://github.com/stylesuxx/udp-hole-punching). Every time, it fails when the two peers are linked then try to communicate. I begin to think that the problem come from my network. I try from : 2 computer behind the same NAT, Same computer behind same NAT, 1 computer on 4G connection and 1 computer behind home NAT. Every time it fail. The Server is running on a VPS with public static address, so this part is OK. This problem come when the peers try to communicate.

I'm stuck in this since 2 days now ... do you have some clue?

PS : One of my computer is MAC OS, i don't know if it can be a problem (you say platform : linux/windows).

Thanks.

服务端在固定IP公网 前端在防火后

我只能收到 如下
('NAT Type:', 'Symmetric NAT')
('External IP:' , '157.122.146.7')
('External Port:', 54320)

客户端是否不断尝试连接, 还是已经没希望连接起来 。

谢谢

测试有问题还请帮解答?

问题1:
一台独立外网ip的服务器,启动server.py 5678
测试机A:RestrictNAT
测试机B:SymmetricNAT
测试机C:RestrictNAT
*测试机A和测试B可以连通,走的是转发模式
*测试A和测试机C 一直在 UDP punching package 0 sent 循环中,无法打通
问题2:
问题代码1:def start_working_threads(send, recv, event=None, *args, **kwargs) 参数报错, 把event=None 改成events=None
问题代码1:ts = Thread(target=send, args=args, kwargs=kwarg) 参数报错,把删除参数 kwargs

stun.py 我改到python3.6下面了,这个文件可以提交上来不了?

stun.py 我改到python3.6下面了,这个文件可以提交上来不了?
`# coding:utf-8

this script come from https://pypi.python.org/pypi/pystun

import random
import socket
import binascii
import logging

version = "0.0.4"

log = logging.getLogger("pystun")

def enable_logging():
logging.basicConfig()
log.setLevel(logging.DEBUG)

stun_servers_list = (
"stun.ekiga.net",
'stunserver.org',
'stun.ideasip.com',
'stun.softjoys.com',
'stun.voipbuster.com',
)

#stun attributes
MappedAddress = '0001'
ResponseAddress = '0002'
ChangeRequest = '0003'
SourceAddress = '0004'
ChangedAddress = '0005'
Username = '0006'
Password = '0007'
MessageIntegrity = '0008'
ErrorCode = '0009'
UnknownAttribute = '000A'
ReflectedFrom = '000B'
XorOnly = '0021'
XorMappedAddress = '8020'
ServerName = '8022'
SecondaryAddress = '8050' # Non standard extention

#types for a stun message
BindRequestMsg = '0001'
BindResponseMsg = '0101'
BindErrorResponseMsg = '0111'
SharedSecretRequestMsg = '0002'
SharedSecretResponseMsg = '0102'
SharedSecretErrorResponseMsg = '0112'

dictAttrToVal = {'MappedAddress': MappedAddress,
'ResponseAddress': ResponseAddress,
'ChangeRequest': ChangeRequest,
'SourceAddress': SourceAddress,
'ChangedAddress': ChangedAddress,
'Username': Username,
'Password': Password,
'MessageIntegrity': MessageIntegrity,
'ErrorCode': ErrorCode,
'UnknownAttribute': UnknownAttribute,
'ReflectedFrom': ReflectedFrom,
'XorOnly': XorOnly,
'XorMappedAddress': XorMappedAddress,
'ServerName': ServerName,
'SecondaryAddress': SecondaryAddress}

dictMsgTypeToVal = {
'BindRequestMsg': BindRequestMsg,
'BindResponseMsg': BindResponseMsg,
'BindErrorResponseMsg': BindErrorResponseMsg,
'SharedSecretRequestMsg': SharedSecretRequestMsg,
'SharedSecretResponseMsg': SharedSecretResponseMsg,
'SharedSecretErrorResponseMsg': SharedSecretErrorResponseMsg}

dictValToMsgType = {}

dictValToAttr = {}

Blocked = "Blocked"
OpenInternet = "Open Internet"
FullCone = "Full Cone"
SymmetricUDPFirewall = "Symmetric UDP Firewall"
RestrictNAT = "Restrict NAT"
RestrictPortNAT = "Restrict Port NAT"
SymmetricNAT = "Symmetric NAT"
ChangedAddressError = "Meet an error, when do Test1 on Changed IP and Port"

def _initialize():
items = list(dictAttrToVal.items())
for i in range(len(items)):
dictValToAttr.update({items[i][1]: items[i][0]})
items = list(dictMsgTypeToVal.items())
for i in range(len(items)):
dictValToMsgType.update({items[i][1]: items[i][0]})

def gen_tran_id():
a = ''
for i in range(32):
a += random.choice('0123456789ABCDEF') # RFC3489 128bits transaction ID
#return binascii.a2b_hex(a)
return a

#sun协议消息的解析
def stun_test(sock, host, port, source_ip, source_port, send_data=""):
retVal = {'Resp': False, 'ExternalIP': None, 'ExternalPort': None,
'SourceIP': None, 'SourcePort': None, 'ChangedIP': None,
'ChangedPort': None}
str_len = "%#04d" % (len(send_data) / 2)
tranid = gen_tran_id()
str_data = ''.join([BindRequestMsg, str_len, tranid, send_data])
print('str_data->'+str_data)
data = binascii.a2b_hex(str_data)
recvCorr = False
while not recvCorr:
recieved = False
count = 3
while not recieved:
log.debug("sendto %s" % str((host, port)))
try:
sock.sendto(data, (host, port))
except socket.gaierror:
retVal['Resp'] = False
return retVal
try:
buf, addr = sock.recvfrom(2048)
log.debug("recvfrom: %s" % str(addr))
recieved = True
except Exception:
recieved = False
if count > 0:
count -= 1
else:
retVal['Resp'] = False
return retVal
msgtype = binascii.b2a_hex(buf[0:2])
bind_resp_msg = dictValToMsgType[str(msgtype)[2:-1]] == "BindResponseMsg"
tranid_match = tranid.upper() == str(binascii.b2a_hex(buf[4:20]))[2:-1].upper()
if bind_resp_msg and tranid_match:
recvCorr = True
retVal['Resp'] = True#full cone
len_message = int(binascii.b2a_hex(buf[2:4]), 16)
len_remain = len_message
base = 20#报文头的长度
while len_remain:
attr_type = str(binascii.b2a_hex(buf[base:(base + 2)]))[2:-1]
attr_len = int(binascii.b2a_hex(buf[(base + 2):(base + 4)]),
16)
if attr_type == MappedAddress: # first two bytes: 0x0001
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
ip = ".".join([
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))])
retVal['ExternalIP'] = ip
retVal['ExternalPort'] = port
if attr_type == SourceAddress:
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
ip = ".".join([
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))])
retVal['SourceIP'] = ip
retVal['SourcePort'] = port
if attr_type == ChangedAddress:
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
ip = ".".join([
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))])
retVal['ChangedIP'] = ip
retVal['ChangedPort'] = port
#if attr_type == ServerName:
#serverName = buf[(base+4):(base+4+attr_len)]
base = base + 4 + attr_len
len_remain = len_remain - (4 + attr_len)
#s.close()
return retVal

def get_nat_type(s, source_ip, source_port, stun_host=None, stun_port=3478):
_initialize()
port = stun_port
log.debug("Do Test1")
resp = False
if stun_host:
ret = stun_test(s, stun_host, port, source_ip, source_port)
resp = ret['Resp']
else:
for stun_host in stun_servers_list:
log.debug('Trying STUN host: %s' % stun_host)
ret = stun_test(s, stun_host, port, source_ip, source_port)
resp = ret['Resp']
if resp:
break
if not resp:
return Blocked, ret
log.debug("Result: %s" % ret)
exIP = ret['ExternalIP']
exPort = ret['ExternalPort']
changedIP = ret['ChangedIP']
changedPort = ret['ChangedPort']
if ret['ExternalIP'] == source_ip:
changeRequest = ''.join([ChangeRequest, '0004', "00000006"])
ret = stun_test(s, stun_host, port, source_ip, source_port,
changeRequest)
if ret['Resp']:
typ = OpenInternet
else:
typ = SymmetricUDPFirewall
else:
changeRequest = ''.join([ChangeRequest, '0004', "00000006"])
log.debug("Do Test2")
ret = stun_test(s, stun_host, port, source_ip, source_port,
changeRequest)
log.debug("Result: %s" % ret)
if ret['Resp']:
typ = FullCone
else:
log.debug("Do Test1")
ret = stun_test(s, changedIP, changedPort, source_ip, source_port)
log.debug("Result: %s" % ret)
if not ret['Resp']:
typ = ChangedAddressError
else:
if exIP == ret['ExternalIP'] and exPort == ret['ExternalPort']:
changePortRequest = ''.join([ChangeRequest, '0004', "00000002"])
log.debug("Do Test3")
ret = stun_test(s, changedIP, port, source_ip, source_port, changePortRequest)
log.debug("Result: %s" % ret)
if ret['Resp'] == True:
typ = RestrictNAT
else:
typ = RestrictPortNAT
else:
typ = SymmetricNAT
return typ, ret

def get_ip_info(source_ip="0.0.0.0", source_port=54320, stun_host=None,
stun_port=3478):
socket.setdefaulttimeout(2)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((source_ip, source_port))
nat_type, nat = get_nat_type(s, source_ip, source_port,
stun_host=stun_host, stun_port=stun_port)
external_ip = nat['ExternalIP']
external_port = nat['ExternalPort']
s.close()
socket.setdefaulttimeout(None)
return nat_type, external_ip, external_port

def main():
nat_type, external_ip, external_port = get_ip_info()
print("NAT Type:", nat_type)
print("External IP:", external_ip)
print("External Port:", external_port)

if name == 'main':
main()
`

两个client 使用同一个路由器, 不能正常通讯

hi, lai

我试用了你的 pypunch P2P, 如果我两个client 是使用不同的路由器上网的话
使可以正常 chat的。 但是如果 两个client 使用同一个 路由器上网, 使无法通讯的。

PS: 我把你的python的代码 porting成了 C代码了。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.