natter.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. import threading
  2. import socket
  3. import struct
  4. import codecs
  5. import json
  6. import time
  7. import sys
  8. import os
  9. __version__ = "0.9.0"
  10. # Fix OpenWRT Python codecs issues:
  11. # Always fallback to ASCII when specified codec is not available.
  12. try:
  13. codecs.lookup("idna")
  14. codecs.lookup("utf-8")
  15. except LookupError:
  16. def search_codec(_):
  17. return codecs.CodecInfo(codecs.ascii_encode, codecs.ascii_decode, name="ascii")
  18. codecs.register(search_codec)
  19. def get_free_port(udp=False):
  20. if udp:
  21. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  22. else:
  23. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  24. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  25. # Not all OS have a SO_REUSEPORT option
  26. if "SO_REUSEPORT" in dir(socket):
  27. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  28. sock.bind(("", 0))
  29. ret = sock.getsockname()[1]
  30. sock.close()
  31. return ret
  32. def test_port_open(dst_addr, timeout = 3):
  33. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  34. sock.settimeout(timeout)
  35. result = sock.connect_ex(dst_addr)
  36. sock.close()
  37. return result == 0
  38. class Logger(object):
  39. DEBUG = 1
  40. INFO = 2
  41. WARNING = 3
  42. ERROR = 4
  43. def __init__(self, level = INFO, files = (sys.stderr,)):
  44. self.level = level
  45. self.files = files
  46. def debug(self, msg):
  47. if self.level <= Logger.DEBUG:
  48. for fo in self.files:
  49. fo.write("[DEBUG] - " + str(msg) + "\n")
  50. fo.flush()
  51. def info(self, msg):
  52. if self.level <= Logger.INFO:
  53. for fo in self.files:
  54. fo.write("[INFO] - " + str(msg) + "\n")
  55. fo.flush()
  56. def warning(self, msg):
  57. if self.level <= Logger.WARNING:
  58. for fo in self.files:
  59. fo.write("[WARNING] - " + str(msg) + "\n")
  60. fo.flush()
  61. def error(self, msg):
  62. if self.level <= Logger.ERROR:
  63. for fo in self.files:
  64. fo.write("[ERROR] - " + str(msg) + "\n")
  65. fo.flush()
  66. class StunClient(object):
  67. # Note: IPv4 Only.
  68. # Reference:
  69. # https://www.rfc-editor.org/rfc/rfc3489
  70. # https://www.rfc-editor.org/rfc/rfc5389
  71. # https://www.rfc-editor.org/rfc/rfc8489
  72. # Servers in this list must be compatible with rfc5389 or rfc8489
  73. stun_server_tcp = [
  74. "fwa.lifesizecloud.com",
  75. "stun.isp.net.au",
  76. "stun.freeswitch.org",
  77. "stun.voip.blackberry.com",
  78. "stun.nextcloud.com",
  79. "stun.stunprotocol.org",
  80. "stun.sipnet.com",
  81. "stun.radiojar.com",
  82. "stun.sonetel.com",
  83. "stun.voipgate.com"
  84. ]
  85. # Servers in this list must be compatible with rfc3489, with "change IP" and "change port" functions available
  86. stun_server_udp = [
  87. "stun.miwifi.com",
  88. "stun.qq.com"
  89. ]
  90. _stun_ip_tcp = []
  91. _stun_ip_udp = []
  92. MTU = 1500
  93. STUN_PORT = 3478
  94. MAGIC_COOKIE = 0x2112a442
  95. BIND_REQUEST = 0x0001
  96. BIND_RESPONSE = 0x0101
  97. FAMILY_IPV4 = 0x01
  98. FAMILY_IPV6 = 0x02
  99. CHANGE_PORT = 0x0002
  100. CHANGE_IP = 0x0004
  101. ATTRIB_MAPPED_ADDRESS = 0x0001
  102. ATTRIB_CHANGE_REQUEST = 0x0003
  103. ATTRIB_XOR_MAPPED_ADDRESS = 0x0020
  104. NAT_OPEN_INTERNET = 0
  105. NAT_FULL_CONE = 1
  106. NAT_RESTRICTED = 2
  107. NAT_PORT_RESTRICTED = 3
  108. NAT_SYMMETRIC = 4
  109. NAT_SYM_UDP_FIREWALL = 5
  110. def __init__(self, source_ip = "0.0.0.0", logger = None):
  111. self.logger = logger if logger else Logger()
  112. self.source_ip = source_ip
  113. if not self.check_reuse_ability():
  114. raise OSError("This OS or Python does not support reusing ports!")
  115. if not self._stun_ip_tcp or not self._stun_ip_udp:
  116. self.logger.info("Getting STUN server IP...")
  117. for hostname in self.stun_server_tcp:
  118. self._stun_ip_tcp.extend(
  119. ip for ip in self.resolve_hostname(hostname) if ip not in self._stun_ip_tcp
  120. )
  121. for hostname in self.stun_server_udp:
  122. self._stun_ip_udp.extend(
  123. ip for ip in self.resolve_hostname(hostname) if ip not in self._stun_ip_udp
  124. )
  125. if not self._stun_ip_tcp or not self._stun_ip_udp:
  126. raise Exception("No public STUN server is avaliable. Please check your Internet connection.")
  127. def check_reuse_ability(self):
  128. try:
  129. # A simple test: listen on the same port
  130. test_port = get_free_port()
  131. s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  132. s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  133. if "SO_REUSEPORT" in dir(socket):
  134. s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  135. s1.bind(("", test_port))
  136. s1.listen(5)
  137. s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  138. s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  139. if "SO_REUSEPORT" in dir(socket):
  140. s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  141. s2.bind(("", test_port))
  142. s2.listen(5)
  143. s1.close()
  144. s2.close()
  145. return True
  146. except OSError as e:
  147. self.logger.debug("Cannot reuse: %s: %s" % (e.__class__.__name__, e))
  148. return False
  149. def resolve_hostname(self, hostname):
  150. self.logger.debug("Resolving hostname [%s]..." % hostname)
  151. try:
  152. host, alias, ip_addresses = socket.gethostbyname_ex(hostname)
  153. return ip_addresses
  154. except Exception as e:
  155. self.logger.debug("Cannot resolve: %s: %s" % (e.__class__.__name__, e))
  156. return []
  157. def random_tran_id(self, use_magic_cookie = False):
  158. if use_magic_cookie:
  159. # Compatible with rfc3489, rfc5389 and rfc8489
  160. return struct.pack("!L", self.MAGIC_COOKIE) + os.urandom(12)
  161. else:
  162. # Compatible with rfc3489
  163. return os.urandom(16)
  164. def pack_stun_message(self, msg_type, tran_id, payload = b""):
  165. return struct.pack("!HH", msg_type, len(payload)) + tran_id + payload
  166. def unpack_stun_message(self, data):
  167. msg_type, msg_length = struct.unpack("!HH", data[:4])
  168. tran_id = data[4:20]
  169. payload = data[20:20 + msg_length]
  170. return msg_type, tran_id, payload
  171. def extract_mapped_addr(self, payload):
  172. while payload:
  173. attrib_type, attrib_length = struct.unpack("!HH", payload[:4])
  174. attrib_value = payload[4:4 + attrib_length]
  175. payload = payload[4 + attrib_length:]
  176. if attrib_type == self.ATTRIB_MAPPED_ADDRESS:
  177. _, family, port = struct.unpack("!BBH", attrib_value[:4])
  178. if family == self.FAMILY_IPV4:
  179. ip = socket.inet_ntoa(attrib_value[4:8])
  180. return ip, port
  181. elif attrib_type == self.ATTRIB_XOR_MAPPED_ADDRESS:
  182. # rfc5389 and rfc8489
  183. _, family, xor_port = struct.unpack("!BBH", attrib_value[:4])
  184. if family == self.FAMILY_IPV4:
  185. xor_iip, = struct.unpack("!L", attrib_value[4:8])
  186. ip = socket.inet_ntoa(struct.pack("!L", self.MAGIC_COOKIE ^ xor_iip))
  187. port = (self.MAGIC_COOKIE >> 16) ^ xor_port
  188. return ip, port
  189. return None
  190. def tcp_test(self, stun_host, source_port, timeout = 1):
  191. # rfc5389 and rfc8489 only
  192. self.logger.debug("Trying TCP STUN: %s" % stun_host)
  193. tran_id = self.random_tran_id(use_magic_cookie = True)
  194. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  195. try:
  196. sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  197. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  198. if "SO_REUSEPORT" in dir(socket):
  199. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  200. sock.settimeout(timeout)
  201. sock.bind((self.source_ip, source_port))
  202. sock.connect((stun_host, self.STUN_PORT))
  203. data = self.pack_stun_message(self.BIND_REQUEST, tran_id)
  204. sock.sendall(data)
  205. buf = sock.recv(self.MTU)
  206. msg_type, msg_id, payload = self.unpack_stun_message(buf)
  207. if tran_id == msg_id and msg_type == self.BIND_RESPONSE:
  208. source_addr = sock.getsockname()
  209. mapped_addr = self.extract_mapped_addr(payload)
  210. ret = source_addr, mapped_addr
  211. self.logger.debug("(TCP) %s says: %s" % (stun_host, mapped_addr))
  212. else:
  213. ret = None
  214. sock.shutdown(socket.SHUT_RDWR)
  215. sock.close()
  216. except Exception as e:
  217. self.logger.debug("Cannot do TCP STUN test: %s: %s" % (e.__class__.__name__, e))
  218. sock.close()
  219. ret = None
  220. return ret
  221. def udp_test(self, stun_host, source_port, change_ip = False, change_port = False, timeout = 1, repeat = 3, custom_sock = None):
  222. # Note:
  223. # Assuming STUN is being multiplexed with other protocols,
  224. # the packet must be inspected to check if it is a STUN packet.
  225. # Parameter source_port has no effect when custom_sock is set
  226. self.logger.debug("Trying UDP STUN: %s (change ip:%d/port:%d)" % (stun_host, change_ip, change_port))
  227. time_start = time.time()
  228. tran_id = self.random_tran_id()
  229. if custom_sock is None:
  230. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  231. else:
  232. sock = custom_sock
  233. origin_timeout = sock.gettimeout()
  234. try:
  235. if sock is not custom_sock:
  236. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  237. if "SO_REUSEPORT" in dir(socket):
  238. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  239. sock.bind((self.source_ip, source_port))
  240. flags = 0
  241. if change_ip:
  242. flags |= self.CHANGE_IP
  243. if change_port:
  244. flags |= self.CHANGE_PORT
  245. if flags:
  246. payload = struct.pack("!HHL", self.ATTRIB_CHANGE_REQUEST, 0x4, flags)
  247. data = self.pack_stun_message(self.BIND_REQUEST, tran_id, payload)
  248. else:
  249. data = self.pack_stun_message(self.BIND_REQUEST, tran_id)
  250. # Send packets repeatedly to avoid packet loss.
  251. for _ in range(repeat):
  252. sock.sendto(data, (stun_host, self.STUN_PORT))
  253. while True:
  254. time_left = time_start + timeout - time.time()
  255. if time_left <= 0:
  256. raise socket.timeout("timed out")
  257. sock.settimeout(time_left)
  258. buf, recv_addr = sock.recvfrom(self.MTU)
  259. recv_host, recv_port = recv_addr
  260. # Check the STUN packet.
  261. if len(buf) < 20:
  262. continue
  263. msg_type, msg_id, payload = self.unpack_stun_message(buf)
  264. if tran_id != msg_id or msg_type != self.BIND_RESPONSE:
  265. continue
  266. source_addr = sock.getsockname()
  267. mapped_addr = self.extract_mapped_addr(payload)
  268. ip_changed = (recv_host != self.STUN_PORT)
  269. port_changed = (recv_port != self.STUN_PORT)
  270. self.logger.debug("(UDP) %s says: %s" % (recv_addr, mapped_addr))
  271. return source_addr, mapped_addr, ip_changed, port_changed
  272. except Exception as e:
  273. self.logger.debug("Cannot do UDP STUN test: %s: %s" % (e.__class__.__name__, e))
  274. return None
  275. finally:
  276. sock.settimeout(origin_timeout)
  277. if sock is not custom_sock:
  278. sock.close()
  279. def get_tcp_mapping(self, source_port):
  280. server_ip = first = self._stun_ip_tcp[0]
  281. while True:
  282. ret = self.tcp_test(server_ip, source_port)
  283. if ret is None:
  284. # Server unavailable, put it at the end of the list.
  285. self._stun_ip_tcp.append(self._stun_ip_tcp.pop(0))
  286. server_ip = self._stun_ip_tcp[0]
  287. if server_ip == first:
  288. raise Exception("No public STUN server is avaliable. Please check your Internet connection.")
  289. else:
  290. source_addr, mapped_addr = ret
  291. return source_addr, mapped_addr
  292. def get_udp_mapping(self, source_port, custom_sock = None):
  293. server_ip = first = self._stun_ip_udp[0]
  294. while True:
  295. ret = self.udp_test(server_ip, source_port, custom_sock = custom_sock)
  296. if ret is None:
  297. # Server unavailable, put it at the end of the list.
  298. self._stun_ip_udp.append(self._stun_ip_udp.pop(0))
  299. server_ip = self._stun_ip_udp[0]
  300. if server_ip == first:
  301. raise Exception("No public STUN server is avaliable. Please check your Internet connection.")
  302. else:
  303. source_addr, mapped_addr, ip_changed, port_changed = ret
  304. return source_addr, mapped_addr
  305. def check_nat_type(self, source_port = 0):
  306. # Like classic STUN (rfc3489). Detect NAT behavior for UDP.
  307. # Modified from rfc3489. Requires at least two STUN servers.
  308. ret_test1_1 = None
  309. ret_test1_2 = None
  310. ret_test2 = None
  311. ret_test3 = None
  312. if source_port == 0:
  313. source_port = get_free_port(udp=True)
  314. for server_ip in self._stun_ip_udp:
  315. ret = self.udp_test(server_ip, source_port, change_ip=False, change_port=False)
  316. if ret is None:
  317. self.logger.debug("No response. Trying another STUN server...")
  318. continue
  319. if ret_test1_1 is None:
  320. ret_test1_1 = ret
  321. continue
  322. ret_test1_2 = ret
  323. ret = self.udp_test(server_ip, source_port, change_ip=True, change_port=True)
  324. if ret is not None:
  325. source_addr, mapped_addr, ip_changed, port_changed = ret
  326. if not ip_changed or not port_changed:
  327. self.logger.debug("Trying another STUN server because current server do not have another available IP or port...")
  328. continue
  329. ret_test2 = ret
  330. ret_test3 = self.udp_test(server_ip, source_port, change_ip=False, change_port=True)
  331. break
  332. else:
  333. raise Exception("UDP Blocked or not enough STUN servers available.")
  334. source_addr_1_1, mapped_addr_1_1, _, _ = ret_test1_1
  335. source_addr_1_2, mapped_addr_1_2, _, _ = ret_test1_2
  336. if mapped_addr_1_1 != mapped_addr_1_2:
  337. return StunClient.NAT_SYMMETRIC
  338. if source_addr_1_1 == mapped_addr_1_1:
  339. if ret_test2 is not None:
  340. return StunClient.NAT_OPEN_INTERNET
  341. else:
  342. return StunClient.NAT_SYM_UDP_FIREWALL
  343. else:
  344. if ret_test2 is not None:
  345. return StunClient.NAT_FULL_CONE
  346. else:
  347. if ret_test3 is not None:
  348. return StunClient.NAT_RESTRICTED
  349. else:
  350. return StunClient.NAT_PORT_RESTRICTED
  351. def is_tcp_cone(self, source_port = 0):
  352. # Detect NAT behavior for TCP. Requires at least three STUN servers for accuracy.
  353. if source_port == 0:
  354. source_port = get_free_port()
  355. mapped_addr_first = None
  356. count = 0
  357. for server_ip in self._stun_ip_tcp:
  358. if count >= 3:
  359. return True
  360. ret = self.tcp_test(server_ip, source_port)
  361. if ret is not None:
  362. source_addr, mapped_addr = ret
  363. if mapped_addr_first is not None and mapped_addr != mapped_addr_first:
  364. return False
  365. mapped_addr_first = ret[1]
  366. count += 1
  367. raise Exception("Not enough STUN servers available.")
  368. class HttpTestServer(object):
  369. # HTTP Server for testing purpose
  370. # On success, you can see the text "It works!".
  371. def __init__(self, listen_addr, logger = None):
  372. self.logger = logger if logger else Logger()
  373. self.running = False
  374. self.listen_addr = listen_addr
  375. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  376. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  377. if "SO_REUSEPORT" in dir(socket):
  378. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  379. def run(self):
  380. self.running = True
  381. self.sock.bind(self.listen_addr)
  382. self.sock.listen(5)
  383. while self.running:
  384. try:
  385. conn, addr = self.sock.accept()
  386. self.logger.debug("HttpTestServer got client %s" % (addr,))
  387. except Exception:
  388. return
  389. try:
  390. conn.recv(4096)
  391. conn.sendall(b"HTTP/1.1 200 OK\r\n")
  392. conn.sendall(b"Content-Type: text/html\r\n")
  393. conn.sendall(b"\r\n")
  394. conn.sendall(b"<h1>It works!</h1><hr/>Natter\r\n")
  395. conn.shutdown(socket.SHUT_RDWR)
  396. except Exception:
  397. pass
  398. finally:
  399. conn.close()
  400. def start(self):
  401. self.logger.info("HttpTestServer starting...")
  402. threading.Thread(target=self.run).start()
  403. def stop(self):
  404. self.logger.info("HttpTestServer stopping...")
  405. self.running = False
  406. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  407. sock.settimeout(0.1)
  408. sock.connect_ex(self.listen_addr)
  409. sock.close()
  410. self.sock.close()
  411. class TCPForwarder(object):
  412. def __init__(self, listen_addr, forward_addr, logger = None):
  413. self.listen_sock = None
  414. self.listen_addr = listen_addr
  415. self.forward_addr = forward_addr
  416. self.logger = logger if logger else Logger()
  417. self.stopped = False
  418. def run(self):
  419. self.stopped = False
  420. self.listen_sock = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  421. s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  422. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  423. if "SO_REUSEPORT" in dir(socket):
  424. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  425. s.bind(self.listen_addr)
  426. s.settimeout(None)
  427. s.listen(5)
  428. while not self.stopped:
  429. try:
  430. client_sock, client_addr = s.accept()
  431. self.logger.debug("Got client: %s" % (client_addr,))
  432. except Exception as e:
  433. self.logger.debug("Cannot accept client: %s: %s" % (e.__class__.__name__, e))
  434. continue
  435. server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  436. server_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  437. try:
  438. server_sock.settimeout(3)
  439. server_sock.connect(self.forward_addr)
  440. server_sock.settimeout(None)
  441. except Exception as e:
  442. self.logger.debug("Cannot connect to forward_addr: %s: %s" % (e.__class__.__name__, e))
  443. client_sock.close()
  444. server_sock.close()
  445. threading.Thread(target=self._forward, args=(client_sock, server_sock)).start()
  446. threading.Thread(target=self._forward, args=(server_sock, client_sock)).start()
  447. @staticmethod
  448. def _forward(s1, s2):
  449. data = "..."
  450. try:
  451. while data:
  452. data = s1.recv(1024)
  453. if data:
  454. s2.sendall(data)
  455. else:
  456. s1.shutdown(socket.SHUT_RD)
  457. s2.shutdown(socket.SHUT_WR)
  458. except Exception:
  459. s1.close()
  460. s2.close()
  461. def start(self):
  462. threading.Thread(target=self.run).start()
  463. def stop(self):
  464. if self.stopped:
  465. return
  466. self.stopped = True
  467. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  468. sock.settimeout(0.1)
  469. sock.connect_ex(self.listen_addr)
  470. sock.close()
  471. self.listen_sock.close()
  472. self.listen_sock = None
  473. class UDPForwarder(object):
  474. def __init__(self, listen_sock, listen_addr, forward_addr, logger):
  475. self.listen_sock = listen_sock
  476. self.listen_addr = listen_addr
  477. self.forward_addr = forward_addr
  478. self.logger = logger if logger else Logger()
  479. self.stopped = False
  480. self.client_last = {}
  481. self.srv_socks = {}
  482. self.udp_timeout = 90
  483. def run(self):
  484. self.stopped = False
  485. while not self.stopped:
  486. try:
  487. data, client_addr = self.listen_sock.recvfrom(2048)
  488. except socket.timeout:
  489. continue
  490. self.client_last[client_addr] = time.time()
  491. server_sock = self.srv_socks.get(client_addr)
  492. if data and server_sock is None:
  493. self.logger.debug("Got client: %s" % (client_addr,))
  494. self.srv_socks[client_addr] = server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  495. threading.Thread(target=self._udp_forward, args=(client_addr,)).start()
  496. if server_sock:
  497. server_sock.sendto(data, self.forward_addr)
  498. def _udp_forward(self, client_addr):
  499. server_sock = self.srv_socks[client_addr]
  500. server_sock.settimeout(self.udp_timeout)
  501. data = "..."
  502. try:
  503. while data:
  504. time_diff = time.time() - self.client_last[client_addr]
  505. if time_diff > self.udp_timeout:
  506. server_sock.sendto("", self.forward_addr)
  507. raise socket.timeout("client timeout")
  508. data, server_addr = server_sock.recvfrom(2048)
  509. self.listen_sock.sendto(data, client_addr)
  510. except Exception:
  511. pass
  512. finally:
  513. server_sock.close()
  514. del self.client_last[client_addr]
  515. del self.srv_socks[client_addr]
  516. def start(self):
  517. threading.Thread(target=self.run).start()
  518. def stop(self):
  519. if self.stopped:
  520. return
  521. self.stopped = True
  522. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  523. sock.sendto(b"", self.listen_addr)
  524. sock.close()
  525. class NatterTCP(object):
  526. def __init__(self, source_addr, forward_addr, keep_alive_host, logger = None):
  527. self.logger = logger if logger else Logger()
  528. self.stun_client = StunClient(source_addr[0], logger = self.logger)
  529. self.forwarder = TCPForwarder(source_addr, forward_addr, logger = self.logger)
  530. self.source_addr = source_addr
  531. self.forward_addr = forward_addr
  532. self.keep_alive_host = keep_alive_host
  533. self.keep_alive_sock = None
  534. self.forward_running = False
  535. def keep_alive(self, timeout = 1):
  536. # Note:
  537. # The only purpose of this method is to keep the outgoing TCP connection from being closed.
  538. # Natter will send a HEAD HTTP request with keep-alive header to the target host.
  539. # We don't want to disturb the host too much, and meanwhile we will get minimal return data this way.
  540. s = self.keep_alive_sock
  541. try:
  542. if s is None:
  543. self.keep_alive_sock = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  544. s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  545. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  546. if "SO_REUSEPORT" in dir(socket):
  547. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  548. s.bind(self.source_addr)
  549. s.settimeout(timeout)
  550. s.connect((self.keep_alive_host, 80))
  551. s.sendall(b"HEAD / HTTP/1.1\r\n")
  552. s.sendall(b"Host: %s\r\n" % self.keep_alive_host.encode())
  553. s.sendall(b"User-Agent: Mozilla/5.0 (%s; %s) Natter\r\n" % (sys.platform.encode(), os.name.encode()))
  554. s.sendall(b"Accept: */*\r\n")
  555. s.sendall(b"Connection: keep-alive\r\n")
  556. s.sendall(b"\r\n")
  557. received = b""
  558. conn_closed = False
  559. while b"\r\n\r\n" not in received and not conn_closed:
  560. received = received[-4:] + s.recv(4096)
  561. conn_closed = (len(received) == 0)
  562. if not conn_closed:
  563. self.logger.debug("[%s] Keep-Alive OK!" % time.asctime())
  564. return True
  565. else:
  566. raise socket.error("Server closed connection")
  567. except Exception as e:
  568. self.logger.debug("Cannot TCP keep-alive: %s: %s" % (e.__class__.__name__, e))
  569. if self.keep_alive_sock is None:
  570. return False
  571. try:
  572. # Explicitly shut down the socket
  573. self.keep_alive_sock.shutdown(socket.SHUT_RDWR)
  574. except Exception:
  575. pass
  576. self.keep_alive_sock.close()
  577. # Set self.keep_alive_sock to None so the keep-alive connection will be re-established the
  578. # next time keep_alive() is called.
  579. self.keep_alive_sock = None
  580. return False
  581. def get_mapping(self):
  582. try:
  583. return self.stun_client.get_tcp_mapping(self.source_addr[1])
  584. except Exception as e:
  585. self.logger.debug("Cannot get TCP mapping: %s: %s" % (e.__class__.__name__, e))
  586. return None
  587. def start_forward(self):
  588. if not self.forward_running:
  589. self.forwarder.start()
  590. self.forward_running = True
  591. def stop_forward(self):
  592. if self.forward_running:
  593. self.forwarder.stop()
  594. self.forward_running = False
  595. class NatterUDP(object):
  596. def __init__(self, source_addr, forward_addr, keep_alive_host, logger = None):
  597. self.base_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  598. self.base_sock.bind(source_addr)
  599. self.logger = logger if logger else Logger()
  600. self.stun_client = StunClient(source_addr[0], logger = self.logger)
  601. self.forwarder = UDPForwarder(self.base_sock, source_addr, forward_addr, logger = self.logger)
  602. self.source_addr = source_addr
  603. self.forward_addr = forward_addr
  604. self.keep_alive_host = keep_alive_host
  605. self.forward_running = False
  606. def keep_alive(self):
  607. # Note:
  608. # Natter will send a message to port 30003 of the target's UDP, regardless of
  609. # whether the target replies to it.
  610. try:
  611. self.base_sock.sendto(b"hello", (self.keep_alive_host, 30003))
  612. self.logger.debug("[%s] Keep-Alive OK!" % time.asctime())
  613. return True
  614. except Exception as e:
  615. self.logger.debug("Cannot UDP keep-alive: %s: %s" % (e.__class__.__name__, e))
  616. return False
  617. def get_mapping(self):
  618. fwd = self.forward_running
  619. if fwd:
  620. # Temporarily stop port forwarding to avoid interference.
  621. self.stop_forward()
  622. try:
  623. return self.stun_client.get_udp_mapping(self.source_addr[1], custom_sock = self.base_sock)
  624. except Exception as e:
  625. self.logger.debug("Cannot get UDP mapping: %s: %s" % (e.__class__.__name__, e))
  626. return None
  627. finally:
  628. if fwd:
  629. self.start_forward()
  630. def start_forward(self):
  631. if not self.forward_running:
  632. self.forwarder.start()
  633. self.forward_running = True
  634. def stop_forward(self):
  635. if self.forward_running:
  636. self.forwarder.stop()
  637. self.forward_running = False
  638. class Natter(object):
  639. def __init__(self, keep_alive_host, interval = 10, logger = None):
  640. self.logger = logger if logger else Logger()
  641. self.nr_list = []
  642. self.keep_alive_host = keep_alive_host
  643. self.interval = interval
  644. self.hook_command = None
  645. self.status_file = None
  646. self.maps = {"tcp": {}, "udp": {}}
  647. def __del__(self):
  648. self.close()
  649. def add_tcp_open_port(self, source_addr):
  650. self.nr_list.append(NatterTCP(source_addr, None, self.keep_alive_host, logger = self.logger))
  651. def add_udp_open_port(self, source_addr):
  652. self.nr_list.append(NatterUDP(source_addr, None, self.keep_alive_host, logger = self.logger))
  653. def add_tcp_forward_port(self, forward_addr):
  654. source_addr = ("0.0.0.0", get_free_port())
  655. self.nr_list.append(NatterTCP(source_addr, forward_addr, self.keep_alive_host, logger = self.logger))
  656. def add_udp_forward_port(self, forward_addr):
  657. source_addr = ("0.0.0.0", get_free_port(udp=True))
  658. self.nr_list.append(NatterUDP(source_addr, forward_addr, self.keep_alive_host, logger = self.logger))
  659. def set_hook(self, hook_command):
  660. self.hook_command = hook_command
  661. def set_status_file(self, status_file_path):
  662. self.status_file = open(status_file_path, "w+")
  663. def execute_hook(self, inner_addr, outer_addr, protocol, command):
  664. inner_ip, inner_port = inner_addr
  665. outer_ip, outer_port = outer_addr
  666. command = command.replace("{inner_ip}", str(inner_ip))
  667. command = command.replace("{inner_port}", str(inner_port))
  668. command = command.replace("{outer_ip}", str(outer_ip))
  669. command = command.replace("{outer_port}", str(outer_port))
  670. command = command.replace("{protocol}", str(protocol))
  671. os.system(command)
  672. def update_status_file(self):
  673. status = {"tcp": [], "udp": []}
  674. for protocol in status:
  675. for inner_ip, inner_port in self.maps[protocol]:
  676. outer_ip, outer_port = self.maps[protocol][inner_ip, inner_port]
  677. record = {
  678. "inner": "%s:%d" % (inner_ip, inner_port),
  679. "outer": "%s:%d" % (outer_ip, outer_port),
  680. }
  681. status[protocol].append(record)
  682. self.status_file.seek(0)
  683. self.status_file.truncate(0)
  684. json.dump(status, self.status_file, indent = 4)
  685. self.status_file.flush()
  686. def _update_status(self, nr):
  687. mapping = nr.get_mapping()
  688. if not mapping:
  689. return
  690. # update mapping dict
  691. protocol = "tcp" if type(nr) is NatterTCP else "udp"
  692. inner_addr, outer_addr = mapping
  693. if nr.forward_addr:
  694. inner_addr = nr.forward_addr
  695. if inner_addr not in self.maps[protocol] or self.maps[protocol][inner_addr] != outer_addr:
  696. self.maps[protocol][inner_addr] = outer_addr
  697. self.logger.info(">>> [%s] %s -> %s <<<" % (protocol.upper(), inner_addr, outer_addr))
  698. # update status file
  699. if self.status_file:
  700. self.update_status_file()
  701. # excute hook command
  702. if self.hook_command:
  703. threading.Thread(
  704. target = self.execute_hook,
  705. args = (inner_addr, outer_addr, protocol, self.hook_command)
  706. ).start()
  707. def run(self):
  708. last_ok = {}
  709. for nr in self.nr_list:
  710. last_ok[nr] = False
  711. nr.keep_alive()
  712. if nr.forward_addr:
  713. nr.start_forward()
  714. while True:
  715. for nr in self.nr_list:
  716. if not last_ok[nr]:
  717. self._update_status(nr)
  718. last_ok[nr] = nr.keep_alive()
  719. time.sleep(self.interval)
  720. self.logger.debug("Current threads: %s" % threading.active_count())
  721. @staticmethod
  722. def from_config(config_path):
  723. fo = open(config_path)
  724. config = json.load(fo)
  725. fo.close()
  726. log_level = {
  727. "debug": Logger.DEBUG,
  728. "info": Logger.INFO,
  729. "warning": Logger.WARNING,
  730. "error": Logger.ERROR
  731. }[config["logging"]["level"]]
  732. log_file = config["logging"]["log_file"]
  733. if log_file:
  734. logger = Logger(log_level, files=(sys.stderr, open(log_file, "a")))
  735. else:
  736. logger = Logger(log_level)
  737. StunClient.stun_server_tcp = config["stun_server"]["tcp"]
  738. StunClient.stun_server_udp = config["stun_server"]["udp"]
  739. keep_alive_host = config["keep_alive"]
  740. natter = Natter(keep_alive_host, interval=10, logger=logger)
  741. hook = config["status_report"]["hook"]
  742. if hook:
  743. natter.set_hook(hook)
  744. statfile = config["status_report"]["status_file"]
  745. if statfile:
  746. natter.set_status_file(statfile)
  747. for addr_str in config["open_port"]["tcp"]:
  748. ip, port_str = addr_str.split(":")
  749. port = int(port_str)
  750. natter.add_tcp_open_port((ip, port))
  751. for addr_str in config["open_port"]["udp"]:
  752. ip, port_str = addr_str.split(":")
  753. port = int(port_str)
  754. natter.add_udp_open_port((ip, port))
  755. for addr_str in config["forward_port"]["tcp"]:
  756. ip, port_str = addr_str.split(":")
  757. port = int(port_str)
  758. natter.add_tcp_forward_port((ip, port))
  759. for addr_str in config["forward_port"]["udp"]:
  760. ip, port_str = addr_str.split(":")
  761. port = int(port_str)
  762. natter.add_udp_forward_port((ip, port))
  763. return natter
  764. def close(self):
  765. for nr in self.nr_list:
  766. nr.stop_forward()
  767. if self.status_file:
  768. self.status_file.close()
  769. def print_nat(source_ip = "0.0.0.0", source_port = 0):
  770. logger = Logger()
  771. stun_client = StunClient(source_ip, logger = logger)
  772. nat_type = stun_client.check_nat_type(source_port)
  773. if nat_type == StunClient.NAT_OPEN_INTERNET:
  774. nat_type_txt = "Open Internet"
  775. elif nat_type == StunClient.NAT_SYM_UDP_FIREWALL:
  776. nat_type_txt = "Symmetric UDP firewall"
  777. elif nat_type == StunClient.NAT_FULL_CONE:
  778. nat_type_txt = "Full cone (NAT 1)"
  779. elif nat_type == StunClient.NAT_RESTRICTED:
  780. nat_type_txt = "Restricted (NAT 2)"
  781. elif nat_type == StunClient.NAT_PORT_RESTRICTED:
  782. nat_type_txt = "Port restricted (NAT 3)"
  783. elif nat_type == StunClient.NAT_SYMMETRIC:
  784. nat_type_txt = "Symmetric (NAT 4)"
  785. else:
  786. nat_type_txt = "Unknown"
  787. logger.info("NAT Type for UDP: [ %s ]" % nat_type_txt)
  788. if nat_type == StunClient.NAT_OPEN_INTERNET:
  789. logger.warning("It looks like you are not in a NAT network, so there is no need to use this tool.")
  790. elif nat_type != StunClient.NAT_FULL_CONE:
  791. logger.warning("The NAT type of your network is not full cone (NAT 1). TCP hole punching may fail.")
  792. logger.info("Checking NAT Type for TCP...")
  793. if stun_client.is_tcp_cone():
  794. logger.info("NAT Type for TCP: [ Cone NAT ]")
  795. else:
  796. logger.info("NAT Type for TCP: [ Symmetric ]")
  797. logger.warning("You cannot perform TCP hole punching in a symmetric NAT network.")
  798. return
  799. def main():
  800. try:
  801. config_path = ""
  802. src_host = "0.0.0.0"
  803. src_port = -1
  804. verbose = False
  805. test_http = False
  806. use_config = False
  807. check_nat = False
  808. l = []
  809. for arg in sys.argv[1:]:
  810. if arg[0] == "-":
  811. if arg == "-c":
  812. use_config = True
  813. elif arg == "-v":
  814. verbose = True
  815. elif arg == "-t":
  816. test_http = True
  817. elif arg == "--check-nat":
  818. check_nat = True
  819. else:
  820. raise ValueError
  821. else:
  822. l.append(arg)
  823. if not use_config:
  824. if len(l) == 0 and check_nat:
  825. src_port = 0
  826. elif len(l) == 1:
  827. src_port = int(l[0])
  828. elif len(l) == 2:
  829. src_host = l[0]
  830. src_port = int(l[1])
  831. else:
  832. raise ValueError
  833. else:
  834. if len(l) == 1:
  835. config_path = l[0]
  836. if not os.path.exists(config_path):
  837. print("Config file not found.")
  838. raise ValueError
  839. else:
  840. raise ValueError
  841. except ValueError:
  842. print(
  843. "Usage: \n"
  844. " python natter.py [-v] [-t] [SRC_IP] SRC_PORT\n"
  845. " python natter.py --check-nat [SRC_IP] SRC_PORT\n"
  846. " python natter.py --check-nat\n"
  847. " python natter.py -c config_file\n"
  848. )
  849. return
  850. if check_nat:
  851. print_nat(src_host, src_port)
  852. return
  853. http_test_server = None
  854. if not use_config:
  855. # TCP single port punching
  856. log_level = Logger.DEBUG if verbose else Logger.INFO
  857. natter = Natter("www.qq.com", interval=10, logger=Logger(log_level))
  858. natter.add_tcp_open_port((src_host, src_port))
  859. if test_http:
  860. http_test_server = HttpTestServer((src_host, src_port), logger=natter.logger)
  861. http_test_server.start()
  862. else:
  863. natter = Natter.from_config(config_path)
  864. try:
  865. natter.run()
  866. except KeyboardInterrupt:
  867. if http_test_server:
  868. http_test_server.stop()
  869. natter.logger.info("Exiting...")
  870. natter.close()
  871. os._exit(0)
  872. if __name__ == '__main__':
  873. main()