from ryu.base.app_manager import RyuApp from ryu.controller import ofp_event from ryu.controller.handler import HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER, set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib.packet import packet from ryu.lib.packet.ethernet import ethernet from ryu.lib.packet.ipv4 import ipv4 from ryu.lib.packet.tcp import tcp from ryu.lib.packet.udp import udp from ryu.lib.ip import valid_ipv4 from ryu.lib.dpid import dpid_to_str from netaddr import valid_mac import json import toml import time class Obfuscator(RyuApp): OFP_VERSIONS = [ ofproto_v1_3.OFP_VERSION ] FIREWALL_TABLE = 0 OBFUSCATOR_BYPASS_TABLE = 1 OBFUSCATOR_DYNAMIC_TABLE = 2 OBFUSCATOR_STATIC_TABLE = 3 SWITCH_TABLE = 4 TABLE_BASE_PRIORITY = 0 HONEYPOT_BLOCK_PRIORITY = 256 def __init__(self, *args, **kwargs): super(Obfuscator, self).__init__(*args, **kwargs) self.config = self.__load_configuration_file() print(self.config) self.switch_mappings = {} @ set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def features_handler(self, ev): ''' Handshake: Features Request Response Handler ''' datapath = ev.msg.datapath datapath_id = dpid_to_str(datapath.id) self.__clear_tables(datapath) self.__init_firewall_table(datapath) self.__init_obfuscator_tables(datapath) self.__init_switch_table(datapath) for rule in self.config.get(datapath_id, {}).get("firewall_rules", []): self.__install_firewall_rule(datapath, rule) self.logger.debug("๐ฅ\tinstalled firewall rule | {} | {}".format(rule.get("name", "unknown"), datapath_id)) obfuscator_hidden_nodes = self.config.get(datapath_id, {}).get("hidden_node", []) obfuscator_rules = self.config.get(datapath_id, {}).get("rule", []) obfuscator_bypass_rules = self.config.get(datapath_id, {}).get("bypass", []) self.__obfuscator_install_bypass(datapath, obfuscator_hidden_nodes, obfuscator_bypass_rules) self.__obfuscator_install_static(datapath, obfuscator_hidden_nodes, obfuscator_rules) self.__obfuscator_block_honeypots(datapath, obfuscator_rules) @ set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): datapath = ev.msg.datapath datapath_id = dpid_to_str(datapath.id) pkt = packet.Packet(ev.msg.data) in_port = ev.msg.match['in_port'] self.__switch_learn(datapath, pkt, in_port, install=False) if ev.msg.table_id == self.OBFUSCATOR_STATIC_TABLE: # dynamic obfuscator logic self.logger.debug("โฉ๏ธ\tinstalling reverse obfuscator mapping | {}".format(ev.msg.cookie)) self.__obfuscator_flow_manager(datapath, pkt, ev.msg.cookie, in_port) else: # fallback l2 switching logic out_port = self.__switch_output_port(datapath, pkt, install=True) actions = [datapath.ofproto_parser.OFPActionOutput(out_port)] pkt_out_msg = datapath.ofproto_parser.OFPPacketOut(datapath=datapath, buffer_id=ev.msg.buffer_id, in_port=in_port, actions=actions, data=ev.msg.data) datapath.send_msg(pkt_out_msg) self.logger.debug("โก๏ธ\tsent packet out via controller | {}".format(datapath_id)) return # Init Functions def __clear_tables(self, datapath, table_id=-1): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto table = ofp.OFPTT_ALL if table_id < 0 else table_id mod = ofp_parser.OFPFlowMod(datapath=datapath, cookie=0, cookie_mask=0, table_id=table, command=ofp.OFPFC_DELETE, out_port=ofp.OFPP_ANY, out_group=ofp.OFPG_ANY ) datapath.send_msg(mod) def __init_firewall_table(self, datapath): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto match = datapath.ofproto_parser.OFPMatch() instructions = [ofp_parser.OFPInstructionGotoTable(self.OBFUSCATOR_BYPASS_TABLE)] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.FIREWALL_TABLE, ofp.OFPFC_ADD, 0, 0, self.TABLE_BASE_PRIORITY, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, match, instructions) datapath.send_msg(mod) def __init_obfuscator_tables(self, datapath): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto match = datapath.ofproto_parser.OFPMatch() instructions = [ofp_parser.OFPInstructionGotoTable(self.OBFUSCATOR_DYNAMIC_TABLE)] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.OBFUSCATOR_BYPASS_TABLE, ofp.OFPFC_ADD, 0, 0, self.TABLE_BASE_PRIORITY, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, match, instructions) datapath.send_msg(mod) instructions = [ofp_parser.OFPInstructionGotoTable(self.OBFUSCATOR_STATIC_TABLE)] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.OBFUSCATOR_DYNAMIC_TABLE, ofp.OFPFC_ADD, 0, 0, self.TABLE_BASE_PRIORITY, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, match, instructions) datapath.send_msg(mod) instructions = [ofp_parser.OFPInstructionGotoTable(self.SWITCH_TABLE)] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.OBFUSCATOR_STATIC_TABLE, ofp.OFPFC_ADD, 0, 0, self.TABLE_BASE_PRIORITY, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, match, instructions) datapath.send_msg(mod) def __init_switch_table(self, datapath): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto match = datapath.ofproto_parser.OFPMatch() instructions = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [datapath.ofproto_parser.OFPActionOutput( datapath.ofproto.OFPP_CONTROLLER, datapath.ofproto.OFPCML_NO_BUFFER)])] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.SWITCH_TABLE, ofp.OFPFC_ADD, 0, 0, self.TABLE_BASE_PRIORITY, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, match, instructions) datapath.send_msg(mod) # Static Rule Functions def __install_firewall_rule(self, datapath, rule): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto if rule.get("block", True): instructions = [ofp_parser.OFPInstructionActions(ofp.OFPIT_CLEAR_ACTIONS, [])] else: instructions = [ofp_parser.OFPInstructionGotoTable(self.OBFUSCATOR_BYPASS_TABLE)] match = datapath.ofproto_parser.OFPMatch(**rule.get("match", {})) instructions = [ofp_parser.OFPInstructionActions(ofp.OFPIT_CLEAR_ACTIONS, [])] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.FIREWALL_TABLE, ofp.OFPFC_ADD, 0, 0, rule.get("priority", self.TABLE_BASE_PRIORITY + 1), ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM | ofp.OFPFF_RESET_COUNTS, match, instructions) datapath.send_msg(mod) def __obfuscator_block_honeypots(self, datapath, rules): for rule in rules: fw_rule = { "name": "honeypot block", "block": True, "priority": self.HONEYPOT_BLOCK_PRIORITY, "match": { "eth_dst": rule.get("honeypot", {}).get("mac_address"), "eth_type": 2048, "ipv4_dst": rule.get("honeypot", {}).get("ipv4_address") } } self.__install_firewall_rule(datapath, fw_rule) def __obfuscator_install_bypass(self, datapath, hidden_nodes, rules): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto instructions = [ofp_parser.OFPInstructionGotoTable(self.SWITCH_TABLE)] for node in hidden_nodes: for rule in rules: rule_match = rule rule_match["eth_dst"] = node.get("mac_address") rule_match["ipv4_dst"] = node.get("ipv4_address") rule_match["eth_type"] = 2048 match = datapath.ofproto_parser.OFPMatch(**rule_match) mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.OBFUSCATOR_BYPASS_TABLE, ofp.OFPFC_ADD, 0, 0, self.TABLE_BASE_PRIORITY + 1, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM | ofp.OFPFF_RESET_COUNTS, match, instructions) datapath.send_msg(mod) def __obfuscator_install_static(self, datapath, hidden_nodes, rules): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto instructions = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [datapath.ofproto_parser.OFPActionOutput( datapath.ofproto.OFPP_CONTROLLER, datapath.ofproto.OFPCML_NO_BUFFER)])] for node in hidden_nodes: for rule in rules: rule_match = rule.get("match", {}) rule_match["eth_dst"] = node.get("mac_address") rule_match["ipv4_dst"] = node.get("ipv4_address") rule_match["eth_type"] = 2048 match = datapath.ofproto_parser.OFPMatch(**rule_match) mod = ofp_parser.OFPFlowMod(datapath, rule.get("id"), 0, self.OBFUSCATOR_STATIC_TABLE, ofp.OFPFC_ADD, 0, 0, rule.get("priority", self.TABLE_BASE_PRIORITY + 1), ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM | ofp.OFPFF_RESET_COUNTS, match, instructions) datapath.send_msg(mod) # Dynamic Logic def __switch_learn(self, datapath, pkt, in_port, install=False): if eth_header := pkt.get_protocol(ethernet): self.switch_mappings.setdefault(dpid_to_str(datapath.id), {}) self.switch_mappings[dpid_to_str(datapath.id)][eth_header.src] = in_port if install: self.__switch_install_rule(datapath, eth_header.src, in_port) def __switch_output_port(self, datapath, pkt, install=False): if eth_header := pkt.get_protocol(ethernet): out_port = self.switch_mappings.get(dpid_to_str(datapath.id), {}).get(eth_header.dst, None) or datapath.ofproto.OFPP_FLOOD if install and not out_port == datapath.ofproto.OFPP_FLOOD: self.__switch_install_rule(datapath, eth_header.dst, out_port) return out_port return datapath.ofproto.OFPP_FLOOD def __switch_output_port_ethdst(self, datapath, eth_dst): out_port = self.switch_mappings.get(dpid_to_str(datapath.id), {}).get(eth_dst, None) or datapath.ofproto.OFPP_FLOOD return out_port def __switch_install_rule(self, datapath, dst_mac, output_port): ofp_parser = datapath.ofproto_parser ofp = datapath.ofproto match = datapath.ofproto_parser.OFPMatch(eth_dst=dst_mac) actions = [datapath.ofproto_parser.OFPActionOutput(output_port)] instructions = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)] mod = ofp_parser.OFPFlowMod(datapath, 0, 0, self.SWITCH_TABLE, ofp.OFPFC_ADD, 60, 0, 1, ofp.OFP_NO_BUFFER, ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM | ofp.OFPFF_RESET_COUNTS, match, instructions) datapath.send_msg(mod) def __obfuscator_flow_manager(self, datapath, pkt, rule_id, in_port): datapath_id = dpid_to_str(datapath.id) rules = self.config.get(datapath_id, {}).get("rule", []) matched_rule = None for rule in rules: if rule.get("id") == rule_id: matched_rule = rule if not matched_rule: self.logger.error("๐\tfailed to find a matching obfuscator rule") return # 1. create a 5 tuple of the packet as is ft = {"eth_type": 2048} if eth_header := pkt.get_protocol(ethernet): ft["eth_src"] = eth_header.src ft["eth_dst"] = eth_header.dst if ipv4_header := pkt.get_protocol(ipv4): ft["ipv4_src"] = ipv4_header.src ft["ipv4_dst"] = ipv4_header.dst ft["ip_proto"] = ipv4_header.proto else: self.logger.error("๐ค\tnon-ipv4 packet will not be obfuscated") return if tcp_header := pkt.get_protocol(tcp): ft["tcp_src"] = tcp_header.src_port ft["tcp_dst"] = tcp_header.dst_port elif udp_header := pkt.get_protocol(udp): ft["udp_src"] = udp_header.src_port ft["udp_dst"] = udp_header.dst_port else: self.logger.error("๐ค\tnon-tcp/udp packet will not be obfuscated") return # 2. actions are to change the dst ipv4 and mac and output to the # honeypot's port (or flood) actions = [ datapath.ofproto_parser.OFPActionSetField(ipv4_dst=matched_rule.get("honeypot", {}).get("ipv4_address")), datapath.ofproto_parser.OFPActionSetField(eth_dst=matched_rule.get("honeypot", {}).get("mac_address")), datapath.ofproto_parser.OFPActionOutput(self.__switch_output_port_ethdst(datapath, matched_rule.get("honeypot", {}).get("mac_address"))) ] # 3. Send the packet out and install the relevant flow mod (with an idle # timeout) pkt_out_msg = datapath.ofproto_parser.OFPPacketOut(datapath=datapath, buffer_id=datapath.ofproto.OFP_NO_BUFFER, in_port=in_port, actions=actions, data=pkt.serialize()) datapath.send_msg(pkt_out_msg) inst = [datapath.ofproto_parser.OFPInstructionActions(datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] mod = datapath.ofproto_parser.OFPFlowMod( datapath=datapath, priority=2, match=datapath.ofproto_parser.OFPMatch(**ft), instructions=inst, idle_timeout=120, hard_timeout=0, table_id=self.OBFUSCATOR_DYNAMIC_TABLE) self.logger.info("โ๏ธ\tflow-Mod written to datapath: {}".format(dpid_to_str(datapath.id))) datapath.send_msg(mod) # ---- # 1. create a 5 tuple of the expected return packet reverse_ft = {"eth_type": 2048} if ipv4_header := pkt.get_protocol(ipv4): reverse_ft["ipv4_src"] = matched_rule.get("honeypot", {}).get("ipv4_address") reverse_ft["ipv4_dst"] = ipv4_header.src reverse_ft["ip_proto"] = ipv4_header.proto else: self.logger.error("๐ค\tnon-ipv4 packet will not be obfuscated") return if tcp_header := pkt.get_protocol(tcp): reverse_ft["tcp_src"] = tcp_header.dst_port reverse_ft["tcp_dst"] = tcp_header.src_port elif udp_header := pkt.get_protocol(udp): reverse_ft["udp_src"] = udp_header.dst_port reverse_ft["udp_dst"] = udp_header.src_port else: self.logger.error("๐ค\tnon-tcp/udp packet will not be obfuscated") return # 2. actions are to change the src ipv4 and mac and output to the # original target's port (or flood) actions = [ datapath.ofproto_parser.OFPActionSetField(ipv4_src=ft.get("ipv4_dst")), datapath.ofproto_parser.OFPActionSetField(eth_src=ft.get("eth_dst")), datapath.ofproto_parser.OFPActionOutput(in_port) ] # 3. install the relevant flow mod (with an idle # timeout) inst = [datapath.ofproto_parser.OFPInstructionActions(datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] mod = datapath.ofproto_parser.OFPFlowMod(datapath=datapath, priority=3, match=datapath.ofproto_parser.OFPMatch(** reverse_ft), instructions=inst, idle_timeout=120, hard_timeout=0, table_id=self.OBFUSCATOR_DYNAMIC_TABLE) self.logger.info("โ๏ธ\tflow-Mod written to datapath: {}".format(dpid_to_str(datapath.id))) datapath.send_msg(mod) # Config def __load_configuration_file(self, file_path="/tmp/config.toml"): try: with open(file_path) as toml_file: self.logger.debug("๐\ttoml config file opened: {}".format(file_path)) config_data = toml.load(toml_file) self.logger.debug("โ๏ธ\tparsed config toml file as dictionary: {}".format(file_path)) return config_data except Exception as e: self.logger.error("๐\tfailed to load or parse toml from file: {}".format(file_path)) return {}