Ejemplo n.º 1
0
class ACLSwitch(ABCRyuApp):
    """Main class for ACLSwitch.
    """

    _APP_NAME = "ACLSwitch"
    _CONFIG_POLICIES_FILE_NAME = "policies.json"
    _CONFIG_RULE_FILE_NAME = "rules.json"
    _CONFIG_TIME_RULE_FILE_NAME = "time_rules.json"
    _EXPECTED_HANDLERS = (EventOFPSwitchFeatures.__name__, )
    _INSTANCE_NAME_ASW_API = "asw_api"
    # Default priority is defined to be in the middle (0x8000 in 1.3)
    # Note that for a priority p, 0 <= p <= MAX (i.e. 65535)
    _OFP_MAX_PRIORITY = ofproto_v1_3.OFP_DEFAULT_PRIORITY * 2 - 1
    _POLICY_DEFAULT = "default"
    _RULE_TCP = "tcp"
    _RULE_UDP = "udp"
    _RULE_WILDCARD = "*"
    # TODO Table IDs should be obtained from an application with higher knowledge.
    # An api call to the controller. ACLSwitch should have no idea what
    # what other apps have what table IDS, just what table to forward
    # entries onto.
    _VERSION = "1.0"

    def __init__(self, contr):
        # Load config
        path_to_config = os.path.dirname(__file__) + "/config/"
        self._config = ConfigLoader(
            path_to_config + self._CONFIG_POLICIES_FILE_NAME,
            path_to_config + self._CONFIG_RULE_FILE_NAME,
            path_to_config + self._CONFIG_TIME_RULE_FILE_NAME)
        # Set logging
        logging_config = self._config.get_logging_config()
        self._logging = logging.getLogger(__name__)
        self._logging.setLevel(logging_config["min_lvl"])
        self._logging.propagate = logging_config["propagate"]
        self._logging.addHandler(logging_config["handler"])

        # Set flow table numbers
        self._table_id_blacklist = 0
        self._table_id_whitelist = 1
        self._table_id_next = 2

        self._contr = contr
        self._supported = self._verify_contr_handlers()
        self._logging.info("Starting ACLSwitch...")

        # Create objects to manage different features
        self._flow_man = FlowManager(self, logging_config)
        self._api = ACLSwitchAPI(logging_config, self._VERSION, self._flow_man)

        self._api.policy_create(self._POLICY_DEFAULT)

        # Read config files
        # TODO Command line argument for custom location for config file
        policies = self._config.load_policies()
        rules = self._config.load_rules()
        time_rules = self._config.load_time_rules()
        for pol in policies:
            self._api.policy_create(pol)
        for rule in rules:
            self._api.acl_create_rule(rule)
        for rule in time_rules:
            self._api.acl_create_rule(rule)

        # Register REST WSGI through the controller app
        self._contr.register_rest_wsgi(
            ACLSwitchREST, kwargs={self._INSTANCE_NAME_ASW_API: self._api})

        self._logging.info("ACLSwitch started successfully.")

    def add_acl_fte(self, switch_id, rule):
        """Add a rule to the flow table as a flow table entry.

        :param switch_id: The switch to add an entry to.
        :param rule: The rule to add.
        """
        datapath = self._contr.switch_get_datapath(switch_id)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        priority = self._OFP_MAX_PRIORITY
        if rule.action == "drop":
            actions = []
            inst = [
                parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)
            ]
            table = self._table_id_blacklist
        else:
            inst = [parser.OFPInstructionGotoTable(self._table_id_next)]
            table = self._table_id_whitelist
        match = self._create_match(rule)
        if rule.time_enforce == "N/A":
            hard_timeout = 0
        else:
            hard_timeout = rule.time_enforce[1]
        self._contr.add_flow(datapath, priority, match, inst, hard_timeout,
                             table)

    def remove_acl_fte(self, switch_id, rule):
        """Remove a flow table entry.

        :param switch_id: The switch to remove the entry from.
        :param rule: The rule to remove.
        """
        datapath = self._contr.switch_get_datapath(switch_id)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        table = self._table_id_blacklist if rule.action == "drop" else\
            self._table_id_whitelist
        remove_type = ofproto.OFPFC_DELETE_STRICT
        priority = self._OFP_MAX_PRIORITY
        match = self._create_match(rule)
        out_port = ofproto.OFPP_ANY
        out_group = ofproto.OFPG_ANY
        self._contr.remove_flow(datapath, parser, table, remove_type, priority,
                                match, out_port, out_group)

    def _create_match(self, rule):
        """Create an OFPMatch instance based on the contents of an
        ACL_ENTRY.

        :param rule: The rule entry to create an OFPMatch instance from.
        :return: The OFPMatch instance.
        """
        match = ofp13_parser.OFPMatch()
        ip_version = self._return_ip_version(rule.ip_src, rule.ip_dst)
        # Match IP layer (layer 3)
        if ip_version == 4:
            # Match IPv4
            match.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE,
                               ethernet.ether.ETH_TYPE_IP)
            if rule.ip_src != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC,
                                   int(IPAddress(rule.ip_src)))
            if rule.ip_dst != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV4_DST,
                                   int(IPAddress(rule.ip_dst)))
        else:
            # Match IPv6
            match.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE,
                               ethernet.ether.ETH_TYPE_IPV6)
            if rule.ip_src != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV6_SRC,
                                   IPAddress(rule.ip_src).words)
            if rule.ip_dst != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV6_DST,
                                   IPAddress(rule.ip_dst).words)

        # Match transport layer (layer 4)
        if rule.tp_proto != self._RULE_WILDCARD:
            if rule.tp_proto == self._RULE_TCP:
                # Match TCP
                match.append_field(ofproto_v1_3.OXM_OF_IP_PROTO,
                                   ipv4.inet.IPPROTO_TCP)  # covers IPv6
                if rule.port_src != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_TCP_SRC,
                                       int(rule.port_src))
                if rule.port_dst != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_TCP_DST,
                                       int(rule.port_dst))
            elif rule.tp_proto == self._RULE_UDP:
                # Match UDP
                match.append_field(ofproto_v1_3.OXM_OF_IP_PROTO,
                                   ipv4.inet.IPPROTO_UDP)  # covers IPv6
                if rule.port_src != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_UDP_SRC,
                                       int(rule.port_src))
                if rule.port_dst != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_UDP_DST,
                                       int(rule.port_dst))
        return match

    def _return_ip_version(self, ip_src, ip_dst):
        """Return the IP version being used given the source and
        destination addresses.

        :param ip_src: the source IP address to check.
        :param ip_dst: the destination IP address to check.
        :return: the IP version being used.
        """
        if self._RULE_WILDCARD not in ip_src:
            return IPAddress(ip_src).version
        else:
            return IPAddress(ip_dst).version

    def switch_features(self, event):
        """Process a switch features event from the controller.

        :param event: The OpenFlow event.
        """
        datapath = event.msg.datapath
        datapath_id = event.msg.datapath_id
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # Establish the flow table pipeline used by ACLSwitch. The
        # first flow table represents a blacklist for dropping flows
        # and the second flow table represents a whitelist for
        # allowing flows. We must set the appropriate table miss
        # entries and allow ARP traffic through the whitelist.

        # Table miss entries
        match = parser.OFPMatch()
        inst = [parser.OFPInstructionGotoTable(self._table_id_whitelist)]
        self._contr.add_flow(datapath, 0, match, inst, 0,
                             self._table_id_blacklist)
        actions = []
        inst = [
            parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)
        ]
        self._contr.add_flow(datapath, 0, match, inst, 0,
                             self._table_id_whitelist)
        # ARP entries
        inst = [parser.OFPInstructionGotoTable(self._table_id_next)]
        match = parser.OFPMatch(arp_op=arp.ARP_REQUEST,
                                eth_type=ethernet.ether.ETH_TYPE_ARP)
        self._contr.add_flow(datapath, 1, match, inst, 0,
                             self._table_id_whitelist)
        match = parser.OFPMatch(arp_op=arp.ARP_REPLY,
                                eth_type=ethernet.ether.ETH_TYPE_ARP)
        self._contr.add_flow(datapath, 1, match, inst, 0,
                             self._table_id_whitelist)

        # Take note of switches (via their datapaths IDs)
        self._api.switch_connect(datapath_id)
        self._api.policy_assign_switch(datapath_id, self._POLICY_DEFAULT)

    def get_app_name(self):
        return self._APP_NAME

    def get_expected_handlers(self):
        return self._EXPECTED_HANDLERS

    def is_supported(self):
        return self._supported

    def _verify_contr_handlers(self):
        contr_handlers = self._contr.get_ofpe_handlers()
        failures = ()
        for expected_h in self._EXPECTED_HANDLERS:
            if expected_h not in contr_handlers:
                failures = failures + (expected_h, )
        if not len(failures) == 0:
            fail_msg = ("{0}: The following OpenFlow protocol events "
                        "are not supported by the controller:".format(
                            self._APP_NAME))
            for f in failures:
                fail_msg += "\n\t- {0}".format(f)
            self._logging.critical(fail_msg)
            return False
        else:
            return True
Ejemplo n.º 2
0
class ACLSwitch(ABCRyuApp):
    """Main class for ACLSwitch.
    """

    _APP_NAME = "ACLSwitch"
    _CONFIG_POLICIES_FILE_NAME = "policy_domains.yaml"
    _CONFIG_RULE_FILE_NAME = "acl_rules.yaml"
    _EXPECTED_HANDLERS = (EventOFPSwitchFeatures.__name__, )
    _INSTANCE_NAME_ASW_API = "asw_api"
    # Default priority is defined to be in the middle (0x8000 in 1.3)
    # Note that for a priority p, 0 <= p <= MAX (i.e. 65535)
    _OFP_MAX_PRIORITY = ofproto_v1_3.OFP_DEFAULT_PRIORITY * 2 - 1
    _POLICY_DEFAULT = "default"
    _RULE_TCP = "tcp"
    _RULE_UDP = "udp"
    _RULE_WILDCARD = "*"
    # TODO Table IDs should be obtained from an application with higher knowledge.
    # An api call to the controller. ACLSwitch should have no idea what
    # what other apps have what table IDS, just what table to forward
    # entries onto.
    _VERSION = "1.1.0"

    def __init__(self, contr):
        # Load config
        path_to_config = os.path.dirname(__file__) + "/config/"
        self._config = ConfigLoader(path_to_config +
                                    self._CONFIG_POLICIES_FILE_NAME,
                                    path_to_config +
                                    self._CONFIG_RULE_FILE_NAME)
        # Set logging
        logging_config = self._config.get_logging_config()
        self._logging = logging.getLogger(__name__)
        self._logging.setLevel(logging_config["min_lvl"])
        self._logging.propagate = logging_config["propagate"]
        self._logging.addHandler(logging_config["handler"])

        # Set flow table numbers
        self._table_id_blacklist = 0
        self._table_id_whitelist = 1
        self._table_id_next = 2

        self._contr = contr
        self._supported = self._verify_contr_handlers()
        self._logging.info("Starting ACLSwitch...")

        # Create objects to manage different features
        self._flow_man = FlowManager(self, logging_config)
        self._api = ACLSwitchAPI(logging_config, self._VERSION,
                                 self._flow_man)
        self._api.policy_create(self._POLICY_DEFAULT)

        # Read config files
        # TODO Command line argument for custom location for config file
        policies, pd_assignments = self._config.load_policies()
        rules = self._config.load_rules()
        for pol in policies:
            self._api.policy_create(pol)
        for rule in rules:
            self._api.acl_create_rule(rule)
        for assignment in pd_assignments:
            self._api.switch_register(assignment["switch_id"])
            self._api.policy_assign_switch(assignment["switch_id"],
                                           assignment["policy"],
                                           from_file=True)

        # Register REST WSGI through the controller app
        self._contr.register_rest_wsgi(ACLSwitchREST, kwargs={
            self._INSTANCE_NAME_ASW_API: self._api})

        self._logging.info("ACLSwitch started successfully.")

    def add_acl_fte(self, switch_id, rule):
        """Add a rule to the flow table as a flow table entry.

        :param switch_id: The switch to add an entry to.
        :param rule: The rule to add.
        """
        datapath = self._contr.switch_get_datapath(switch_id)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        priority = self._OFP_MAX_PRIORITY
        if rule.action == "drop":
            actions = []
            inst = [parser.OFPInstructionActions(
                ofproto.OFPIT_APPLY_ACTIONS, actions)]
            table = self._table_id_blacklist
        else:
            inst = [parser.OFPInstructionGotoTable(self._table_id_next)]
            table = self._table_id_whitelist
        match = self._create_match(rule)
        if rule.time_enforce == "N/A":
            hard_timeout = 0
        else:
            hard_timeout = rule.time_enforce[1]
        self._contr.add_flow(datapath, priority, match, inst,
                             hard_timeout, table)

    def remove_acl_fte(self, switch_id, rule):
        """Remove a flow table entry.

        :param switch_id: The switch to remove the entry from.
        :param rule: The rule to remove.
        """
        datapath = self._contr.switch_get_datapath(switch_id)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        table = self._table_id_blacklist if rule.action == "drop" else\
            self._table_id_whitelist
        remove_type = ofproto.OFPFC_DELETE_STRICT
        priority = self._OFP_MAX_PRIORITY
        match = self._create_match(rule)
        out_port = ofproto.OFPP_ANY
        out_group = ofproto.OFPG_ANY
        self._contr.remove_flow(datapath, parser, table, remove_type,
                                priority, match, out_port, out_group)

    def _create_match(self, rule):
        """Create an OFPMatch instance based on the contents of an
        ACL_ENTRY.

        :param rule: The rule entry to create an OFPMatch instance from.
        :return: The OFPMatch instance.
        """
        match = ofp13_parser.OFPMatch()
        ip_version = self._return_ip_version(rule.ip_src, rule.ip_dst)
        # Match IP layer (layer 3)
        if ip_version == 4:
            # Match IPv4
            match.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE,
                               ethernet.ether.ETH_TYPE_IP)
            if rule.ip_src != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC,
                                   int(IPAddress(rule.ip_src)))
            if rule.ip_dst != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV4_DST,
                                   int(IPAddress(rule.ip_dst)))
        else:
            # Match IPv6
            match.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE,
                               ethernet.ether.ETH_TYPE_IPV6)
            if rule.ip_src != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV6_SRC,
                                   IPAddress(rule.ip_src).words)
            if rule.ip_dst != self._RULE_WILDCARD:
                match.append_field(ofproto_v1_3.OXM_OF_IPV6_DST,
                                   IPAddress(rule.ip_dst).words)

        # Match transport layer (layer 4)
        if rule.tp_proto != self._RULE_WILDCARD:
            if rule.tp_proto == self._RULE_TCP:
                # Match TCP
                match.append_field(ofproto_v1_3.OXM_OF_IP_PROTO,
                                   ipv4.inet.IPPROTO_TCP)  # covers IPv6
                if rule.port_src != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_TCP_SRC,
                                       int(rule.port_src))
                if rule.port_dst != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_TCP_DST,
                                       int(rule.port_dst))
            elif rule.tp_proto == self._RULE_UDP:
                # Match UDP
                match.append_field(ofproto_v1_3.OXM_OF_IP_PROTO,
                                   ipv4.inet.IPPROTO_UDP)  # covers IPv6
                if rule.port_src != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_UDP_SRC,
                                       int(rule.port_src))
                if rule.port_dst != self._RULE_WILDCARD:
                    match.append_field(ofproto_v1_3.OXM_OF_UDP_DST,
                                       int(rule.port_dst))
        return match

    def _return_ip_version(self, ip_src, ip_dst):
        """Return the IP version being used given the source and
        destination addresses.

        :param ip_src: the source IP address to check.
        :param ip_dst: the destination IP address to check.
        :return: the IP version being used.
        """
        if self._RULE_WILDCARD not in ip_src:
            return IPAddress(ip_src).version
        else:
            return IPAddress(ip_dst).version

    def switch_features(self, event):
        """Process a switch features event from the controller.

        :param event: The OpenFlow event.
        """
        datapath = event.msg.datapath
        datapath_id = event.msg.datapath_id
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # Establish the flow table pipeline used by ACLSwitch. The
        # first flow table represents a blacklist for dropping flows
        # and the second flow table represents a whitelist for
        # allowing flows. We must set the appropriate table miss
        # entries and allow ARP traffic through the whitelist.

        # Table miss entries
        match = parser.OFPMatch()
        inst = [parser.OFPInstructionGotoTable(self._table_id_whitelist)]
        self._contr.add_flow(datapath, 0, match, inst, 0,
                             self._table_id_blacklist)
        actions = []
        inst = [parser.OFPInstructionActions(
            ofproto.OFPIT_APPLY_ACTIONS, actions)]
        self._contr.add_flow(datapath, 0, match, inst, 0,
                             self._table_id_whitelist)
        # ARP entries
        inst = [parser.OFPInstructionGotoTable(self._table_id_next)]
        match = parser.OFPMatch(arp_op=arp.ARP_REQUEST,
                                eth_type=ethernet.ether.ETH_TYPE_ARP)
        self._contr.add_flow(datapath, 1, match, inst, 0,
                             self._table_id_whitelist)
        match = parser.OFPMatch(arp_op=arp.ARP_REPLY,
                                eth_type=ethernet.ether.ETH_TYPE_ARP)
        self._contr.add_flow(datapath, 1, match, inst, 0,
                             self._table_id_whitelist)

        # Registered the switch, assign the default policy, then set
        # to a connected state. Once connected, the rules will be sent
        # to the switch.
        self._api.switch_register(datapath_id)
        self._api.policy_assign_switch(datapath_id, self._POLICY_DEFAULT)
        self._api.switch_connect(datapath_id)

    def get_app_name(self):
        return self._APP_NAME

    def get_expected_handlers(self):
        return self._EXPECTED_HANDLERS

    def is_supported(self):
        return self._supported

    def _verify_contr_handlers(self):
        contr_handlers = self._contr.get_ofpe_handlers()
        failures = ()
        for expected_h in self._EXPECTED_HANDLERS:
            if expected_h not in contr_handlers:
                failures = failures + (expected_h,)
        if not len(failures) == 0:
            fail_msg = ("{0}: The following OpenFlow protocol events "
                        "are not supported by the controller:".format(
                         self._APP_NAME))
            for f in failures:
                fail_msg += "\n\t- {0}".format(f)
            self._logging.critical(fail_msg)
            return False
        else:
            return True