From 24d7fe0d3ee4be92e54c996276167da92cb48cc3 Mon Sep 17 00:00:00 2001 From: Muromi Rikka Date: Sun, 17 Jul 2016 19:41:06 +0800 Subject: [PATCH 1/5] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 809e8f7..5f4e208 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ 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 write [an article][4] about the detailed implementation (in Chinese). From f8d30dceb92f2e9a3e4a826c23d069e51b00fe94 Mon Sep 17 00:00:00 2001 From: Yao Zuo Date: Wed, 21 Dec 2016 13:26:43 +0800 Subject: [PATCH 2/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f4e208..6976c45 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 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 write [an article][4] about the detailed implementation (in Chinese). +I've written [an article][4] about the detailed implementation (in Chinese). Based on [koenbollen's gist][1] From a8bce60243fc8b568245e4d591dc30429d61d0d3 Mon Sep 17 00:00:00 2001 From: laike9m Date: Tue, 4 Apr 2017 14:15:26 +0800 Subject: [PATCH 3/5] Allow user to use Crtl+C to exit #4 --- .gitignore | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ client.py | 33 ++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a29c35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject +.idea + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ diff --git a/client.py b/client.py index b8c3d8e..c2eb9dc 100644 --- a/client.py +++ b/client.py @@ -5,6 +5,7 @@ import sys import struct import socket +import time from threading import Thread, Event import stun @@ -85,9 +86,20 @@ def send_msg(self, sock): data = sys.stdin.readline() sock.sendto(data, self.target) + @staticmethod + def start_working_threads(send, recv, event=None, *args, **kwargs): + ts = Thread(target=send, args=args, kwargs=kwargs) + ts.setDaemon(True) + ts.start() + if event: + event.wait() + tr = Thread(target=recv, args=args, kwargs=kwargs) + tr.setDaemon(True) + tr.start() + def chat_fullcone(self): - Thread(target=self.send_msg, args=(self.sockfd,)).start() - Thread(target=self.recv_msg, args=(self.sockfd,)).start() + self.start_working_threads(self.send_msg, self.recv_msg, None, + self.sockfd) def chat_restrict(self): from threading import Timer @@ -102,9 +114,8 @@ def send(count): self.periodic_running = True send(0) kwargs = {'is_restrict': True, 'event': cancel_event} - Thread(target=self.recv_msg, args=(self.sockfd,), kwargs=kwargs).start() - cancel_event.wait() - Thread(target=self.send_msg, args=(self.sockfd,)).start() + self.start_working_threads(self.send_msg, self.recv_msg, + cancel_event, self.sockfd, **kwargs) def chat_symmetric(self): """ @@ -120,8 +131,8 @@ def recv_msg_symm(sock): data, addr = sock.recvfrom(1024) if addr == self.master: sys.stdout.write(data) - Thread(target=send_msg_symm, args=(self.sockfd,)).start() - Thread(target=recv_msg_symm, args=(self.sockfd,)).start() + self.start_working_threads(send_msg_symm, recv_msg_symm, None, + self.sockfd) def main(self, test_nat_type=None): """ @@ -155,6 +166,13 @@ def main(self, test_nat_type=None): else: print("NAT type wrong!") + while True: + try: + time.sleep(0.5) + except KeyboardInterrupt: + print("exit") + sys.exit(0) + @staticmethod def get_nat_type(): parser = optparse.OptionParser(version=stun.__version__) @@ -189,4 +207,5 @@ def get_nat_type(): test_nat_type = NATTYPE[int(sys.argv[4])] # 输入数字0,1,2,3 except IndexError: test_nat_type = None + c.main(test_nat_type) From 3cf3adda2335ed7063f8d01acd733106e92b231b Mon Sep 17 00:00:00 2001 From: laike9m Date: Tue, 4 Apr 2017 14:17:57 +0800 Subject: [PATCH 4/5] beautify --- client.py | 101 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/client.py b/client.py index c2eb9dc..cb5b63c 100644 --- a/client.py +++ b/client.py @@ -2,11 +2,11 @@ # coding:utf-8 import optparse -import sys -import struct import socket +import struct +import sys import time -from threading import Thread, Event +from threading import Event, Thread import stun @@ -14,7 +14,7 @@ RestrictNAT = "Restrict NAT" # 1 RestrictPortNAT = "Restrict Port NAT" # 2 SymmetricNAT = "Symmetric NAT" # 3 -UnknownNAT = "Unknown NAT" # 4 +UnknownNAT = "Unknown NAT" # 4 NATTYPE = (FullCone, RestrictNAT, RestrictPortNAT, SymmetricNAT, UnknownNAT) @@ -23,24 +23,25 @@ def bytes2addr(bytes): if len(bytes) != 8: raise ValueError("invalid bytes") host = socket.inet_ntoa(bytes[:4]) - port = struct.unpack("H", bytes[-4:-2])[0] # unpack returns a tuple even if it contains exactly one item + port = struct.unpack("H", bytes[-4:-2])[ + 0] # unpack returns a tuple even if it contains exactly one item nat_type_id = struct.unpack("H", bytes[-2:])[0] target = (host, port) return target, nat_type_id class Client(): - def __init__(self): try: - master_ip = '127.0.0.1' if sys.argv[1] == 'localhost' else sys.argv[1] + master_ip = '127.0.0.1' if sys.argv[ + 1] == 'localhost' else sys.argv[1] self.master = (master_ip, int(sys.argv[2])) self.pool = sys.argv[3].strip() self.sockfd = self.target = None self.periodic_running = False self.peer_nat_type = None except (IndexError, ValueError): - print sys.stderr, "usage: %s " % sys.argv[0] + print(sys.stderr, "usage: %s " % sys.argv[0]) sys.exit(65) def request_for_connection(self, nat_type_id=0): @@ -48,27 +49,30 @@ def request_for_connection(self, nat_type_id=0): self.sockfd.sendto(self.pool + ' {0}'.format(nat_type_id), self.master) data, addr = self.sockfd.recvfrom(len(self.pool) + 3) if data != "ok " + self.pool: - print sys.stderr, "unable to request!" + print(sys.stderr, "unable to request!") sys.exit(1) self.sockfd.sendto("ok", self.master) sys.stderr = sys.stdout - print sys.stderr, "request sent, waiting for partner in pool '%s'..." % self.pool + print(sys.stderr, + "request sent, waiting for partner in pool '%s'..." % self.pool) data, addr = self.sockfd.recvfrom(8) self.target, peer_nat_type_id = bytes2addr(data) print(self.target, peer_nat_type_id) self.peer_nat_type = NATTYPE[peer_nat_type_id] - print sys.stderr, "connected to {1}:{2}, its NAT type is {0}".format(self.peer_nat_type, *self.target) + print(sys.stderr, "connected to {1}:{2}, its NAT type is {0}".format( + self.peer_nat_type, *self.target)) def recv_msg(self, sock, is_restrict=False, event=None): if is_restrict: while True: data, addr = sock.recvfrom(1024) if self.periodic_running: - print "periodic_send is alive" + print("periodic_send is alive") self.periodic_running = False event.set() - print "received msg from target, periodic send cancelled, chat start." + print("received msg from target," + "periodic send cancelled, chat start.") if addr == self.target or addr == self.master: sys.stdout.write(data) if data == "punching...\n": @@ -109,18 +113,19 @@ def send(count): self.sockfd.sendto('punching...\n', self.target) print("UDP punching package {0} sent".format(count)) if self.periodic_running: - Timer(0.5, send, args=(count + 1,)).start() + Timer(0.5, send, args=(count + 1, )).start() self.periodic_running = True send(0) kwargs = {'is_restrict': True, 'event': cancel_event} - self.start_working_threads(self.send_msg, self.recv_msg, - cancel_event, self.sockfd, **kwargs) + self.start_working_threads(self.send_msg, self.recv_msg, cancel_event, + self.sockfd, **kwargs) def chat_symmetric(self): """ Completely rely on relay server(TURN) """ + def send_msg_symm(sock): while True: data = 'msg ' + sys.stdin.readline() @@ -131,6 +136,7 @@ def recv_msg_symm(sock): data, addr = sock.recvfrom(1024) if addr == self.master: sys.stdout.write(data) + self.start_working_threads(send_msg_symm, recv_msg_symm, None, self.sockfd) @@ -176,31 +182,56 @@ def main(self, test_nat_type=None): @staticmethod def get_nat_type(): parser = optparse.OptionParser(version=stun.__version__) - parser.add_option("-d", "--debug", dest="DEBUG", action="store_true", - default=False, help="Enable debug logging") - parser.add_option("-H", "--host", dest="stun_host", default=None, - help="STUN host to use") - parser.add_option("-P", "--host-port", dest="stun_port", type="int", - default=3478, help="STUN host port to use (default: " - "3478)") - parser.add_option("-i", "--interface", dest="source_ip", default="0.0.0.0", - help="network interface for client (default: 0.0.0.0)") - parser.add_option("-p", "--port", dest="source_port", type="int", - default=54320, help="port to listen on for client " - "(default: 54320)") + parser.add_option( + "-d", + "--debug", + dest="DEBUG", + action="store_true", + default=False, + help="Enable debug logging") + parser.add_option( + "-H", + "--host", + dest="stun_host", + default=None, + help="STUN host to use") + parser.add_option( + "-P", + "--host-port", + dest="stun_port", + type="int", + default=3478, + help="STUN host port to use (default: " + "3478)") + parser.add_option( + "-i", + "--interface", + dest="source_ip", + default="0.0.0.0", + help="network interface for client (default: 0.0.0.0)") + parser.add_option( + "-p", + "--port", + dest="source_port", + type="int", + default=54320, + help="port to listen on for client " + "(default: 54320)") (options, args) = parser.parse_args() if options.DEBUG: stun.enable_logging() - kwargs = dict(source_ip=options.source_ip, - source_port=int(options.source_port), - stun_host=options.stun_host, - stun_port=options.stun_port) + kwargs = dict( + source_ip=options.source_ip, + source_port=int(options.source_port), + stun_host=options.stun_host, + stun_port=options.stun_port) nat_type, external_ip, external_port = stun.get_ip_info(**kwargs) - print "NAT Type:", nat_type - print "External IP:", external_ip - print "External Port:", external_port + print("NAT Type:", nat_type) + print("External IP:", external_ip) + print("External Port:", external_port) return nat_type, external_ip, external_port + if __name__ == "__main__": c = Client() try: From 3b9fcf6a5a6e9801b20793ebb7d8ecd539b817bf Mon Sep 17 00:00:00 2001 From: Yao Zuo Date: Wed, 28 Jun 2017 10:32:13 +0800 Subject: [PATCH 5/5] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 766a0a5..f6b562f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) {{{year}}} {{{fullname}}} +Copyright (c) 2017 laike9m Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal