def _unset_filter(self, mark, eth): """Given a mark and an interface, delete the filter. Args: mark: The mark based on which we delete the filter. eth: The interface on which we delete the filter. Returns: A TrafficControlRc containing information on success/failure. """ ifid = eth['id'] parent = 0x10000 self.logger.info( "deleting filter on IFID {0}, handle {1:X}".format( eth['name'], mark ) ) try: self.ipr.tc( RTM_DELTFILTER, 'fw', ifid, mark, parent=parent, protocol=ETH_P_IP, prio=PRIO ) except NetlinkError as e: return TrafficControlRc( code=ReturnCode.NETLINK_FW_ERROR, message=str(e)) except Exception as e: self.logger.exception('_unset_filter') exc_info = sys.exc_info() return TrafficControlRc( code=ReturnCode.UNKNOWN_FW_ERROR, message=str(exc_info)) return TrafficControlRc(code=ReturnCode.OK)
def _unset_htb_class(self, mark, eth): """Given a mark and an interface, unset the HTB class. Args: mark: The mark based on which we delete the class. eth: The interface on which to delete that class id. Returns: A TrafficControlRc containing information on success/failure. """ ifid = eth['id'] idx = 0x10000 + mark try: self.logger.info( "deleting class on IFID {0}, classid {1}".format( eth['name'], int_to_classid(idx) ) ) self.ipr.tc(RTM_DELTCLASS, 'htb', ifid, idx) except NetlinkError as e: return TrafficControlRc( code=ReturnCode.NETLINK_HTB_ERROR, message=str(e)) except Exception as e: self.logger.exception('_unset_htb_class') exc_info = sys.exc_info() return TrafficControlRc( code=ReturnCode.UNKNOWN_HTB_ERROR, message=str(exc_info)) return TrafficControlRc(code=ReturnCode.OK)
def stopShaping(self, dev): """Stop shaping a connection for a given traffic controlled device. Implements the `stopShaping` thrift method. Args: A TrafficControlledDevice object that contains the shaped device. Returns: A TrafficControlRc object with code and message set to reflect success/failure. Raises: A TrafficControlException with code and message set on uncaught exception. """ self.logger.info("Request stopShaping for ip {0}".format( dev.controlledIP)) try: socket.inet_aton(dev.controlledIP) except Exception as e: return TrafficControlRc(code=ReturnCode.INVALID_IP, message="Invalid IP {0}: {1}".format( dev.controlledIP, e)) id = self._ip_to_id_map.get(dev.controlledIP, None) shaping = self._current_shapings.get(dev.controlledIP, {}).get('tc') if id is not None: # unshaping up way for count, item in enumerate(shaping.settings.up, start=1): self._unshape_interface( id + count, self.wan, dev.controlledIP, item, ) # unshaping down way for count, item in enumerate(shaping.settings.down, start=1): self._unshape_interface( id + count, self.lan, dev.controlledIP, item, ) self._del_mapping(id, dev.controlledIP) self.db_task.queue.put((dev.controlledIP, 'remove_shaping')) self.idmanager.free(id) else: return TrafficControlRc( code=ReturnCode.UNKNOWN_SESSION, message="No session for IP {} found".format(dev.controlledIP)) return TrafficControlRc(code=ReturnCode.OK)
def _set_netem_qdisc(self, mark, eth, shaping): """Given a mark, interface and shaping settings, create the NetEm Qdisc. Args: mark: The mark based on which we create the Qdisc. eth: The interface on which we will create the Qdisc. shaping: The shaping settings for that interface. Returns: A TrafficControlRc containing information on success/failure. """ ifid = eth['id'] parent = 0x10000 + mark idx = 0 # automatically assign a handleid self.logger.info( "create new Netem qdisc on IFID {0}, parent {1}," " loss {2}%, delay {3}".format( eth['name'], int_to_classid(parent), shaping.loss.percentage, shaping.delay.delay * 1000) ) try: self.ipr.tc( RTM_NEWQDISC, 'netem', ifid, idx, parent=parent, loss=shaping.loss.percentage, delay=shaping.delay.delay * 1000, jitter=shaping.delay.jitter * 1000, delay_corr=shaping.delay.correlation, loss_corr=shaping.loss.correlation, prob_reorder=shaping.reorder.percentage, corr_reorder=shaping.reorder.correlation, gap=shaping.reorder.gap, prob_corrupt=shaping.corruption.percentage, corr_corrupt=shaping.corruption.correlation, ) except NetlinkError as e: return TrafficControlRc( code=ReturnCode.NETLINK_NETEM_ERROR, message=str(e)) except Exception as e: self.logger.exception('_set_netem_qdisc') exc_info = sys.exc_info() return TrafficControlRc( code=ReturnCode.UNKNOWN_NETEM_ERROR, message=str(exc_info)) return TrafficControlRc(code=ReturnCode.OK)
def _set_filter(self, mark, eth, shaping): """Given a mark, interface and shaping settings, create a TC filter. Args: mark: The mark based on which we create the filter. eth: The interface on which we create the filter. shaping: The shaping associated to this interface. Returns: A TrafficControlRc containing information on success/failure. """ ifid = eth['id'] idx = 0x10000 + mark parent = 0x10000 self.logger.info( "create new FW filter on IFID {0}, classid {1}," " handle {2:X}, rate: {3}kbits".format( eth['name'], int_to_classid(idx), mark, shaping.rate ) ) try: extra_args = {} if not self.dont_drop_packets: extra_args.update({ 'rate': "{}kbit".format(shaping.rate or 2**22 - 1), 'burst': self.burst_size, 'action': 'drop', }) self.ipr.tc(RTM_NEWTFILTER, 'fw', ifid, mark, parent=parent, protocol=ETH_P_IP, prio=PRIO, classid=idx, **extra_args ) except NetlinkError as e: return TrafficControlRc( code=ReturnCode.NETLINK_FW_ERROR, message=str(e)) except Exception as e: self.logger.exception('_set_filter') exc_info = sys.exc_info() return TrafficControlRc( code=ReturnCode.UNKNOWN_FW_ERROR, message=str(exc_info)) return TrafficControlRc(code=ReturnCode.OK)
def _shape_interface(self, mark, eth, ip, shaping): """Shape the traffic for a given interface. Shape the traffic for a given IP on a given interface, given the mark and the shaping settings. There is a few steps to shape the traffic of an IP: 1. Create an HTB class that limit the throughput. 2. Create a NetEm QDisc that adds corruption, loss, reordering, loss and delay. 3. Create the TC filter that will bucket packets with a given mark in the right HTB class. 4. Set an iptables rule that mark packets going to/coming from IP Args: mark: The mark to set on IP packets. eth: The network interface. ip: The IP to shape traffic for. shaping: The shaping setting to set. Returns: A TrafficControlRc containing information on success/failure. """ self.logger.info( "Shaping ip {0} on interface {1}".format(ip, eth['name'])) # HTB class tcrc = self._set_htb_class(mark, eth, shaping) if tcrc.code != ReturnCode.OK: self.logger.error( "adding HTB class on IFID {0}, mark {1}, err: {2}".format( eth['name'], mark, tcrc.message)) return tcrc # NetemQdisc tcrc = self._set_netem_qdisc(mark, eth, shaping) if tcrc.code != ReturnCode.OK: self.logger.error( "adding NetEm qdisc on IFID {0}, mark {1}, err: {2}".format( eth['name'], mark, tcrc.message)) # delete class self._unset_htb_class(mark, eth) return tcrc # filter tcrc = self._set_filter(mark, eth, shaping) if tcrc.code != ReturnCode.OK: self.logger.error( "adding filter FW on IFID {0}, mark {1}, err: {2}".format( eth['name'], mark, tcrc.message)) # delete class self._unset_htb_class(mark, eth) return tcrc # iptables self._set_iptables(mark, eth, ip, shaping.iptables_options) return TrafficControlRc(code=ReturnCode.OK)
def _set_htb_class(self, mark, eth, shaping): """Given a mark, an interface and shaping settings, set the HTB class. Args: mark: The mark based on which we create the class eth: The interface on which to create that class id. shaping: The shaping settings to set. Returns: A TrafficControlRc containing information on success/failure. """ ifid = eth['id'] idx = 0x10000 + mark parent = 0x10000 self.logger.info( "create new HTB class on IFID {0}, classid {1}," "parent {2}, rate {3}kbits".format( eth['name'], int_to_classid(idx), int_to_classid(parent), shaping.rate or 2**22 - 1) ) try: self.ipr.tc( RTM_NEWTCLASS, 'htb', ifid, idx, parent=parent, rate="{}kbit".format(shaping.rate or (2**22 - 1)), ) except NetlinkError as e: return TrafficControlRc( code=ReturnCode.NETLINK_HTB_ERROR, message=str(e)) except Exception as e: self.logger.exception('_set_htb_class') exc_info = sys.exc_info() return TrafficControlRc( code=ReturnCode.UNKNOWN_HTB_ERROR, message=str(exc_info)) return TrafficControlRc(code=ReturnCode.OK)
def _initialize_tc_for_interface(self, eth): """Initialize TC on a given interface. If an exception is thrown, it will be forwarded to the main loop unless it can be ignored. Args: eth: the interface to flush TC on. Raises: NetlinkError: An error occured initializing TC subsystem. Exception: Any other exception thrown during initialization. """ idx = 0x10000 eth_name = eth['name'] eth_id = eth['id'] try: self.logger.info("deleting root QDisc on {0}".format(eth_name)) self.ipr.tc(RTM_DELQDISC, None, eth_id, 0, parent=TC_H_ROOT) except Exception as e: # a (2, 'No such file or directory') can be thrown if there is # nothing to delete. Ignore such error, return the error otherwise if isinstance(e, NetlinkError) and e.code == 2: self.logger.warning( "could not delete root QDisc. There might " "have been nothing to delete") else: self.logger.exception( 'Initializing root Qdisc for {0}'.format(eth_name) ) raise try: self.logger.info("setting root qdisc on {0}".format(eth_name)) self.ipr.tc(RTM_NEWQDISC, "htb", eth_id, idx, default=0) except Exception as e: self.logger.exception( 'Setting root Qdisc for {0}'.format(eth_name) ) raise return TrafficControlRc(code=ReturnCode.OK)
def _unshape_interface(self, mark, eth, ip, settings): """Unshape the traffic for a given interface. Unshape the traffic for a given IP on a given interface, given the mark and the shaping settings. There is a few steps to unshape the traffic of an IP: 1. Remove the iptables rule. 2. Remove the TC filter. 3. Remove the HTB class. Args: mark: The mark to set on IP packets. eth: The network interface. ip: The IP to shape traffic for. shaping: The shaping setting to set. Returns: A TrafficControlRc containing information on success/failure. """ self.logger.info( "Unshaping ip {0} on interface {1}".format(ip, eth['name'])) # iptables self._unset_iptables(mark, eth, ip, settings.iptables_options) # filter tcrc = self._unset_filter(mark, eth) if tcrc.code != ReturnCode.OK: self.logger.error( "deleting FW filter on IFID {0}, mark {1}, err: {2}".format( eth['name'], mark, tcrc.message) ) return tcrc # HTB class tcrc = self._unset_htb_class(mark, eth) if tcrc.code != ReturnCode.OK: self.logger.error( "deleting HTB class on IFID {0}, mark {1}, err: {2}".format( eth['name'], mark, tcrc.message) ) return tcrc return TrafficControlRc(code=ReturnCode.OK)
def startShaping(self, tc): """Start shaping a connection for a given device. Implements the `startShaping` thrift method. If the connection is already being shaped, the shaping will be updated and the old one deleted. Args: A TrafficControl object that contains the device to be shaped, the settings and the timeout. Returns: A TrafficControlRc object with code and message set to reflect success/failure. Raises: A TrafficControlException with code and message set on uncaught exception. """ self.logger.info("Request startShaping {0}".format(tc)) # Sanity checking # IP try: socket.inet_aton(tc.device.controlledIP) except Exception as e: return TrafficControlRc(code=ReturnCode.INVALID_IP, message="Invalid IP {}".format( tc.device.controlledIP)) # timer if tc.timeout < 0: return TrafficControlRc(code=ReturnCode.INVALID_TIMEOUT, message="Invalid Timeout {}".format( tc.timeout)) new_id = None try: new_id = self.idmanager.new() except Exception as e: return TrafficControlRc( code=ReturnCode.ID_EXHAUST, message="No more session available: {0}".format(e)) old_id = self._ip_to_id_map.get(tc.device.controlledIP, None) old_settings = self._current_shapings.get(tc.device.controlledIP, {}).get('tc') # start shaping for up way for count, item in enumerate(tc.settings.up, start=1): self.logger.info("up: start Shaping for item: {0}".format(item)) tcrc = self._shape_interface( new_id + count, self.wan, tc.device.controlledIP, item, ) if tcrc.code != ReturnCode.OK: return tcrc # start shaping for down way for count, item in enumerate(tc.settings.down, start=1): self.logger.info("down: start Shaping for item: {0}".format(item)) tcrc = self._shape_interface( new_id + count, self.lan, tc.device.controlledIP, item, ) # If we failed to set shaping for LAN interfaces, we should remove # the shaping we just created for the WAN if tcrc.code != ReturnCode.OK: for count, item in enumerate(tc.settings.up, start=1): self._unshape_interface( old_id + count, self.wan, tc.device.controlledIP, item, ) return tcrc self._add_mapping(new_id, tc) self.db_task.queue.put(((tc, tc.timeout + time.time()), 'add_shaping')) # if there were an existing id, remove it from dict if old_id is not None: # unshaping up way for count, item in enumerate(old_settings.settings.up, start=1): self._unshape_interface( old_id + count, self.wan, tc.device.controlledIP, item, ) # unshaping down way for count, item in enumerate(old_settings.settings.down, start=1): self._unshape_interface( old_id + count, self.lan, tc.device.controlledIP, item, ) del self._id_to_ip_map[old_id] self.idmanager.free(old_id) return TrafficControlRc(code=ReturnCode.OK)