PageRenderTime 52ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/telemetry/third_party/webpagereplay/trafficshaper.py

https://gitlab.com/jonnialva90/iridium-browser
Python | 186 lines | 148 code | 13 blank | 25 comment | 0 complexity | dfe06f5800e16123d4c35784b42b0777 MD5 | raw file
  1. #!/usr/bin/env python
  2. # Copyright 2010 Google Inc. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. import platformsettings
  17. import re
  18. # Mac has broken bandwitdh parsing, so double check the values.
  19. # On Mac OS X 10.6, "KBit/s" actually uses "KByte/s".
  20. BANDWIDTH_PATTERN = r'0|\d+[KM]?(bit|Byte)/s'
  21. class TrafficShaperException(Exception):
  22. pass
  23. class BandwidthValueError(TrafficShaperException):
  24. def __init__(self, value): # pylint: disable=super-init-not-called
  25. self.value = value
  26. def __str__(self):
  27. return 'Value, "%s", does not match regex: %s' % (
  28. self.value, BANDWIDTH_PATTERN)
  29. class TrafficShaper(object):
  30. """Manages network traffic shaping."""
  31. # Pick webpagetest-compatible values (details: http://goo.gl/oghTg).
  32. _UPLOAD_PIPE = '10' # Enforces overall upload bandwidth.
  33. _UPLOAD_QUEUE = '10' # Shares upload bandwidth among source ports.
  34. _UPLOAD_RULE = '5000' # Specifies when the upload queue is used.
  35. _DOWNLOAD_PIPE = '11' # Enforces overall download bandwidth.
  36. _DOWNLOAD_QUEUE = '11' # Shares download bandwidth among destination ports.
  37. _DOWNLOAD_RULE = '5100' # Specifies when the download queue is used.
  38. _QUEUE_SLOTS = 100 # Number of packets to queue.
  39. _BANDWIDTH_RE = re.compile(BANDWIDTH_PATTERN)
  40. def __init__(self,
  41. dont_use=None,
  42. host='127.0.0.1',
  43. ports=None,
  44. up_bandwidth='0',
  45. down_bandwidth='0',
  46. delay_ms='0',
  47. packet_loss_rate='0',
  48. init_cwnd='0',
  49. use_loopback=True):
  50. """Start shaping traffic.
  51. Args:
  52. host: a host string (name or IP) for the web proxy.
  53. ports: a list of ports to shape traffic on.
  54. up_bandwidth: Upload bandwidth
  55. down_bandwidth: Download bandwidth
  56. Bandwidths measured in [K|M]{bit/s|Byte/s}. '0' means unlimited.
  57. delay_ms: Propagation delay in milliseconds. '0' means no delay.
  58. packet_loss_rate: Packet loss rate in range [0..1]. '0' means no loss.
  59. init_cwnd: the initial cwnd setting. '0' means no change.
  60. use_loopback: True iff shaping is done on the loopback (or equiv) adapter.
  61. """
  62. assert dont_use is None # Force args to be named.
  63. self.host = host
  64. self.ports = ports
  65. self.up_bandwidth = up_bandwidth
  66. self.down_bandwidth = down_bandwidth
  67. self.delay_ms = delay_ms
  68. self.packet_loss_rate = packet_loss_rate
  69. self.init_cwnd = init_cwnd
  70. self.use_loopback = use_loopback
  71. if not self._BANDWIDTH_RE.match(self.up_bandwidth):
  72. raise BandwidthValueError(self.up_bandwidth)
  73. if not self._BANDWIDTH_RE.match(self.down_bandwidth):
  74. raise BandwidthValueError(self.down_bandwidth)
  75. self.is_shaping = False
  76. def __enter__(self):
  77. if self.use_loopback:
  78. platformsettings.setup_temporary_loopback_config()
  79. if self.init_cwnd != '0':
  80. platformsettings.set_temporary_tcp_init_cwnd(self.init_cwnd)
  81. try:
  82. ipfw_list = platformsettings.ipfw('list')
  83. if not ipfw_list.startswith('65535 '):
  84. logging.warn('ipfw has existing rules:\n%s', ipfw_list)
  85. self._delete_rules(ipfw_list)
  86. except Exception:
  87. pass
  88. if (self.up_bandwidth == '0' and self.down_bandwidth == '0' and
  89. self.delay_ms == '0' and self.packet_loss_rate == '0'):
  90. logging.info('Skipped shaping traffic.')
  91. return
  92. if not self.ports:
  93. raise TrafficShaperException('No ports on which to shape traffic.')
  94. ports = ','.join(str(p) for p in self.ports)
  95. half_delay_ms = int(self.delay_ms) / 2 # split over up/down links
  96. try:
  97. # Configure upload shaping.
  98. platformsettings.ipfw(
  99. 'pipe', self._UPLOAD_PIPE,
  100. 'config',
  101. 'bw', self.up_bandwidth,
  102. 'delay', half_delay_ms,
  103. )
  104. platformsettings.ipfw(
  105. 'queue', self._UPLOAD_QUEUE,
  106. 'config',
  107. 'pipe', self._UPLOAD_PIPE,
  108. 'plr', self.packet_loss_rate,
  109. 'queue', self._QUEUE_SLOTS,
  110. 'mask', 'src-port', '0xffff',
  111. )
  112. platformsettings.ipfw(
  113. 'add', self._UPLOAD_RULE,
  114. 'queue', self._UPLOAD_QUEUE,
  115. 'ip',
  116. 'from', 'any',
  117. 'to', self.host,
  118. self.use_loopback and 'out' or 'in',
  119. 'dst-port', ports,
  120. )
  121. self.is_shaping = True
  122. # Configure download shaping.
  123. platformsettings.ipfw(
  124. 'pipe', self._DOWNLOAD_PIPE,
  125. 'config',
  126. 'bw', self.down_bandwidth,
  127. 'delay', half_delay_ms,
  128. )
  129. platformsettings.ipfw(
  130. 'queue', self._DOWNLOAD_QUEUE,
  131. 'config',
  132. 'pipe', self._DOWNLOAD_PIPE,
  133. 'plr', self.packet_loss_rate,
  134. 'queue', self._QUEUE_SLOTS,
  135. 'mask', 'dst-port', '0xffff',
  136. )
  137. platformsettings.ipfw(
  138. 'add', self._DOWNLOAD_RULE,
  139. 'queue', self._DOWNLOAD_QUEUE,
  140. 'ip',
  141. 'from', self.host,
  142. 'to', 'any',
  143. 'out',
  144. 'src-port', ports,
  145. )
  146. logging.info('Started shaping traffic')
  147. except Exception:
  148. logging.error('Unable to shape traffic.')
  149. raise
  150. def __exit__(self, unused_exc_type, unused_exc_val, unused_exc_tb):
  151. if self.is_shaping:
  152. try:
  153. self._delete_rules()
  154. logging.info('Stopped shaping traffic')
  155. except Exception:
  156. logging.error('Unable to stop shaping traffic.')
  157. raise
  158. def _delete_rules(self, ipfw_list=None):
  159. if ipfw_list is None:
  160. ipfw_list = platformsettings.ipfw('list')
  161. existing_rules = set(
  162. r.split()[0].lstrip('0') for r in ipfw_list.splitlines())
  163. delete_rules = [r for r in (self._DOWNLOAD_RULE, self._UPLOAD_RULE)
  164. if r in existing_rules]
  165. if delete_rules:
  166. platformsettings.ipfw('delete', *delete_rules)