예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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)
예제 #9
0
    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)
예제 #10
0
    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)