natpunch.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # Written by John Hoffman
  2. # derived from NATPortMapping.py by Yejun Yang
  3. # and from example code by Myers Carpenter
  4. # see LICENSE.txt for license information
  5. import socket
  6. from traceback import print_exc
  7. from subnetparse import IP_List
  8. from clock import clock
  9. from __init__ import createPeerID
  10. try:
  11. True
  12. except:
  13. True = 1
  14. False = 0
  15. DEBUG = False
  16. EXPIRE_CACHE = 30 # seconds
  17. ID = "BT-"+createPeerID()[-4:]
  18. try:
  19. import pythoncom, win32com.client
  20. _supported = 1
  21. except ImportError:
  22. _supported = 0
  23. class _UPnP1: # derived from Myers Carpenter's code
  24. # seems to use the machine's local UPnP
  25. # system for its operation. Runs fairly fast
  26. def __init__(self):
  27. self.map = None
  28. self.last_got_map = -10e10
  29. def _get_map(self):
  30. if self.last_got_map + EXPIRE_CACHE < clock():
  31. try:
  32. dispatcher = win32com.client.Dispatch("HNetCfg.NATUPnP")
  33. self.map = dispatcher.StaticPortMappingCollection
  34. self.last_got_map = clock()
  35. except:
  36. self.map = None
  37. return self.map
  38. def test(self):
  39. try:
  40. assert self._get_map() # make sure a map was found
  41. success = True
  42. except:
  43. success = False
  44. return success
  45. def open(self, ip, p):
  46. map = self._get_map()
  47. try:
  48. map.Add(p,'TCP',p,ip,True,ID)
  49. if DEBUG:
  50. print 'port opened: '+ip+':'+str(p)
  51. success = True
  52. except:
  53. if DEBUG:
  54. print "COULDN'T OPEN "+str(p)
  55. print_exc()
  56. success = False
  57. return success
  58. def close(self, p):
  59. map = self._get_map()
  60. try:
  61. map.Remove(p,'TCP')
  62. success = True
  63. if DEBUG:
  64. print 'port closed: '+str(p)
  65. except:
  66. if DEBUG:
  67. print 'ERROR CLOSING '+str(p)
  68. print_exc()
  69. success = False
  70. return success
  71. def clean(self, retry = False):
  72. if not _supported:
  73. return
  74. try:
  75. map = self._get_map()
  76. ports_in_use = []
  77. for i in xrange(len(map)):
  78. try:
  79. mapping = map[i]
  80. port = mapping.ExternalPort
  81. prot = str(mapping.Protocol).lower()
  82. desc = str(mapping.Description).lower()
  83. except:
  84. port = None
  85. if port and prot == 'tcp' and desc[:3] == 'bt-':
  86. ports_in_use.append(port)
  87. success = True
  88. for port in ports_in_use:
  89. try:
  90. map.Remove(port,'TCP')
  91. except:
  92. success = False
  93. if not success and not retry:
  94. self.clean(retry = True)
  95. except:
  96. pass
  97. class _UPnP2: # derived from Yejun Yang's code
  98. # apparently does a direct search for UPnP hardware
  99. # may work in some cases where _UPnP1 won't, but is slow
  100. # still need to implement "clean" method
  101. def __init__(self):
  102. self.services = None
  103. self.last_got_services = -10e10
  104. def _get_services(self):
  105. if not self.services or self.last_got_services + EXPIRE_CACHE < clock():
  106. self.services = []
  107. try:
  108. f=win32com.client.Dispatch("UPnP.UPnPDeviceFinder")
  109. for t in ( "urn:schemas-upnp-org:service:WANIPConnection:1",
  110. "urn:schemas-upnp-org:service:WANPPPConnection:1" ):
  111. try:
  112. conns = f.FindByType(t,0)
  113. for c in xrange(len(conns)):
  114. try:
  115. svcs = conns[c].Services
  116. for s in xrange(len(svcs)):
  117. try:
  118. self.services.append(svcs[s])
  119. except:
  120. pass
  121. except:
  122. pass
  123. except:
  124. pass
  125. except:
  126. pass
  127. self.last_got_services = clock()
  128. return self.services
  129. def test(self):
  130. try:
  131. assert self._get_services() # make sure some services can be found
  132. success = True
  133. except:
  134. success = False
  135. return success
  136. def open(self, ip, p):
  137. svcs = self._get_services()
  138. success = False
  139. for s in svcs:
  140. try:
  141. s.InvokeAction('AddPortMapping',['',p,'TCP',p,ip,True,ID,0],'')
  142. success = True
  143. except:
  144. pass
  145. if DEBUG and not success:
  146. print "COULDN'T OPEN "+str(p)
  147. print_exc()
  148. return success
  149. def close(self, p):
  150. svcs = self._get_services()
  151. success = False
  152. for s in svcs:
  153. try:
  154. s.InvokeAction('DeletePortMapping', ['',p,'TCP'], '')
  155. success = True
  156. except:
  157. pass
  158. if DEBUG and not success:
  159. print "COULDN'T OPEN "+str(p)
  160. print_exc()
  161. return success
  162. class _UPnP: # master holding class
  163. def __init__(self):
  164. self.upnp1 = _UPnP1()
  165. self.upnp2 = _UPnP2()
  166. self.upnplist = (None, self.upnp1, self.upnp2)
  167. self.upnp = None
  168. self.local_ip = None
  169. self.last_got_ip = -10e10
  170. def get_ip(self):
  171. if self.last_got_ip + EXPIRE_CACHE < clock():
  172. local_ips = IP_List()
  173. local_ips.set_intranet_addresses()
  174. try:
  175. for info in socket.getaddrinfo(socket.gethostname(),0,socket.AF_INET):
  176. # exception if socket library isn't recent
  177. self.local_ip = info[4][0]
  178. if local_ips.includes(self.local_ip):
  179. self.last_got_ip = clock()
  180. if DEBUG:
  181. print 'Local IP found: '+self.local_ip
  182. break
  183. else:
  184. raise ValueError('couldn\'t find intranet IP')
  185. except:
  186. self.local_ip = None
  187. if DEBUG:
  188. print 'Error finding local IP'
  189. print_exc()
  190. return self.local_ip
  191. def test(self, upnp_type):
  192. if DEBUG:
  193. print 'testing UPnP type '+str(upnp_type)
  194. if not upnp_type or not _supported or self.get_ip() is None:
  195. if DEBUG:
  196. print 'not supported'
  197. return 0
  198. pythoncom.CoInitialize() # leave initialized
  199. self.upnp = self.upnplist[upnp_type] # cache this
  200. if self.upnp.test():
  201. if DEBUG:
  202. print 'ok'
  203. return upnp_type
  204. if DEBUG:
  205. print 'tested bad'
  206. return 0
  207. def open(self, p):
  208. assert self.upnp, "must run UPnP_test() with the desired UPnP access type first"
  209. return self.upnp.open(self.get_ip(), p)
  210. def close(self, p):
  211. assert self.upnp, "must run UPnP_test() with the desired UPnP access type first"
  212. return self.upnp.close(p)
  213. def clean(self):
  214. return self.upnp1.clean()
  215. _upnp_ = _UPnP()
  216. UPnP_test = _upnp_.test
  217. UPnP_open_port = _upnp_.open
  218. UPnP_close_port = _upnp_.close
  219. UPnP_reset = _upnp_.clean