Ports.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import random, copy
  2. # information taken from https://www.cymru.com/jtk/misc/ephemeralports.html
  3. class PortRanges:
  4. # dynamic ports as listed by RFC 6056
  5. DYNAMIC_PORTS = range(49152, 65536)
  6. LINUX = range(32768, 61001)
  7. FREEBSD = range(10000, 65536)
  8. APPLE_IOS = DYNAMIC_PORTS
  9. APPLE_OSX = DYNAMIC_PORTS
  10. WINDOWS_7 = DYNAMIC_PORTS
  11. WINDOWS_8 = DYNAMIC_PORTS
  12. WINDOWS_VISTA = DYNAMIC_PORTS
  13. WINDOWS_XP = range(1024, 5001)
  14. # This class uses classes instead of functions so deepcloning works
  15. class PortSelectionStrategy:
  16. class sequential:
  17. def __init__(self):
  18. self.counter = -1
  19. # that function will always return a one higher counter than before,
  20. # restarting from the start once it reached the highest value
  21. def __call__(port_range):
  22. if self.counter == -1:
  23. self.counter = port_range.start
  24. port = counter
  25. self.counter += 1
  26. if self.counter == port_range.stop:
  27. self.counter = port_range.start
  28. return port
  29. class random:
  30. def __call__(port_range):
  31. return random.randrange(port_range.start, port_range.stop)
  32. class PortSelector:
  33. """
  34. This class simulates a port-selection-process. Instances keep a list of port-numbers they generated so
  35. the same port-number will not be generated again.
  36. """
  37. def __init__(self, port_range, select_function):
  38. """
  39. Create a PortSelector given a range of ports to choose from and a function that chooses the next port
  40. :param port_range: a range-object containing the range of ports to choose from
  41. :param select_function: a function that receives the port_range and selects a port
  42. """
  43. if len(port_range) == 0:
  44. raise ValueError("cannot choose from an empty range")
  45. if x.start not in range(1, 65536) or x.stop not in range(1, 65536):
  46. raise ValueError("port_range is no subset of the valid port-range")
  47. self.port_range = port_range
  48. self._select_port = select_function
  49. self.generated = []
  50. def select_port(self):
  51. # do this check to avoid endless loops
  52. if len(self.generated) == len(self.port_range):
  53. raise RuntimeError("All %i port numbers were already generated, no more can be generated" % len(self.port_range))
  54. while True:
  55. port = self._select_port(self.port_range)
  56. if port not in self.generated:
  57. self.generated.append(port)
  58. return port
  59. def is_port_in_use(self, port: int):
  60. return port in self.generated
  61. def undo_port_use(self, port: int):
  62. if port in self.generated:
  63. self.generated.remove(port)
  64. else:
  65. raise ValueError("Port %i is not in use and thus can not be undone" % port)
  66. def reduce_size(self, size: int):
  67. """
  68. Reduce the list of already generated ports to the last <size> generated.
  69. If size if bigger than the number of generated ports nothing happens.
  70. """
  71. self.generated = self.generated[-size:]
  72. def clear(self):
  73. """
  74. Clear the list of generated ports. As of now this does not reset the state of the selection-function
  75. """
  76. self.generated = []
  77. def clone(self):
  78. return copy.deepcopy(self)
  79. class ProtocolPortSelector:
  80. """
  81. This class contains a method to select ports for udp and tcp. It generally consists of the port-selectors, one
  82. for tcp and one for udp. For convenience this class has a __getattr__-method to call methods on both selectors
  83. at once. E.g, clear() does not exist for ProtocolPortSelector but it does for PortSelector, therefore
  84. protocolPortSelector.clear() will call clear for both port-selectors.
  85. """
  86. def __init__(self, port_range, select_tcp, select_udp = None):
  87. self.tcp = PortSelector(port_range, select_tcp)
  88. self.udp = PortSelector(port_range, select_udp or select_tcp)
  89. def get_tcp_generator(self):
  90. return self.tcp
  91. def get_udp_generator(self):
  92. return self.udp
  93. def select_port_tcp(self):
  94. return self.tcp.select_port()
  95. def select_port_udp(self):
  96. return self.udp.select_port()
  97. def is_port_in_use_tcp(self, port):
  98. return self.tcp.is_port_in_use(port)
  99. def is_port_in_use_udp(self, port):
  100. return self.udp.is_port_in_use(port)
  101. def clone(self):
  102. class Tmp: pass
  103. clone = Tmp()
  104. clone.__class__ = type(self)
  105. clone.udp = self.udp.clone()
  106. clone.tcp = self.tcp.clone()
  107. return clone
  108. def __getattr__(self, attr):
  109. val = getattr(self.tcp, attr)
  110. if callable(val): # we proprably got a method here
  111. tcp_meth = val
  112. udp_meth = getattr(self.udp, attr)
  113. def double_method(*args, **kwargs):
  114. return (tcp_meth(*args, **kwargs), udp_meth(*args, **kwargs))
  115. return double_method # calling this function will call the method for both port-selectors
  116. else: # we have found a simple value, return a tuple containing the attribute-value from both port-selectors
  117. return (val, getattr(self.udp, attr))
  118. class PortSelectors:
  119. """
  120. To save some time this class contains some of the port-selection-strategies found in the wild. It is recommend to use
  121. .clone() to get your personal copy, otherwise two parts of your code might select ports on the same port-selector which
  122. is something your might want to avoid.
  123. """
  124. LINUX = ProtocolPortSelector(PortRanges.LINUX, PortSelectionStrategy.random)
  125. APPLE = ProtocolPortSelector(PortRanges.DYNAMIC_PORTS,
  126. PortSelectionStrategy.sequential(),
  127. PortSelectionStrategy.random())
  128. FREEBSD = ProtocolPortSelector(PortRanges.FREEBSD, PortSelectionStrategy.random)
  129. WINDOWS = ProtocolPortSelector(PortRanges.WINDOWS_7, PortSelectionStrategy.random) # the selection strategy is a guess as i can't find more info on it