natter.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. import threading
  2. import socket
  3. import struct
  4. import codecs
  5. import time
  6. import sys
  7. import os
  8. # Fix OpenWRT Python codecs issues:
  9. # Always fallback to ASCII when specified codec is not available.
  10. try:
  11. codecs.lookup('idna')
  12. codecs.lookup('utf-8')
  13. except LookupError:
  14. def search_codec(_):
  15. return codecs.CodecInfo(codecs.ascii_encode, codecs.ascii_decode, name='ascii')
  16. codecs.register(search_codec)
  17. class Logger(object):
  18. DEBUG = 1
  19. INFO = 2
  20. WARNING = 3
  21. ERROR = 4
  22. def __init__(self, level = INFO):
  23. self.level = level
  24. def debug(self, msg):
  25. if self.level <= Logger.DEBUG:
  26. sys.stdout.write("[DEBUG] - " + str(msg) + "\n")
  27. sys.stdout.flush()
  28. def info(self, msg):
  29. if self.level <= Logger.INFO:
  30. sys.stdout.write("[INFO] - " + str(msg) + "\n")
  31. sys.stdout.flush()
  32. def warning(self, msg):
  33. if self.level <= Logger.WARNING:
  34. sys.stderr.write("[WARNING] - " + str(msg) + "\n")
  35. sys.stderr.flush()
  36. def error(self, msg):
  37. if self.level <= Logger.ERROR:
  38. sys.stderr.write("[ERROR] - " + str(msg) + "\n")
  39. sys.stderr.flush()
  40. class StunClient(object):
  41. # Note: IPv4 Only.
  42. # Reference:
  43. # https://www.rfc-editor.org/rfc/rfc3489
  44. # https://www.rfc-editor.org/rfc/rfc5389
  45. # https://www.rfc-editor.org/rfc/rfc8489
  46. # Servers in this list must be compatible with rfc5389 or rfc8489
  47. stun_server_tcp = [
  48. "fwa.lifesizecloud.com",
  49. "stun.isp.net.au",
  50. "stun.freeswitch.org",
  51. "stun.voip.blackberry.com",
  52. "stun.nextcloud.com",
  53. "stun.stunprotocol.org",
  54. "stun.sipnet.com",
  55. "stun.radiojar.com",
  56. "stun.sonetel.com",
  57. "stun.voipgate.com"
  58. ]
  59. # Servers in this list must be compatible with rfc3489, with "change IP" and "change port" functions available
  60. stun_server_udp = [
  61. "stun.miwifi.com",
  62. "stun.qq.com"
  63. ]
  64. MTU = 1500
  65. STUN_PORT = 3478
  66. MAGIC_COOKIE = 0x2112a442
  67. BIND_REQUEST = 0x0001
  68. BIND_RESPONSE = 0x0101
  69. FAMILY_IPV4 = 0x01
  70. FAMILY_IPV6 = 0x02
  71. CHANGE_PORT = 0x0002
  72. CHANGE_IP = 0x0004
  73. ATTRIB_MAPPED_ADDRESS = 0x0001
  74. ATTRIB_CHANGE_REQUEST = 0x0003
  75. ATTRIB_XOR_MAPPED_ADDRESS = 0x0020
  76. NAT_OPEN_INTERNET = 0
  77. NAT_FULL_CONE = 1
  78. NAT_RESTRICTED = 2
  79. NAT_PORT_RESTRICTED = 3
  80. NAT_SYMMETRIC = 4
  81. NAT_SYM_UDP_FIREWALL = 5
  82. def __init__(self, source_ip = "0.0.0.0", log_level = Logger.INFO):
  83. self.logger = Logger(log_level)
  84. self.source_ip = source_ip
  85. self.stun_ip_tcp = []
  86. self.stun_ip_udp = []
  87. if not self.check_reuse_ability():
  88. raise OSError("This OS or Python does not support reusing ports!")
  89. self.logger.info("Getting STUN server IP...")
  90. for hostname in self.stun_server_tcp:
  91. self.stun_ip_tcp.extend(self.resolve_hostname(hostname))
  92. for hostname in self.stun_server_udp:
  93. self.stun_ip_udp.extend(self.resolve_hostname(hostname))
  94. if not self.stun_ip_tcp or not self.stun_ip_udp:
  95. raise Exception("No public STUN server is avaliable. Please check your Internet connection.")
  96. def get_free_port(self, udp=False):
  97. if udp:
  98. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  99. else:
  100. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  101. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  102. if 'SO_REUSEPORT' in dir(socket):
  103. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  104. sock.bind(('', 0))
  105. ret = sock.getsockname()[1]
  106. sock.close()
  107. return ret
  108. def check_reuse_ability(self):
  109. try:
  110. test_port = self.get_free_port()
  111. s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  112. s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  113. if 'SO_REUSEPORT' in dir(socket):
  114. s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  115. s1.bind(("0.0.0.0", test_port))
  116. s1.listen(1)
  117. s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  118. s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  119. if 'SO_REUSEPORT' in dir(socket):
  120. s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  121. s2.bind(("0.0.0.0", test_port))
  122. s2.listen(1)
  123. s1.close()
  124. s2.close()
  125. return True
  126. except OSError as e:
  127. self.logger.debug("%s: %s" % (e.__class__.__name__, e))
  128. return False
  129. def resolve_hostname(self, hostname):
  130. self.logger.debug("Resolving hostname [%s]..." % hostname)
  131. try:
  132. host, alias, ip_addresses = socket.gethostbyname_ex(hostname)
  133. return ip_addresses
  134. except Exception as e:
  135. self.logger.debug("%s: %s" % (e.__class__.__name__, e))
  136. return []
  137. def random_tran_id(self, use_magic_cookie = False):
  138. if use_magic_cookie:
  139. # Compatible with rfc3489, rfc5389 and rfc8489
  140. return struct.pack("!L", self.MAGIC_COOKIE) + os.urandom(12)
  141. else:
  142. # Compatible with rfc3489
  143. return os.urandom(16)
  144. def pack_stun_message(self, msg_type, tran_id, payload = b""):
  145. return struct.pack("!HH", msg_type, len(payload)) + tran_id + payload
  146. def unpack_stun_message(self, data):
  147. msg_type, msg_length = struct.unpack("!HH", data[:4])
  148. tran_id = data[4:20]
  149. payload = data[20:20 + msg_length]
  150. return msg_type, tran_id, payload
  151. def get_mapped_addr(self, payload):
  152. while payload:
  153. attrib_type, attrib_length = struct.unpack("!HH", payload[:4])
  154. attrib_value = payload[4:4 + attrib_length]
  155. payload = payload[4 + attrib_length:]
  156. if attrib_type == self.ATTRIB_MAPPED_ADDRESS:
  157. _, family, port = struct.unpack("!BBH", attrib_value[:4])
  158. if family == self.FAMILY_IPV4:
  159. ip = socket.inet_ntoa(attrib_value[4:8])
  160. return ip, port
  161. elif attrib_type == self.ATTRIB_XOR_MAPPED_ADDRESS:
  162. # rfc5389 and rfc8489
  163. _, family, xor_port = struct.unpack("!BBH", attrib_value[:4])
  164. if family == self.FAMILY_IPV4:
  165. xor_iip, = struct.unpack("!L", attrib_value[4:8])
  166. ip = socket.inet_ntoa(struct.pack("!L", self.MAGIC_COOKIE ^ xor_iip))
  167. port = (self.MAGIC_COOKIE >> 16) ^ xor_port
  168. return ip, port
  169. return None
  170. def tcp_test(self, stun_host, source_port, timeout = 1):
  171. # rfc5389 and rfc8489 only
  172. self.logger.debug("Trying TCP STUN: %s" % stun_host)
  173. tran_id = self.random_tran_id(use_magic_cookie = True)
  174. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  175. try:
  176. sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  177. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  178. if 'SO_REUSEPORT' in dir(socket):
  179. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  180. sock.settimeout(timeout)
  181. sock.bind((self.source_ip, source_port))
  182. sock.connect((stun_host, self.STUN_PORT))
  183. data = self.pack_stun_message(self.BIND_REQUEST, tran_id)
  184. sock.sendall(data)
  185. buf = sock.recv(self.MTU)
  186. msg_type, msg_id, payload = self.unpack_stun_message(buf)
  187. if tran_id == msg_id and msg_type == self.BIND_RESPONSE:
  188. source_addr = sock.getsockname()
  189. mapped_addr = self.get_mapped_addr(payload)
  190. ret = source_addr, mapped_addr
  191. self.logger.debug("(TCP) %s says: %s" % (stun_host, mapped_addr))
  192. else:
  193. ret = None
  194. sock.shutdown(socket.SHUT_RDWR)
  195. sock.close()
  196. except Exception as e:
  197. self.logger.debug("%s: %s" % (e.__class__.__name__, e))
  198. sock.close()
  199. ret = None
  200. return ret
  201. def udp_test(self, stun_host, source_port, change_ip = False, change_port = False, timeout = 1, repeat = 3):
  202. # Note:
  203. # Assuming STUN is being multiplexed with other protocols,
  204. # the packet must be inspected to check if it is a STUN packet.
  205. self.logger.debug("Trying UDP STUN: %s (change ip:%d/port:%d)" % (stun_host, change_ip, change_port))
  206. time_start = time.time()
  207. tran_id = self.random_tran_id()
  208. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  209. try:
  210. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  211. if 'SO_REUSEPORT' in dir(socket):
  212. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  213. sock.settimeout(timeout)
  214. sock.bind((self.source_ip, source_port))
  215. flags = 0
  216. if change_ip:
  217. flags |= self.CHANGE_IP
  218. if change_port:
  219. flags |= self.CHANGE_PORT
  220. if flags:
  221. payload = struct.pack("!HHL", self.ATTRIB_CHANGE_REQUEST, 0x4, flags)
  222. data = self.pack_stun_message(self.BIND_REQUEST, tran_id, payload)
  223. else:
  224. data = self.pack_stun_message(self.BIND_REQUEST, tran_id)
  225. # Send packets repeatedly to avoid packet loss.
  226. for _ in range(repeat):
  227. sock.sendto(data, (stun_host, self.STUN_PORT))
  228. while True:
  229. time_left = time_start + timeout - time.time()
  230. if time_left <= 0:
  231. raise socket.timeout("timed out")
  232. sock.settimeout(time_left)
  233. buf, recv_addr = sock.recvfrom(self.MTU)
  234. recv_host, recv_port = recv_addr
  235. # check STUN packet
  236. if len(buf) < 20:
  237. continue
  238. msg_type, msg_id, payload = self.unpack_stun_message(buf)
  239. if tran_id != msg_id or msg_type != self.BIND_RESPONSE:
  240. continue
  241. source_addr = sock.getsockname()
  242. mapped_addr = self.get_mapped_addr(payload)
  243. ip_changed = (recv_host != self.STUN_PORT)
  244. port_changed = (recv_port != self.STUN_PORT)
  245. self.logger.debug("(UDP) %s says: %s" % (recv_addr, mapped_addr))
  246. return source_addr, mapped_addr, ip_changed, port_changed
  247. except Exception as e:
  248. self.logger.debug("%s: %s" % (e.__class__.__name__, e))
  249. return None
  250. finally:
  251. sock.close()
  252. def get_tcp_mapping(self, source_port):
  253. server_ip = first = self.stun_ip_tcp[0]
  254. while True:
  255. ret = self.tcp_test(server_ip, source_port)
  256. if ret is None:
  257. # server unavailable, put it at the end of the list
  258. self.stun_ip_tcp.append(self.stun_ip_tcp.pop(0))
  259. server_ip = self.stun_ip_tcp[0]
  260. if server_ip == first:
  261. raise Exception("No public STUN server is avaliable. Please check your Internet connection.")
  262. else:
  263. source_addr, mapped_addr = ret
  264. return source_addr, mapped_addr
  265. def get_udp_mapping(self, source_port):
  266. server_ip = first = self.stun_ip_udp[0]
  267. while True:
  268. ret = self.udp_test(server_ip, source_port)
  269. if ret is None:
  270. # server unavailable, put it at the end of the list
  271. self.stun_ip_udp.append(self.stun_ip_udp.pop(0))
  272. server_ip = self.stun_ip_udp[0]
  273. if server_ip == first:
  274. raise Exception("No public STUN server is avaliable. Please check your Internet connection.")
  275. else:
  276. source_addr, mapped_addr, ip_changed, port_changed = ret
  277. return source_addr, mapped_addr
  278. def check_nat_type(self, source_port = 0):
  279. # Like classic STUN (rfc3489). Detect NAT behavior for UDP.
  280. # Modified from rfc3489. Requires at least two STUN servers.
  281. ret_test1_1 = None
  282. ret_test1_2 = None
  283. ret_test2 = None
  284. ret_test3 = None
  285. if source_port == 0:
  286. source_port = self.get_free_port(udp=True)
  287. for server_ip in self.stun_ip_udp:
  288. ret = self.udp_test(server_ip, source_port, change_ip=False, change_port=False)
  289. if ret is None:
  290. self.logger.debug("No response. Trying another STUN server...")
  291. continue
  292. if ret_test1_1 is None:
  293. ret_test1_1 = ret
  294. continue
  295. ret_test1_2 = ret
  296. ret = self.udp_test(server_ip, source_port, change_ip=True, change_port=True)
  297. if ret is not None:
  298. source_addr, mapped_addr, ip_changed, port_changed = ret
  299. if not ip_changed or not port_changed:
  300. self.logger.debug("Trying another STUN server because current server do not have another available IP or port...")
  301. continue
  302. ret_test2 = ret
  303. ret_test3 = self.udp_test(server_ip, source_port, change_ip=False, change_port=True)
  304. break
  305. else:
  306. raise Exception("UDP Blocked or not enough STUN servers available.")
  307. source_addr_1_1, mapped_addr_1_1, _, _ = ret_test1_1
  308. source_addr_1_2, mapped_addr_1_2, _, _ = ret_test1_2
  309. if mapped_addr_1_1 != mapped_addr_1_2:
  310. return StunClient.NAT_SYMMETRIC
  311. if source_addr_1_1 == mapped_addr_1_1:
  312. if ret_test2 is not None:
  313. return StunClient.NAT_OPEN_INTERNET
  314. else:
  315. return StunClient.NAT_SYM_UDP_FIREWALL
  316. else:
  317. if ret_test2 is not None:
  318. return StunClient.NAT_FULL_CONE
  319. else:
  320. if ret_test3 is not None:
  321. return StunClient.NAT_RESTRICTED
  322. else:
  323. return StunClient.NAT_PORT_RESTRICTED
  324. def is_tcp_cone(self, source_port = 0):
  325. # Detect NAT behavior for TCP. Requires at least three STUN servers for accuracy.
  326. if source_port == 0:
  327. source_port = self.get_free_port()
  328. mapped_addr_first = None
  329. count = 0
  330. for server_ip in self.stun_ip_tcp:
  331. if count >= 3:
  332. return True
  333. ret = self.tcp_test(server_ip, source_port)
  334. if ret is not None:
  335. source_addr, mapped_addr = ret
  336. if mapped_addr_first is not None and mapped_addr != mapped_addr_first:
  337. return False
  338. mapped_addr_first = ret[1]
  339. count += 1
  340. raise Exception("Not enough STUN servers available.")
  341. class HttpTestServer(object):
  342. # HTTP Server for testing purpose
  343. # On success, you can see the text "It works!".
  344. def __init__(self, listen_addr):
  345. self.running = False
  346. self.listen_addr = listen_addr
  347. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  348. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  349. if 'SO_REUSEPORT' in dir(socket):
  350. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  351. def run(self):
  352. self.running = True
  353. self.sock.bind(self.listen_addr)
  354. self.sock.listen(1)
  355. while self.running:
  356. try:
  357. conn, addr = self.sock.accept()
  358. except Exception:
  359. return
  360. try:
  361. conn.recv(4096)
  362. conn.sendall(b"HTTP/1.1 200 OK\r\n")
  363. conn.sendall(b"Content-Type: text/html\r\n")
  364. conn.sendall(b"\r\n")
  365. conn.sendall(b"<h1>It works!</h1><hr/>Natter\r\n")
  366. conn.shutdown(socket.SHUT_RDWR)
  367. except Exception:
  368. pass
  369. finally:
  370. conn.close()
  371. def start(self):
  372. threading.Thread(target=self.run).start()
  373. def stop(self):
  374. self.running = False
  375. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  376. sock.settimeout(0.1)
  377. result = sock.connect_ex(self.listen_addr)
  378. sock.close()
  379. self.sock.close()
  380. class Natter(object):
  381. def __init__(self, source_ip, source_port, test_http = False,
  382. keep_alive_host = "www.qq.com", keep_alive_interval = 10, retry_sec = 3, log_level = Logger.INFO):
  383. self.logger = Logger(log_level)
  384. self.source_ip = source_ip
  385. self.source_port = source_port
  386. self.test_http = test_http
  387. self.keep_alive_host = keep_alive_host
  388. self.keep_alive_interval = keep_alive_interval
  389. self.retry_sec = retry_sec
  390. self.stun_client = StunClient(source_ip, log_level = log_level)
  391. self.keep_alive_sock = self._init_keep_alive_sock()
  392. self.http_test_server = HttpTestServer((source_ip, source_port))
  393. def _init_keep_alive_sock(self):
  394. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  395. s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  396. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  397. if 'SO_REUSEPORT' in dir(socket):
  398. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  399. s.bind((self.source_ip, self.source_port))
  400. s.connect((self.keep_alive_host, 80))
  401. s.settimeout(self.keep_alive_interval)
  402. return s
  403. def _keep_alive(self):
  404. s = self.keep_alive_sock
  405. try:
  406. s.sendall(b"GET /~ HTTP/1.1\r\n")
  407. s.sendall(b"Host: %s\r\n" % self.keep_alive_host.encode())
  408. s.sendall(b"Connection: keep-alive\r\n")
  409. s.sendall(b"\r\n")
  410. except Exception as e:
  411. self.logger.debug("%s: %s" % (e.__class__.__name__, e))
  412. return False
  413. try:
  414. while s.recv(4096):
  415. self.logger.debug("[%s] Keep-Alive OK!" % time.asctime())
  416. self.logger.debug("Server closed connection")
  417. return False
  418. except socket.timeout:
  419. return True
  420. except Exception as e:
  421. self.logger.debug("%s: %s" % (e.__class__.__name__, e))
  422. return False
  423. def test_port_open(self, dst_addr, timeout = 3):
  424. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  425. sock.settimeout(timeout)
  426. result = sock.connect_ex(dst_addr)
  427. sock.close()
  428. return result == 0
  429. def tcp_punch(self):
  430. self.logger.info("Checking NAT Type for UDP...")
  431. nat_type = self.stun_client.check_nat_type()
  432. if nat_type == StunClient.NAT_OPEN_INTERNET:
  433. nat_type_txt = "Open Internet"
  434. elif nat_type == StunClient.NAT_SYM_UDP_FIREWALL:
  435. nat_type_txt = "Symmetric UDP firewall"
  436. elif nat_type == StunClient.NAT_FULL_CONE:
  437. nat_type_txt = "Full cone (NAT 1)"
  438. elif nat_type == StunClient.NAT_RESTRICTED:
  439. nat_type_txt = "Restricted (NAT 2)"
  440. elif nat_type == StunClient.NAT_PORT_RESTRICTED:
  441. nat_type_txt = "Port restricted (NAT 3)"
  442. elif nat_type == StunClient.NAT_SYMMETRIC:
  443. nat_type_txt = "Symmetric (NAT 4)"
  444. else:
  445. nat_type_txt = "Unknown"
  446. self.logger.info("NAT Type for UDP: [ %s ]" % nat_type_txt)
  447. if nat_type == StunClient.NAT_OPEN_INTERNET:
  448. self.logger.warning("It looks like you are not in a NAT network, so there is no need to use this tool.")
  449. elif nat_type != StunClient.NAT_FULL_CONE:
  450. self.logger.warning("The NAT type of your network is not full cone (NAT 1). TCP hole punching may fail.")
  451. self.logger.info("Checking NAT Type for TCP...")
  452. if self.stun_client.is_tcp_cone():
  453. self.logger.info("NAT Type for TCP: [ Cone NAT ]")
  454. else:
  455. self.logger.info("NAT Type for TCP: [ Symmetric ]")
  456. self.logger.error("You cannot perform TCP hole punching in a symmetric NAT network.")
  457. return
  458. self.logger.info("Start punching...")
  459. self.http_test_server.start()
  460. source_addr, mapped_addr = self.stun_client.get_tcp_mapping(self.source_port)
  461. if not self.test_port_open(source_addr):
  462. self.logger.error("Local address %s is not available. Check your firewall settings." % source_addr)
  463. return
  464. if self.test_port_open(mapped_addr):
  465. self.logger.info(
  466. "The TCP hole punching appears to be successful. "
  467. "Please test this address from another network: %s" % str(mapped_addr)
  468. )
  469. print("\n================================\n %s\n================================\n"% str(mapped_addr))
  470. if self.test_http:
  471. print("HTTP test server is enabled. Please check [ http://%s:%d/ ]\n" % mapped_addr)
  472. else:
  473. self.logger.warning(
  474. "TCP hole punching seems to fail. Maybe you are behind a firewall. "
  475. "However, you may check this address from another network: %s" % str(mapped_addr)
  476. )
  477. if not self.test_http:
  478. self.http_test_server.stop()
  479. # Keep alive
  480. self.logger.info("TCP keep-alive...")
  481. while True:
  482. ok = self._keep_alive()
  483. if not ok:
  484. self.keep_alive_sock.close()
  485. time.sleep(self.retry_sec)
  486. self.keep_alive_sock = self._init_keep_alive_sock()
  487. self._keep_alive()
  488. source_addr, mapped_addr = self.stun_client.get_tcp_mapping(self.source_port)
  489. self.logger.info("Mapped address: %s" % str(mapped_addr))
  490. def close(self):
  491. try:
  492. self.keep_alive_sock.shutdown(socket.SHUT_RDWR)
  493. except Exception:
  494. pass
  495. self.keep_alive_sock.close()
  496. self.http_test_server.stop()
  497. def main():
  498. try:
  499. src_host = "0.0.0.0"
  500. src_port = -1
  501. verbose = False
  502. test_http = False
  503. l = []
  504. for arg in sys.argv[1:]:
  505. if arg[0] == "-":
  506. if arg == "-v":
  507. verbose = True
  508. elif arg == "-t":
  509. test_http = True
  510. else:
  511. raise ValueError
  512. else:
  513. l.append(arg)
  514. if len(l) == 1:
  515. src_port = int(l[0])
  516. elif len(l) == 2:
  517. src_host = l[0]
  518. src_port = int(l[1])
  519. else:
  520. raise ValueError
  521. except ValueError:
  522. print("Usage: python natter.py [-v] [-t] [SRC_HOST] SRC_PORT\n")
  523. return
  524. if verbose:
  525. log_level=Logger.DEBUG
  526. else:
  527. log_level=Logger.INFO
  528. natter = Natter(src_host, src_port, test_http=test_http, log_level=log_level)
  529. try:
  530. natter.tcp_punch()
  531. except KeyboardInterrupt:
  532. print("\nExiting...\n")
  533. natter.close()
  534. if __name__ == '__main__':
  535. main()