def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args, **kwargs) ## server location self.server_known = False # declares a server has been defined, and addresses set self.server_ipv4_address = IPAddress( '10.0.0.2' ) # IPv4 address that access is restricted to (the 'server') self.server_ipv6_address = IPAddress( 'fe80::200:ff:fe00:2' ) # IPv6 address that access is restricted to (the 'server') self.server_mac_address = '00:00:00:00:00:02' # MAC address that access is restricted to (the 'server') # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = { } # Keys available to auth on; key_id -> key sequence (seq of decimal numbers) self.key_length = 4 # number of packets per key self.seq_size = get_seq_len( self.key_length ) # number of bits used for the sequence number (1-8 are valid) ## host records self.authenticated_hosts = { } # Authorised hosts; host_ip -> timeleft (time of expiry? time to remove access) self.authing_hosts = { } # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = { } # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking': self}) # testing key self.add_auth_key([{ "value": 1489, "seq": 0, "port": 1489 }, { "value": 15961, "seq": 1, "port": 32345 }, { "value": 8637, "seq": 2, "port": 41405 }, { "value": 2929, "seq": 3, "port": 52081 }]) self.load_keys_from_file('test_keys.txt')
def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args,**kwargs) ## server location self.server_known = False # declares a server has been defined, and addresses set self.server_ipv4_address = IPAddress('10.0.0.2') # IPv4 address that access is restricted to (the 'server') self.server_ipv6_address = IPAddress('fe80::200:ff:fe00:2') # IPv6 address that access is restricted to (the 'server') self.server_mac_address = '00:00:00:00:00:02' # MAC address that access is restricted to (the 'server') # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = {} # Keys available to auth on; key_id -> key sequence (seq of decimal numbers) self.key_length = 4 # number of packets per key self.seq_size = get_seq_len( self.key_length ) # number of bits used for the sequence number (1-8 are valid) ## host records self.authenticated_hosts = {} # Authorised hosts; host_ip -> timeleft (time of expiry? time to remove access) self.authing_hosts = {} # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = {} # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking' : self}) # testing key self.add_auth_key([ {"value": 1489, "seq": 0,"port": 1489}, {"value": 15961,"seq": 1,"port": 32345}, {"value": 8637, "seq": 2,"port": 41405}, {"value": 2929, "seq": 3,"port": 52081}]) self.load_keys_from_file('test_keys.txt')
class Port_Knocking(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] _CONTEXTS = {"wsgi": WSGIApplication} def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args, **kwargs) ## server location self.server_known = False # declares a server has been defined, and addresses set self.server_ipv4_address = IPAddress( '10.0.0.2' ) # IPv4 address that access is restricted to (the 'server') self.server_ipv6_address = IPAddress( 'fe80::200:ff:fe00:2' ) # IPv6 address that access is restricted to (the 'server') self.server_mac_address = '00:00:00:00:00:02' # MAC address that access is restricted to (the 'server') # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = { } # Keys available to auth on; key_id -> key sequence (seq of decimal numbers) self.key_length = 4 # number of packets per key self.seq_size = get_seq_len( self.key_length ) # number of bits used for the sequence number (1-8 are valid) ## host records self.authenticated_hosts = { } # Authorised hosts; host_ip -> timeleft (time of expiry? time to remove access) self.authing_hosts = { } # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = { } # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking': self}) # testing key self.add_auth_key([{ "value": 1489, "seq": 0, "port": 1489 }, { "value": 15961, "seq": 1, "port": 32345 }, { "value": 8637, "seq": 2, "port": 41405 }, { "value": 2929, "seq": 3, "port": 52081 }]) self.load_keys_from_file('test_keys.txt') @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """Runs when switches handshake with controller installs a default flow to out:CONTROLLER""" datapath = ev.msg.datapath self.datapaths[datapath.id] = datapath self.switching.switch_features_handler(ev) self.install_server_blocking_flows(datapath) self.install_auth_init_flow(datapath) def set_server_address(self, server_mac, server_ipv4, server_ipv6): # currently unused ''' Selects a server on the network ''' self.server_mac_address = server_mac self.server_ipv4_address = server_ipv4 self.server_ipv6_address = server_ipv6 self.server_known = True # flush existing flows to for server return def load_keys_from_file(self, filename): """ Loads a list of keys from file Keys are separated by line, values by commas """ print('(AUTH-key file) loading from file') def convert_to_key(port_num): port_num = int(port_num) i, k = port_to_parts(port_num, self.seq_size) return {'value': k, 'seq': i, 'port': port_num} with open(filename, 'r') as infile: for line in infile: self.add_auth_key(map(convert_to_key, line.split(','))) def add_auth_key(self, key_list): ''' Keys are a given as a list of (seq, key) values, each pair of letters corresponds to a port number first is used as the key's ID return true is no key conflict, false if key_id exists ''' if len(key_list) != self.key_length: print('(AUTH-addkey) invalid key list, too long (%d!=%d)' % (len(key_list), self.key_length)) return True if len(key_list) > 2**self.seq_size: print( '(AUTH-addkey) invalid key, longer than max sequence (%d > %d)' % (len(key_list), 2**self.seq_size)) return True # to avoid infinite loop idx = 0 for k in key_list: # check they're valid and in order n = k['seq'] val = k['value'] if val > 2**(num_port_bits - self.seq_size): print( '(AUTH-addkey) invalid value %d at key[%d] -- too large! (>%d)' % (val, n, 2**(num_port_bits - self.seq_size))) return True idx += 1 # check key_id doesn't exist already if key_list[0]['value'] in self.active_keys: return False auth_key = [] auth_ports = [] for key in key_list: # print('%d: %d -> %d' % (key['port'], key['seq'], key['value'])) auth_key.append(key['value']) auth_ports.append(key['port']) self.active_keys[auth_key[0]] = {'key': auth_key, 'port': auth_ports} print('(AUTH-addkey) Added key %s' % auth_key) return True def install_server_blocking_flows(self, datapath): ''' Blocking IP access to the server and allowing ARP ''' print('(AUTH-install) installing %d\'s server block flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_block = [] # empty == drop # install block all to server rule (mac, ipv4, ipv6) match_mac = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, eth_dst=self.server_mac_address) match_ipv4 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=self.server_ipv4_address) match_ipv6 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6, ipv6_dst=self.server_ipv6_address) add_flow(datapath, 1, match_mac, action_block) add_flow(datapath, 1, match_ipv4, action_block) add_flow(datapath, 1, match_ipv6, action_block) def install_auth_init_flow(self, datapath): ''' Install rule for matching for the TCP auth init packet ''' print('(AUTH-install) installing %d\'s knock init flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_packet_in = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] # send TCP on port self.auth_port to controller match_tcp_auth_ipv4 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv4_dst=IPAddress( self.server_ipv4_address), tcp_dst=self.auth_port) match_tcp_auth_ipv6 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv6_dst=IPAddress( self.server_ipv6_address), tcp_dst=self.auth_port) # add a flow for auth init packet capture add_flow(datapath, 2, match_tcp_auth_ipv4, action_packet_in) add_flow(datapath, 2, match_tcp_auth_ipv6, action_packet_in) def auth_host(self, src_ip, datapath): ''' Allows given host to access the server ''' action_allow_to_server = [ ofproto_v1_3_parser.OFPActionOutput(self.server_port[datapath.id]) ] # add rules for mac to access server match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(self.server_ipv4_address)) add_flow(datapath, 3, match_ipv4, action_allow_to_server) print('(AUTH-auth authenticated id:%s on dpid:%s' % (src_ip, datapath.id)) def match_key(self, src_ip, dst_port, datapath): ''' Matches the sequence of knocks against buffered key ''' # TODO no penalty on self.auth_port! print('dst port %d' % dst_port) idx, key_val = port_to_parts(dst_port, self.seq_size) if (idx > self.key_length) or (idx < 0): self.invalid_key('Key sequence number out of bounds') return if len(self.authing_hosts[src_ip]) == 0: # first sequence key if idx != 0: print('key not yet defined, seq #%d received' % idx) return # don't accept anything until key is selected else: key_id = key_val else: key_id = self.authing_hosts[src_ip][0] if key_id not in self.active_keys: self.invalid_key('Key id not valid %d' % (key_id)) self.remove_key_from(src_ip) return if self.active_keys[key_id]['key'][idx] != key_val: # check they match self.invalid_key( 'value %d doesn\'t match key idx %d of key %d (%d)' % (key_val, idx, key_id, self.active_keys[key_id]['key'][idx])) return if idx not in self.authing_hosts[src_ip]: print('(AUTH-buffered) %s length: %d/%d (%d)' % (src_ip, len(self.authing_hosts[src_ip]) + 1, self.key_length, key_val)) self.authing_hosts[src_ip][idx] = key_val else: print('duplicate %d->%d' % (idx, key_val)) if len(self.authing_hosts[src_ip]) == self.key_length: # key complete, authorise IP address to access server print('(AUTH-seq complete) ip:%s' % src_ip) # add host to authenticated hosts self.authenticated_hosts[src_ip] = 10000 # tidy up del self.authing_hosts[src_ip] del self.active_keys[key_id] # self.remove_authing_flows(src_ip) # redundant, is replaced with allow flow # install flows to access server self.auth_host(src_ip, datapath) def invalid_key(self, msg=''): # TODO: block for a few seconds # TODO: release authing key from host? print('(AUTH-invalid key) %s' % msg) def remove_key_from(self, src_ip): ''' expired keys are disassociated from authing host ''' del self.authing_hosts[src_ip] def initialise_host_auth(self, src_ip, datapath): print('(AUTH-auth init) received init from %s' % src_ip) self.authing_hosts[src_ip] = {} # empty key buffer # install flow, fwd all tcp to controller action_fwd_to_controller = [ ofproto_v1_3_parser.OFPActionOutput(ofproto_v1_3.OFPP_CONTROLLER) ] match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(self.server_ipv4_address)) add_flow(datapath, 3, match_ipv4, action_fwd_to_controller) def remove_auth_flows(self, src_ip): """ removes the flows that capture knock sequence (identified with src_ip and priority) """ match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(self.server_ipv4_address))) for id in self.datapaths: delete_flow(self.datapaths[id], 3, match_ipv4) def remove_host_access(self, src_ip): """ Revokes access to server for an authorised host """ if src_ip not in self.authenticated_hosts: return False print('removing %s from auth hosts' % src_ip) del self.authenticated_hosts[src_ip] self.remove_auth_flows(src_ip) return True def set_datapath_svr_port(self, dpid, in_port): if dpid in self.server_port and self.server_port[dpid] == in_port: # already set and no change return print '(AUTH-packet_in) %d\'s server_port: %d' % (dpid, in_port) self.server_port[dpid] = in_port self.server_known = True @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): '''Listen for auth packets and server announcement''' msg = ev.msg dp = msg.datapath ofp = dp.ofproto parser = dp.ofproto_parser pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] eth_type = eth.ethertype # ''' register the server (not implemented) ''' # if (broadcast and matches server key): # set_server_address() # set server port for this datapath # capture auth packets if eth_type == ether_types.ETH_TYPE_IP: # if TCP and dst is server # ipv4 ip = pkt.get_protocols(ipv4.ipv4)[0] if ip.proto == in_proto.IPPROTO_TCP and ip.dst == str( self.server_ipv4_address): tcp = pkt.get_protocols(TCP.tcp)[0] if ip.src in self.authenticated_hosts: # likely from another switch, so needs to have flow to server installed self.auth_host(ip.src, dp) if len(self.active_keys) <= 0: print('no keys active') return elif ip.src in self.authing_hosts: self.match_key(ip.src, tcp.dst_port, dp) elif tcp.dst_port == self.auth_port: # install key matching flows for host self.initialise_host_auth(ip.src, dp) return # avoid installing flow (block TCP traffic to server) if ip.dst == str(self.server_ipv4_address): # avoids controller forwarding on other IP packets while ALL TO CONTROLLER is active return # ipv6 to server, block from switch if eth_type == ether_types.ETH_TYPE_IPV6: ip = pkt.get_protocols(ipv6.ipv6)[0] if ip.dst == str(self.server_ipv6_address): return # if from server, get port_id of server (reply from ARP will trigger this) if eth.src == self.server_mac_address: self.set_datapath_svr_port(dp.id, msg.match['in_port']) # do regular switching self.switching.packet_in_handler(ev)
def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args, **kwargs) # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## server location, MAC address that access is restricted to (the 'server') self.server_mac_address = [ '00:00:00:00:00:02', '00:00:00:00:00:03' ] ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = { "10.0.0.2": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} }, "10.0.0.3": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} } } self.active_keys_v6 = { str(IPAddress('fe80::200:ff:fe00:2')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, }, str(IPAddress('fe80::200:ff:fe00:3')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, } } ## host records self.authing_hosts = {} # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = {} # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking': self})
class Port_Knocking(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] _CONTEXTS = {"wsgi": WSGIApplication} def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args, **kwargs) # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## server location, MAC address that access is restricted to (the 'server') self.server_mac_address = [ '00:00:00:00:00:02', '00:00:00:00:00:03' ] ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = { "10.0.0.2": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} }, "10.0.0.3": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} } } self.active_keys_v6 = { str(IPAddress('fe80::200:ff:fe00:2')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, }, str(IPAddress('fe80::200:ff:fe00:3')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, } } ## host records self.authing_hosts = {} # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = {} # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking': self}) # testing key TODO # self.add_auth_key([ # {"value": 1489, "seq": 0, "port": 1489}, # {"value": 15961, "seq": 1, "port": 32345}, # {"value": 8637, "seq": 2, "port": 41405}, # {"value": 2929, "seq": 3, "port": 52081}]) # self.load_keys_from_file('test_keys.txt') @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """Runs when switches handshake with controller installs a default flow to out:CONTROLLER""" datapath = ev.msg.datapath self.datapaths[datapath.id] = datapath self.switching.switch_features_handler(ev) self.install_server_blocking_flows(datapath) self.install_auth_init_flow(datapath) def load_keys_from_file(self, filename): """ Loads a list of keys from file Keys are separated by line, values by commas """ print('(AUTH-key file) loading from file') def convert_to_key(port_num): port_num = int(port_num) i, k = port_to_parts(port_num, self.seq_size) return {'value': k, 'seq': i, 'port': port_num} with open(filename, 'r') as infile: for line in infile: self.add_auth_key(map(convert_to_key, line.split(','))) def add_auth_key(self, which_host, key_list): # TODO if self.active_keys.get(which_host) is None: self.active_keys[which_host] = { "current": "", "keys": {} } self.active_keys[which_host]["keys"][key_list[0]] = key_list def install_server_blocking_flows(self, datapath): ''' Blocking IP access to the server and allowing ARP ''' print('(AUTH-install) installing %d\'s server block flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_block = [] # empty == drop # L2 block for addr in self.server_mac_address: match_mac = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, eth_dst=addr) add_flow(datapath, 1, match_mac, action_block) # L3 block for addr in self.active_keys: match_ipv4 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=IPAddress(addr)) add_flow(datapath, 1, match_ipv4, action_block) for addr in self.active_keys_v6: match_ipv6 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6, ipv6_dst=IPAddress(addr)) add_flow(datapath, 1, match_ipv6, action_block) def install_auth_init_flow(self, datapath): ''' Install rule for matching for the TCP auth init packet ''' print('(AUTH-install) installing %d\'s knock init flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_packet_in = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] for addr in self.active_keys: # send TCP on port self.auth_port to controller match_tcp_auth_ipv4 = parser.OFPMatch( eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv4_dst=IPAddress(addr), tcp_dst=self.auth_port) # add a flow for auth init packet capture add_flow(datapath, 2, match_tcp_auth_ipv4, action_packet_in) for addr in self.active_keys_v6: # send TCP on port self.auth_port to controller match_tcp_auth_ipv6 = parser.OFPMatch( eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv6_dst=IPAddress(addr), tcp_dst=self.auth_port) # add a flow for auth init packet capture add_flow(datapath, 2, match_tcp_auth_ipv6, action_packet_in) def auth_host(self, src_ip, dst_ip, datapath): ''' Allows given host to access the server ''' action_allow_to_server = [ofproto_v1_3_parser.OFPActionOutput(self.server_port[datapath.id])] # add rules for mac to access server match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(dst_ip))) add_flow(datapath, 3, match_ipv4, action_allow_to_server) print ('(AUTH-auth authenticated id:%s on dpid:%s' % (src_ip, datapath.id)) def match_key(self, src_ip, dst_ip, dst_port, datapath): ''' Matches the sequence of knocks against buffered key ''' print('dst port %d' % dst_port) key_id = self.active_keys[dst_ip]["current_key"] idx = self.active_keys[dst_ip]["current_seq"] if key_id == -1: # first sequence key if dst_port in self.active_keys[dst_ip]["keys"]: self.active_keys[dst_ip]["current_key"] = key_id = dst_port self.active_keys[dst_ip]["current_seq"] = idx = 0 else: print('key not yet defined') return # don't accept anything until key is selected key_val = self.active_keys[dst_ip]["keys"][key_id][idx] key_length = len(self.active_keys[dst_ip]["keys"][key_id]) if idx > 0: key_val_last = self.active_keys[dst_ip]["keys"][key_id][idx - 1] else: key_val_last = -1 if dst_port == key_val_last: print('duplicate %d->%d' % (idx - 1, dst_port)) return elif dst_port != key_val: self.active_keys[dst_ip]["current_key"] = -1 self.active_keys[dst_ip]["current_seq"] = -1 self.remove_key_from(src_ip) self.invalid_key('value %d doesn\'t match key idx %d of key %d (%d)' % (dst_port, idx + 1, key_length, key_val)) return # dst_port == key_val print('(AUTH-buffered) %s -> %s length: %d/%d (%d)' % (src_ip, dst_ip, idx + 1, key_length, key_val)) if idx + 1 == key_length: # key complete, authorise IP address to access server print('(AUTH-seq complete) ip:%s' % src_ip) # add host to authenticated hosts self.active_keys[dst_ip]["authenticated_hosts"][src_ip] = 10000 # tidy up self.active_keys[dst_ip]["current_key"] = -1 self.active_keys[dst_ip]["current_seq"] = -1 self.remove_key_from(src_ip) # install flows to access server self.auth_host(src_ip, dst_ip, datapath) else: self.authing_hosts[src_ip] = {} self.active_keys[dst_ip]["current_seq"] = idx + 1 def invalid_key(self, msg=''): # TODO: block for a few seconds # TODO: release authing key from host? print('(AUTH-invalid key) %s' % msg) def remove_key_from(self, src_ip): ''' expired keys are disassociated from authing host ''' del self.authing_hosts[src_ip] def initialise_host_auth(self, src_ip, dst_ip, datapath): print ('(AUTH-auth init) received init from %s' % src_ip) self.authing_hosts[src_ip] = {} # empty key buffer # install flow, fwd all tcp to controller action_fwd_to_controller = [ofproto_v1_3_parser.OFPActionOutput(ofproto_v1_3.OFPP_CONTROLLER)] match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(dst_ip))) add_flow(datapath, 3, match_ipv4, action_fwd_to_controller) def remove_auth_flows(self, src_ip, dst_ip): """ removes the flows that capture knock sequence (identified with src_ip and priority) """ match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(dst_ip))) for id in self.datapaths: delete_flow(self.datapaths[id], 3, match_ipv4) def remove_host_access(self, src_ip, dst_ip): """ Revokes access to server for an authorised host """ if src_ip not in self.active_keys[dst_ip]["authenticated_hosts"]: return False print('removing %s from auth hosts' % src_ip) del self.active_keys[dst_ip]["authenticated_hosts"][src_ip] self.remove_auth_flows(src_ip, dst_ip) return True def set_datapath_svr_port(self, dpid, in_port): if dpid in self.server_port and self.server_port[dpid] == in_port: # already set and no change return print '(AUTH-packet_in) %d\'s server_port: %d' % (dpid, in_port) self.server_port[dpid] = in_port @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): '''Listen for auth packets and server announcement''' msg = ev.msg dp = msg.datapath ofp = dp.ofproto parser = dp.ofproto_parser pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] eth_type = eth.ethertype # capture auth packets if eth_type == ether_types.ETH_TYPE_IP: # if TCP and dst is server # ipv4 ip = pkt.get_protocols(ipv4.ipv4)[0] if ip.proto == in_proto.IPPROTO_TCP and ip.dst in self.active_keys: tcp = pkt.get_protocols(TCP.tcp)[0] if ip.src in self.active_keys[ip.dst][ "authenticated_hosts"]: # likely from another switch, so needs to have flow to server installed self.auth_host(ip.src, ip.dst, dp) elif ip.src in self.authing_hosts: self.match_key(ip.src, ip.dst, tcp.dst_port, dp) elif tcp.dst_port == self.auth_port: # install key matching flows for host self.initialise_host_auth(ip.src, ip.dst, dp) return # avoid installing flow (block TCP traffic to server) if ip.dst in self.active_keys: # avoids controller forwarding on other IP packets while ALL TO CONTROLLER is active return # ipv6 to server, block from switch if eth_type == ether_types.ETH_TYPE_IPV6: ip = pkt.get_protocols(ipv6.ipv6)[0] if ip.dst in self.active_keys_v6: return # if from server, get port_id of server (reply from ARP will trigger this) if eth.src in self.server_mac_address: self.set_datapath_svr_port(dp.id, msg.match['in_port']) # do regular switching self.switching.packet_in_handler(ev)
class Port_Knocking(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] _CONTEXTS = {"wsgi":WSGIApplication} def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args,**kwargs) ## server location self.server_known = False # declares a server has been defined, and addresses set self.server_ipv4_address = IPAddress('10.0.0.2') # IPv4 address that access is restricted to (the 'server') self.server_ipv6_address = IPAddress('fe80::200:ff:fe00:2') # IPv6 address that access is restricted to (the 'server') self.server_mac_address = '00:00:00:00:00:02' # MAC address that access is restricted to (the 'server') # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = {} # Keys available to auth on; key_id -> key sequence (seq of decimal numbers) self.key_length = 4 # number of packets per key self.seq_size = get_seq_len( self.key_length ) # number of bits used for the sequence number (1-8 are valid) ## host records self.authenticated_hosts = {} # Authorised hosts; host_ip -> timeleft (time of expiry? time to remove access) self.authing_hosts = {} # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = {} # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking' : self}) # testing key self.add_auth_key([ {"value": 1489, "seq": 0,"port": 1489}, {"value": 15961,"seq": 1,"port": 32345}, {"value": 8637, "seq": 2,"port": 41405}, {"value": 2929, "seq": 3,"port": 52081}]) self.load_keys_from_file('test_keys.txt') @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """Runs when switches handshake with controller installs a default flow to out:CONTROLLER""" datapath = ev.msg.datapath self.datapaths[datapath.id] = datapath self.switching.switch_features_handler(ev) self.install_server_blocking_flows(datapath) self.install_auth_init_flow(datapath) def set_server_address(self, server_mac, server_ipv4, server_ipv6): # currently unused ''' Selects a server on the network ''' self.server_mac_address = server_mac self.server_ipv4_address = server_ipv4 self.server_ipv6_address = server_ipv6 self.server_known = True # flush existing flows to for server return def load_keys_from_file(self, filename): """ Loads a list of keys from file Keys are separated by line, values by commas """ print('(AUTH-key file) loading from file') def convert_to_key(port_num): port_num = int(port_num) i,k = port_to_parts(port_num, self.seq_size) return {'value': k, 'seq':i, 'port':port_num} with open(filename, 'r') as infile: for line in infile: self.add_auth_key(map(convert_to_key, line.split(','))) def add_auth_key(self, key_list): ''' Keys are a given as a list of (seq, key) values, each pair of letters corresponds to a port number first is used as the key's ID return true is no key conflict, false if key_id exists ''' if len(key_list) != self.key_length: print('(AUTH-addkey) invalid key list, too long (%d!=%d)' % (len(key_list),self.key_length)) return True if len(key_list) > 2**self.seq_size: print('(AUTH-addkey) invalid key, longer than max sequence (%d > %d)' % (len(key_list),2**self.seq_size)) return True # to avoid infinite loop idx = 0 for k in key_list: # check they're valid and in order n = k['seq'] val = k['value'] if val > 2**(num_port_bits - self.seq_size): print('(AUTH-addkey) invalid value %d at key[%d] -- too large! (>%d)' % (val,n,2**(num_port_bits - self.seq_size))) return True idx+=1 # check key_id doesn't exist already if key_list[0]['value'] in self.active_keys: return False auth_key = [] auth_ports = [] for key in key_list: # print('%d: %d -> %d' % (key['port'], key['seq'], key['value'])) auth_key.append(key['value']) auth_ports.append(key['port']) self.active_keys[auth_key[0]] = {'key': auth_key,'port': auth_ports} print('(AUTH-addkey) Added key %s' % auth_key) return True def install_server_blocking_flows(self, datapath): ''' Blocking IP access to the server and allowing ARP ''' print('(AUTH-install) installing %d\'s server block flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_block = [] # empty == drop # install block all to server rule (mac, ipv4, ipv6) match_mac = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, eth_dst=self.server_mac_address) match_ipv4 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=self.server_ipv4_address) match_ipv6 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6, ipv6_dst=self.server_ipv6_address) add_flow(datapath, 1, match_mac, action_block) add_flow(datapath, 1, match_ipv4, action_block) add_flow(datapath, 1, match_ipv6, action_block) def install_auth_init_flow(self, datapath): ''' Install rule for matching for the TCP auth init packet ''' print('(AUTH-install) installing %d\'s knock init flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_packet_in = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] # send TCP on port self.auth_port to controller match_tcp_auth_ipv4 = parser.OFPMatch( eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv4_dst= IPAddress(self.server_ipv4_address), tcp_dst= self.auth_port) match_tcp_auth_ipv6 = parser.OFPMatch( eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv6_dst= IPAddress(self.server_ipv6_address), tcp_dst= self.auth_port) # add a flow for auth init packet capture add_flow(datapath, 2, match_tcp_auth_ipv4, action_packet_in) add_flow(datapath, 2, match_tcp_auth_ipv6, action_packet_in) def auth_host(self, src_ip, datapath): ''' Allows given host to access the server ''' action_allow_to_server = [ofproto_v1_3_parser.OFPActionOutput(self.server_port[datapath.id])] # add rules for mac to access server match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(self.server_ipv4_address)) add_flow(datapath, 3, match_ipv4, action_allow_to_server) print ('(AUTH-auth authenticated id:%s on dpid:%s' % (src_ip, datapath.id)) def match_key(self, src_ip, dst_port, datapath): ''' Matches the sequence of knocks against buffered key ''' # TODO no penalty on self.auth_port! print('dst port %d' % dst_port) idx, key_val = port_to_parts(dst_port, self.seq_size) if (idx > self.key_length) or (idx < 0): self.invalid_key('Key sequence number out of bounds') return if len(self.authing_hosts[src_ip]) == 0: # first sequence key if idx != 0: print('key not yet defined, seq #%d received' % idx) return # don't accept anything until key is selected else: key_id = key_val else: key_id = self.authing_hosts[src_ip][0] if key_id not in self.active_keys: self.invalid_key('Key id not valid %d' % (key_id)) self.remove_key_from(src_ip) return if self.active_keys[key_id]['key'][idx] != key_val: # check they match self.invalid_key('value %d doesn\'t match key idx %d of key %d (%d)' % (key_val, idx, key_id, self.active_keys[key_id]['key'][idx])) return if idx not in self.authing_hosts[src_ip]: print('(AUTH-buffered) %s length: %d/%d (%d)' % (src_ip, len(self.authing_hosts[src_ip])+1,self.key_length, key_val)) self.authing_hosts[src_ip][idx] = key_val else: print('duplicate %d->%d' % (idx,key_val)) if len(self.authing_hosts[src_ip]) == self.key_length: # key complete, authorise IP address to access server print('(AUTH-seq complete) ip:%s' % src_ip) # add host to authenticated hosts self.authenticated_hosts[src_ip] = 10000 # tidy up del self.authing_hosts[src_ip] del self.active_keys[key_id] # self.remove_authing_flows(src_ip) # redundant, is replaced with allow flow # install flows to access server self.auth_host(src_ip, datapath) def invalid_key(self, msg=''): # TODO: block for a few seconds # TODO: release authing key from host? print('(AUTH-invalid key) %s' % msg) def remove_key_from(self, src_ip): ''' expired keys are disassociated from authing host ''' del self.authing_hosts[src_ip] def initialise_host_auth(self, src_ip, datapath): print ('(AUTH-auth init) received init from %s' % src_ip) self.authing_hosts[src_ip] = {} # empty key buffer # install flow, fwd all tcp to controller action_fwd_to_controller = [ofproto_v1_3_parser.OFPActionOutput(ofproto_v1_3.OFPP_CONTROLLER)] match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(self.server_ipv4_address)) add_flow(datapath, 3, match_ipv4, action_fwd_to_controller) def remove_auth_flows(self,src_ip): """ removes the flows that capture knock sequence (identified with src_ip and priority) """ match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(self.server_ipv4_address))) for id in self.datapaths: delete_flow(self.datapaths[id], 3, match_ipv4) def remove_host_access(self,src_ip): """ Revokes access to server for an authorised host """ if src_ip not in self.authenticated_hosts: return False print('removing %s from auth hosts' % src_ip) del self.authenticated_hosts[src_ip] self.remove_auth_flows(src_ip) return True def set_datapath_svr_port(self, dpid, in_port): if dpid in self.server_port and self.server_port[dpid] == in_port: # already set and no change return print '(AUTH-packet_in) %d\'s server_port: %d' % (dpid, in_port) self.server_port[dpid] = in_port self.server_known = True @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): '''Listen for auth packets and server announcement''' msg = ev.msg dp = msg.datapath ofp = dp.ofproto parser = dp.ofproto_parser pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] eth_type = eth.ethertype # ''' register the server (not implemented) ''' # if (broadcast and matches server key): # set_server_address() # set server port for this datapath # capture auth packets if eth_type == ether_types.ETH_TYPE_IP: # if TCP and dst is server # ipv4 ip = pkt.get_protocols(ipv4.ipv4)[0] if ip.proto == in_proto.IPPROTO_TCP and ip.dst == str(self.server_ipv4_address): tcp = pkt.get_protocols(TCP.tcp)[0] if ip.src in self.authenticated_hosts: # likely from another switch, so needs to have flow to server installed self.auth_host(ip.src, dp) if len(self.active_keys) <= 0: print('no keys active') return elif ip.src in self.authing_hosts: self.match_key(ip.src, tcp.dst_port, dp) elif tcp.dst_port == self.auth_port: # install key matching flows for host self.initialise_host_auth(ip.src, dp) return # avoid installing flow (block TCP traffic to server) if ip.dst == str(self.server_ipv4_address): # avoids controller forwarding on other IP packets while ALL TO CONTROLLER is active return # ipv6 to server, block from switch if eth_type == ether_types.ETH_TYPE_IPV6: ip = pkt.get_protocols(ipv6.ipv6)[0] if ip.dst == str(self.server_ipv6_address): return # if from server, get port_id of server (reply from ARP will trigger this) if eth.src == self.server_mac_address: self.set_datapath_svr_port(dp.id, msg.match['in_port']) # do regular switching self.switching.packet_in_handler(ev)
def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args, **kwargs) # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## server location, MAC address that access is restricted to (the 'server') self.server_mac_address = ['00:00:00:00:00:02', '00:00:00:00:00:03'] ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = { "10.0.0.2": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} }, "10.0.0.3": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} } } self.active_keys_v6 = { str(IPAddress('fe80::200:ff:fe00:2')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, }, str(IPAddress('fe80::200:ff:fe00:3')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, } } ## host records self.authing_hosts = { } # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = { } # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking': self})
class Port_Knocking(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] _CONTEXTS = {"wsgi": WSGIApplication} def __init__(self, *args, **kwargs): super(Port_Knocking, self).__init__(*args, **kwargs) # record of server location on each switch self.datapaths = {} # dpid -> datapath object self.server_port = {} # dpid -> port number on switch to reach server ## server location, MAC address that access is restricted to (the 'server') self.server_mac_address = ['00:00:00:00:00:02', '00:00:00:00:00:03'] ## key config self.auth_port = 1332 # TCP port to initiate authentication key self.active_keys = { "10.0.0.2": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} }, "10.0.0.3": { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {} } } self.active_keys_v6 = { str(IPAddress('fe80::200:ff:fe00:2')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, }, str(IPAddress('fe80::200:ff:fe00:3')): { "current_key": -1, "current_seq": -1, "keys": { 1000: [1000, 1001, 1002], 1001: [1001, 1002, 1003] }, "authenticated_hosts": {}, } } ## host records self.authing_hosts = { } # Hosts currently entering keys; host_ip -> key buffer s.t. key buffer [port0==keyID,port1,port2,port3,..] self.blocked_hosts = { } # Hosts who entered incorrect key; host_ip -> timeout ## may not implement atm self.default_time = 1800 # seconds till invalid (3600 == one hour) # get/register other classes self.switching = SimpleHubSwitch() wsgi = kwargs['wsgi'] wsgi.register(Portknock_Server, {'port_knocking': self}) # testing key TODO # self.add_auth_key([ # {"value": 1489, "seq": 0, "port": 1489}, # {"value": 15961, "seq": 1, "port": 32345}, # {"value": 8637, "seq": 2, "port": 41405}, # {"value": 2929, "seq": 3, "port": 52081}]) # self.load_keys_from_file('test_keys.txt') @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """Runs when switches handshake with controller installs a default flow to out:CONTROLLER""" datapath = ev.msg.datapath self.datapaths[datapath.id] = datapath self.switching.switch_features_handler(ev) self.install_server_blocking_flows(datapath) self.install_auth_init_flow(datapath) def load_keys_from_file(self, filename): """ Loads a list of keys from file Keys are separated by line, values by commas """ print('(AUTH-key file) loading from file') def convert_to_key(port_num): port_num = int(port_num) i, k = port_to_parts(port_num, self.seq_size) return {'value': k, 'seq': i, 'port': port_num} with open(filename, 'r') as infile: for line in infile: self.add_auth_key(map(convert_to_key, line.split(','))) def add_auth_key(self, which_host, key_list): # TODO if self.active_keys.get(which_host) is None: self.active_keys[which_host] = {"current": "", "keys": {}} self.active_keys[which_host]["keys"][key_list[0]] = key_list def install_server_blocking_flows(self, datapath): ''' Blocking IP access to the server and allowing ARP ''' print('(AUTH-install) installing %d\'s server block flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_block = [] # empty == drop # L2 block for addr in self.server_mac_address: match_mac = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, eth_dst=addr) add_flow(datapath, 1, match_mac, action_block) # L3 block for addr in self.active_keys: match_ipv4 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=IPAddress(addr)) add_flow(datapath, 1, match_ipv4, action_block) for addr in self.active_keys_v6: match_ipv6 = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6, ipv6_dst=IPAddress(addr)) add_flow(datapath, 1, match_ipv6, action_block) def install_auth_init_flow(self, datapath): ''' Install rule for matching for the TCP auth init packet ''' print('(AUTH-install) installing %d\'s knock init flows' % datapath.id) ofproto = datapath.ofproto parser = datapath.ofproto_parser action_packet_in = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] for addr in self.active_keys: # send TCP on port self.auth_port to controller match_tcp_auth_ipv4 = parser.OFPMatch( eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv4_dst=IPAddress(addr), tcp_dst=self.auth_port) # add a flow for auth init packet capture add_flow(datapath, 2, match_tcp_auth_ipv4, action_packet_in) for addr in self.active_keys_v6: # send TCP on port self.auth_port to controller match_tcp_auth_ipv6 = parser.OFPMatch( eth_type=ether_types.ETH_TYPE_IP, ip_proto=in_proto.IPPROTO_TCP, ipv6_dst=IPAddress(addr), tcp_dst=self.auth_port) # add a flow for auth init packet capture add_flow(datapath, 2, match_tcp_auth_ipv6, action_packet_in) def auth_host(self, src_ip, dst_ip, datapath): ''' Allows given host to access the server ''' action_allow_to_server = [ ofproto_v1_3_parser.OFPActionOutput(self.server_port[datapath.id]) ] # add rules for mac to access server match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(dst_ip))) add_flow(datapath, 3, match_ipv4, action_allow_to_server) print('(AUTH-auth authenticated id:%s on dpid:%s' % (src_ip, datapath.id)) def match_key(self, src_ip, dst_ip, dst_port, datapath): ''' Matches the sequence of knocks against buffered key ''' print('dst port %d' % dst_port) key_id = self.active_keys[dst_ip]["current_key"] idx = self.active_keys[dst_ip]["current_seq"] if key_id == -1: # first sequence key if dst_port in self.active_keys[dst_ip]["keys"]: self.active_keys[dst_ip]["current_key"] = key_id = dst_port self.active_keys[dst_ip]["current_seq"] = idx = 0 else: print('key not yet defined') return # don't accept anything until key is selected key_val = self.active_keys[dst_ip]["keys"][key_id][idx] key_length = len(self.active_keys[dst_ip]["keys"][key_id]) if idx > 0: key_val_last = self.active_keys[dst_ip]["keys"][key_id][idx - 1] else: key_val_last = -1 if dst_port == key_val_last: print('duplicate %d->%d' % (idx - 1, dst_port)) return elif dst_port != key_val: self.active_keys[dst_ip]["current_key"] = -1 self.active_keys[dst_ip]["current_seq"] = -1 self.remove_key_from(src_ip) self.invalid_key( 'value %d doesn\'t match key idx %d of key %d (%d)' % (dst_port, idx + 1, key_length, key_val)) return # dst_port == key_val print('(AUTH-buffered) %s -> %s length: %d/%d (%d)' % (src_ip, dst_ip, idx + 1, key_length, key_val)) if idx + 1 == key_length: # key complete, authorise IP address to access server print('(AUTH-seq complete) ip:%s' % src_ip) # add host to authenticated hosts self.active_keys[dst_ip]["authenticated_hosts"][src_ip] = 10000 # tidy up self.active_keys[dst_ip]["current_key"] = -1 self.active_keys[dst_ip]["current_seq"] = -1 self.remove_key_from(src_ip) # install flows to access server self.auth_host(src_ip, dst_ip, datapath) else: self.authing_hosts[src_ip] = {} self.active_keys[dst_ip]["current_seq"] = idx + 1 def invalid_key(self, msg=''): # TODO: block for a few seconds # TODO: release authing key from host? print('(AUTH-invalid key) %s' % msg) def remove_key_from(self, src_ip): ''' expired keys are disassociated from authing host ''' del self.authing_hosts[src_ip] def initialise_host_auth(self, src_ip, dst_ip, datapath): print('(AUTH-auth init) received init from %s' % src_ip) self.authing_hosts[src_ip] = {} # empty key buffer # install flow, fwd all tcp to controller action_fwd_to_controller = [ ofproto_v1_3_parser.OFPActionOutput(ofproto_v1_3.OFPP_CONTROLLER) ] match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(dst_ip))) add_flow(datapath, 3, match_ipv4, action_fwd_to_controller) def remove_auth_flows(self, src_ip, dst_ip): """ removes the flows that capture knock sequence (identified with src_ip and priority) """ match_ipv4 = ofproto_v1_3_parser.OFPMatch() match_ipv4.append_field(ofproto_v1_3.OXM_OF_ETH_TYPE, ether_types.ETH_TYPE_IP) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_SRC, int(IPAddress(src_ip))) match_ipv4.append_field(ofproto_v1_3.OXM_OF_IPV4_DST, int(IPAddress(dst_ip))) for id in self.datapaths: delete_flow(self.datapaths[id], 3, match_ipv4) def remove_host_access(self, src_ip, dst_ip): """ Revokes access to server for an authorised host """ if src_ip not in self.active_keys[dst_ip]["authenticated_hosts"]: return False print('removing %s from auth hosts' % src_ip) del self.active_keys[dst_ip]["authenticated_hosts"][src_ip] self.remove_auth_flows(src_ip, dst_ip) return True def set_datapath_svr_port(self, dpid, in_port): if dpid in self.server_port and self.server_port[dpid] == in_port: # already set and no change return print '(AUTH-packet_in) %d\'s server_port: %d' % (dpid, in_port) self.server_port[dpid] = in_port @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): '''Listen for auth packets and server announcement''' msg = ev.msg dp = msg.datapath ofp = dp.ofproto parser = dp.ofproto_parser pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] eth_type = eth.ethertype # capture auth packets if eth_type == ether_types.ETH_TYPE_IP: # if TCP and dst is server # ipv4 ip = pkt.get_protocols(ipv4.ipv4)[0] if ip.proto == in_proto.IPPROTO_TCP and ip.dst in self.active_keys: tcp = pkt.get_protocols(TCP.tcp)[0] if ip.src in self.active_keys[ip.dst][ "authenticated_hosts"]: # likely from another switch, so needs to have flow to server installed self.auth_host(ip.src, ip.dst, dp) elif ip.src in self.authing_hosts: self.match_key(ip.src, ip.dst, tcp.dst_port, dp) elif tcp.dst_port == self.auth_port: # install key matching flows for host self.initialise_host_auth(ip.src, ip.dst, dp) return # avoid installing flow (block TCP traffic to server) if ip.dst in self.active_keys: # avoids controller forwarding on other IP packets while ALL TO CONTROLLER is active return # ipv6 to server, block from switch if eth_type == ether_types.ETH_TYPE_IPV6: ip = pkt.get_protocols(ipv6.ipv6)[0] if ip.dst in self.active_keys_v6: return # if from server, get port_id of server (reply from ARP will trigger this) if eth.src in self.server_mac_address: self.set_datapath_svr_port(dp.id, msg.match['in_port']) # do regular switching self.switching.packet_in_handler(ev)