def packet_out_free_arp(logicalport, logicalnet, logicalnetid, phyportid): egress = self._parent._gettableindex( "egress", self._connection.protocol.vhost) arp_request_packet = arp_packet_l4( dl_src=mac_addr(logicalport.mac_address), dl_dst=mac_addr("FF:FF:FF:FF:FF:FF"), arp_op=ofdef.ARPOP_REQUEST, arp_sha=mac_addr(logicalport.mac_address), arp_spa=ip4_addr(logicalport.ip_address), arp_tpa=ip4_addr(logicalport.ip_address)) return ofdef.ofp_packet_out( buffer_id=ofdef.OFP_NO_BUFFER, in_port=ofdef.OFPP_CONTROLLER, actions=[ ofdef.ofp_action_set_field(field=ofdef.create_oxm( ofdef.NXM_NX_REG4, logicalnetid)), ofdef.ofp_action_set_field(field=ofdef.create_oxm( ofdef.NXM_NX_REG5, logicalnetid)), ofdef.ofp_action_set_field(field=ofdef.create_oxm( ofdef.NXM_NX_REG6, phyportid)), ofdef.ofp_action_set_field( field=ofdef.create_oxm(ofdef.NXM_NX_REG7, 0x4000)), ofdef.nx_action_resubmit(table=egress) ], data=arp_request_packet._tobytes())
def _dhcp_handler(self): conn = self._connection ofdef = self._connection.openflowdef l3 = self._parent._gettableindex('l3input', self._connection.protocol.vhost) dhcp_packet_matcher = OpenflowAsyncMessageEvent.createMatcher( ofdef.OFPT_PACKET_IN, None, None, l3, 1, self._connection, self._connection.connmark) required_tags = [ d.OPTION_MESSAGE_TYPE, d.OPTION_SERVER_IDENTIFIER, d.OPTION_NETMASK, d.OPTION_ROUTER, d.OPTION_DNSSERVER, d.OPTION_BROADCAST, d.OPTION_MTU, d.OPTION_LEASE_TIME, d.OPTION_T1, d.OPTION_T2 ] server_mac = mac_addr(self._parent.servermac) trans_id = uint16.create(os.urandom(2)) def set_options(payload, option_dict, provide_options, message_type, remove_lease=False): message_type_opt = d.dhcp_option_message_type(value=message_type) if d.OPTION_REQUESTED_OPTIONS in option_dict: reqs = set(option_dict[d.OPTION_REQUESTED_OPTIONS].value) send_tags = [t for t in option_dict[d.OPTION_REQUESTED_OPTIONS].value if t == d.OPTION_MESSAGE_TYPE or t in provide_options] \ + [t for t in required_tags if (t in provide_options or t == d.OPTION_MESSAGE_TYPE) and t not in reqs] \ + [t for t in provide_options if t not in reqs and t not in required_tags] else: send_tags = [t for t in required_tags if t in provide_options or t == d.OPTION_MESSAGE_TYPE] \ + [t for t in set(provide_options.keys()).difference(required_tags)] d.build_options( payload, [ message_type_opt if t == d.OPTION_MESSAGE_TYPE else provide_options[t] for t in send_tags if not remove_lease or (t != d.OPTION_LEASE_TIME and t != d.OPTION_T1 and t != OPTION_T2) ], max(min(option_dict[d.OPTION_MAX_MESSAGE_SIZE].value, 1400), 576) if d.OPTION_MAX_MESSAGE_SIZE in option_dict else 576) def send_packet(pid, packet): for m in self.execute_commands(conn, [ ofdef.ofp_packet_out( buffer_id=ofdef.OFP_NO_BUFFER, in_port=ofdef.OFPP_CONTROLLER, actions=[ ofdef.ofp_action_output( port=pid, max_len=ofdef.OFPCML_NO_BUFFER) ], data=packet._tobytes()) ]): yield m while True: yield (dhcp_packet_matcher, ) msg = self.event.message try: in_port = ofdef.ofp_port_no.create( ofdef.get_oxm(msg.match.oxm_fields, ofdef.OXM_OF_IN_PORT)) if in_port not in self._dhcpentries: continue port_mac, port_ip, server_ip, provide_options = self._dhcpentries[ in_port] l7_packet = ethernet_l7.create(msg.data) dhcp_packet = d.dhcp_payload.create(l7_packet.data) if dhcp_packet.op != d.BOOTREQUEST or \ dhcp_packet.hlen != 6 or dhcp_packet.htype != 1 or \ dhcp_packet.magic_cookie != d.BOOTP_MAGIC_COOKIE or \ dhcp_packet.giaddr != 0: raise ValueError('Unsupported DHCP packet') options = d.reassemble_options(dhcp_packet) option_dict = dict((o.tag, o) for o in options) if d.OPTION_MESSAGE_TYPE not in option_dict: raise ValueError('Message type not found') message_type = option_dict[d.OPTION_MESSAGE_TYPE].value is_nak = False if message_type == d.DHCPDISCOVER: if dhcp_packet.chaddr[:6].ljust( 6, b'\x00') != mac_addr.tobytes(port_mac): # Ignore this packet continue dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=0, yiaddr=port_ip, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) set_options(dhcp_reply, option_dict, provide_options, d.DHCPOFFER) elif message_type == d.DHCPREQUEST: if d.OPTION_SERVER_IDENTIFIER in option_dict and option_dict[ d.OPTION_SERVER_IDENTIFIER].value != server_ip: # Ignore packets to wrong address continue if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac) \ or (d.OPTION_REQUESTED_IP in option_dict and option_dict[d.OPTION_REQUESTED_IP].value != port_ip) \ or (dhcp_packet.ciaddr != 0 and dhcp_packet.ciaddr != port_ip): dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=0, yiaddr=0, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) d.build_options(dhcp_reply, [ d.dhcp_option_message_type(value=d.DHCPNAK), d.dhcp_option_address( tag=d.OPTION_SERVER_IDENTIFIER, value=server_ip) ], 576, 0) is_nak = True else: dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=dhcp_packet.ciaddr, yiaddr=port_ip, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK) elif message_type == d.DHCPDECLINE: self._logger.warning('DHCP client reports DHCPDECLINE, there should be some problems.'\ ' Connection = %r(%016x), port = %d.', self._connection, self._connection.openflow_datapathid) elif message_type == d.DHCPRELEASE: # Safe to ignore continue elif message_type == d.DHCPINFORM: dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=dhcp_packet.ciaddr, yiaddr=0, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK, True) trans_id = (trans_id + 1) & 0xffff if (dhcp_packet.flags & d.DHCPFLAG_BROADCAST) or is_nak: dl_dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff] ip_dst = 0xffffffff else: dl_dst = l7_packet.dl_src ip_dst = port_ip reply_packet = ip4_packet_l7((ip4_payload, ip4_udp_payload), dl_src=server_mac, dl_dst=dl_dst, identifier=trans_id, ttl=128, ip_src=server_ip, ip_dst=ip_dst, sport=67, dport=68, data=dhcp_reply._tobytes()) self.subroutine(send_packet(in_port, reply_packet), True) except Exception: self._logger.info('Invalid DHCP packet received: %r', msg.data, exc_info=True)
def updateflow(self, connection, addvalues, removevalues, updatedvalues): try: allobjs = set(o for o in self._savedresult if o is not None and not o.isdeleted()) # Create DHCP entries lastlognetinfo = self._lastlognetinfo lastlogportinfo = self._lastlogportinfo lastserveraddresses = self._lastserveraddresses currentlognetinfo = dict( (n, id) for n, id in self._lastlognets if n in allobjs) currentlogportinfo = dict( (p, (id, currentlognetinfo[p.network], p.ip_address, p.mac_address, getattr(p.subnet, 'dhcp_server', self._parent.serveraddress)) ) for p, id in self._lastlogports if p in allobjs and p.network in currentlognetinfo and hasattr(p, 'subnet')) currentserveraddresses = set( (v[4], self._parent.servermac, p.network.id, True) for p, v in currentlogportinfo.items()) self._lastlognetinfo = currentlognetinfo self._lastlogportinfo = currentlogportinfo self._lastserveraddresses = currentserveraddresses dhcp_entries = {} t1 = self._parent.t1 t2 = self._parent.t2 for p in self._savedresult: if p is not None and not p.isdeleted() and p.isinstance(LogicalPort) and hasattr(p, 'subnet') \ and hasattr(p, 'ip_address') and hasattr(p, 'mac_address'): if p.network in currentlognetinfo and p in currentlogportinfo: try: pid, _, ip_address, mac_address, server_address = currentlogportinfo[ p] if getattr(p.subnet, 'dhcp_enabled', False): continue cidr = p.subnet.cidr network, mask = parse_ip4_network(cidr) # options from default settings entries = { d.OPTION_LEASE_TIME: (lambda x: d.DHCPTIME_INFINITE if x is None else x)(self._parent.leasetime) } # options from network if hasattr(p.network, 'dns_nameservers'): entries[ d. OPTION_DNSSERVER] = p.network.dns_nameservers if hasattr(p.network, 'domain_name'): entries[ d. OPTION_DOMAINNAME] = p.network.domain_name if hasattr(p.network, 'mtu'): entries[d.OPTION_MTU] = p.network.mtu if hasattr(p.network, 'ntp_servers'): entries[ d.OPTION_NTPSERVER] = p.network.ntp_servers if hasattr(p.network, 'lease_time'): entries[ d.OPTION_LEASE_TIME] = p.network.lease_time if hasattr(p.network, 'extra_dhcp_options'): entries.update(p.network.extra_dhcp_options) options = d.create_dhcp_options( entries, True, True) # options from subnet entries = { d.OPTION_NETMASK: ip4_addr.formatter(get_netmask(mask)), d.OPTION_BROADCAST: ip4_addr.formatter(get_broadcast( network, mask)) } if hasattr(p.subnet, 'gateway'): entries[d.OPTION_ROUTER] = p.subnet.gateway if hasattr(p.subnet, 'dns_nameservers'): entries[ d. OPTION_DNSSERVER] = p.subnet.dns_nameservers if hasattr(p.subnet, 'domain_name'): entries[ d.OPTION_DOMAINNAME] = p.subnet.domain_name if hasattr(p.subnet, 'mtu'): entries[d.OPTION_MTU] = p.subnet.mtu if hasattr(p.subnet, 'ntp_servers'): entries[ d.OPTION_NTPSERVER] = p.subnet.ntp_servers if hasattr(p.subnet, 'lease_time'): entries[ d.OPTION_LEASE_TIME] = p.subnet.lease_time # Routes is special if hasattr(p.subnet, 'host_routes'): routes = list(p.subnet.host_routes) if routes and not any( parse_ip4_network(r[0]) == (0, 0) for r in routes): routes.append( ['0.0.0.0/0', p.subnet.gateway]) entries[d.OPTION_CLASSLESSROUTE] = routes # TODO: add extra routes from routers if hasattr(p.subnet, 'extra_dhcp_options'): entries.update(p.subnet.extra_dhcp_options) options.update( d.create_dhcp_options(entries, True, True)) entries = {} if hasattr(p, 'hostname'): entries[d.OPTION_HOSTNAME] = p.hostname if hasattr(p, 'extra_dhcp_options'): entries.update(p.extra_dhcp_options) options.update( d.create_dhcp_options(entries, True, True)) entries = { d.OPTION_SERVER_IDENTIFIER: server_address } options.update( d.create_dhcp_options(entries, True, True)) options = dict((k, v) for k, v in options.items() if v is not None) if d.OPTION_LEASE_TIME not in options: options[ d. OPTION_LEASE_TIME] = d.create_option_from_value( d.OPTION_LEASE_TIME, d.DHCPTIME_INFINITE) if options[ d. OPTION_LEASE_TIME].value != d.DHCPTIME_INFINITE: leasetime = options[d.OPTION_LEASE_TIME].value if d.OPTION_T1 not in options and t1 is not None and t1 < leasetime: options[ d. OPTION_T1] = d.create_option_from_value( d.OPTION_T1, t1) if d.OPTION_T2 not in options and t2 is not None and t2 < leasetime: options[ d. OPTION_T1] = d.create_option_from_value( d.OPTION_T1, t2) dhcp_entries[pid] = (mac_addr(mac_address), ip4_addr(ip_address), ip4_addr(server_address), options) except Exception: self._logger.warning( "Failed to create DHCP options for port id=%r. Will disable DHCP on this port", p.id, exc_info=True) self._dhcpentries = dhcp_entries # Create flows # For every logical port, create two flows ofdef = connection.openflowdef vhost = connection.protocol.vhost l3 = self._parent._gettableindex('l3input', vhost) cmds = [] if connection.protocol.disablenxext: def match_network(nid): return [ ofdef.create_oxm(ofdef.OXM_OF_METADATA_W, ((nid & 0xffff) << 32) | (0xffff << 16), b'\x00\x00\xff\xff\x40\x00\x00\x00') ] else: def match_network(nid): return [ ofdef.create_oxm(ofdef.NXM_NX_REG5, nid), ofdef.create_oxm(ofdef.NXM_NX_REG7_W, 0x4000, 0x4000) ] def _delete_flows(nid): return ofdef.ofp_flow_mod( cookie=0x1, cookie_mask=0xffffffffffffffff, table_id=l3, command=ofdef.OFPFC_DELETE, priority=ofdef.OFP_DEFAULT_PRIORITY, buffer_id=ofdef.OFP_NO_BUFFER, out_port=ofdef.OFPP_ANY, out_group=ofdef.OFPG_ANY, match=ofdef.ofp_match_oxm(oxm_fields=match_network(nid) + [ ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ])) def _delete_flows2(nid, serveraddr): return ofdef.ofp_flow_mod( cookie=0x1, cookie_mask=0xffffffffffffffff, table_id=l3, command=ofdef.OFPFC_DELETE, priority=ofdef.OFP_DEFAULT_PRIORITY, buffer_id=ofdef.OFP_NO_BUFFER, out_port=ofdef.OFPP_ANY, out_group=ofdef.OFPG_ANY, match=ofdef.ofp_match_oxm(oxm_fields=match_network(nid) + [ ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_IPV4_DST, ip4_addr_bytes(serveraddr)), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ])) def _create_flows(nid): return ofdef.ofp_flow_mod( cookie=0x1, cookie_mask=0xffffffffffffffff, table_id=l3, command=ofdef.OFPFC_ADD, priority=ofdef.OFP_DEFAULT_PRIORITY, buffer_id=ofdef.OFP_NO_BUFFER, out_port=ofdef.OFPP_ANY, out_group=ofdef.OFPG_ANY, match=ofdef.ofp_match_oxm(oxm_fields=match_network(nid) + [ ofdef.create_oxm(ofdef.OXM_OF_ETH_DST_W, b'\x01\x00\x00\x00\x00\x00', b'\x01\x00\x00\x00\x00\x00'), ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ]), instructions=[ ofdef.ofp_instruction_actions(actions=[ ofdef.ofp_action_output( port=ofdef.OFPP_CONTROLLER, max_len=ofdef.OFPCML_NO_BUFFER) ]) ]) def _create_flows2(nid, serveraddr): return ofdef.ofp_flow_mod( cookie=0x1, cookie_mask=0xffffffffffffffff, table_id=l3, command=ofdef.OFPFC_ADD, priority=ofdef.OFP_DEFAULT_PRIORITY, buffer_id=ofdef.OFP_NO_BUFFER, out_port=ofdef.OFPP_ANY, out_group=ofdef.OFPG_ANY, match=ofdef.ofp_match_oxm(oxm_fields=match_network(nid) + [ ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_IPV4_DST, ip4_addr_bytes(serveraddr)), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ]), instructions=[ ofdef.ofp_instruction_actions(actions=[ ofdef.ofp_action_output( port=ofdef.OFPP_CONTROLLER, max_len=ofdef.OFPCML_NO_BUFFER) ]) ]) for n in removevalues: if n in lastlognetinfo: nid = lastlognetinfo[n] cmds.append(_delete_flows(nid)) lastnetdict = {n.id: n for n in lastlognetinfo} for serveraddr in lastserveraddresses: if serveraddr not in currentserveraddresses: addr, _, networkid, _ = serveraddr if networkid in lastnetdict: n = lastnetdict[networkid] nid = lastlognetinfo[n] cmds.append(_delete_flows2(nid, addr)) for m in self.execute_commands(connection, cmds): yield m # Remove old ARP entries; Add new ARP entries remove_arps = lastserveraddresses.difference( currentserveraddresses) if remove_arps: for m in callAPI(self, 'arpresponder', 'removeproxyarp', { 'connection': connection, 'arpentries': remove_arps }): yield m add_arps = currentserveraddresses.difference(lastserveraddresses) if add_arps: for m in callAPI(self, 'arpresponder', 'createproxyarp', { 'connection': connection, 'arpentries': add_arps }): yield m del cmds[:] for n in addvalues: if n in currentlognetinfo: nid = currentlognetinfo[n] cmds.append(_create_flows(nid)) currnetdict = {n.id: n for n in currentlognetinfo} for serveraddr in currentserveraddresses: if serveraddr not in lastserveraddresses: addr, _, networkid, _ = serveraddr if networkid in currnetdict: n = currnetdict[networkid] nid = currentlognetinfo[n] cmds.append(_create_flows2(nid, addr)) for m in self.execute_commands(connection, cmds): yield m except Exception: self._logger.warning( "Unexpected exception in DHCPUpdater. Will ignore and continue.", exc_info=True)
convert=_type_assert(format_ip_address, ip_address_type)) cidr_type = extra() cidr_type.bind(str, convert=_type_assert(partial(format_network_cidr, strict=True), cidr_type)) cidr_nonstrict_type = extra() cidr_nonstrict_type.bind(str, convert=_type_assert(format_network_cidr, cidr_nonstrict_type)) mac_address_type = extra() mac_address_type.bind(str, convert=_type_assert( lambda x: mac_addr.formatter(mac_addr(x)), mac_address_type, "Invalid MAC address")) # Auto convert to number when calling from Web API def _convert_to_int(s): try: return int(s) except ValueError: raise TypeMismatchException(s, autoint, "not an integer") str_int = extra() str_int.bind(str, convert=_type_assert(int, str_int, "not an integer")) autoint = (int, str_int)
async def _dhcp_handler(self): """ Mini DHCP server, respond DHCP packets from OpenFlow """ conn = self._connection ofdef = self._connection.openflowdef l3 = self._parent._gettableindex('l3input', self._connection.protocol.vhost) dhcp_packet_matcher = OpenflowAsyncMessageEvent.createMatcher( ofdef.OFPT_PACKET_IN, None, None, l3, 1, self._connection, self._connection.connmark) # These tags are important options. They are sent first to make sure the client # correctly receive these options. required_tags = [ d.OPTION_MESSAGE_TYPE, d.OPTION_SERVER_IDENTIFIER, d.OPTION_NETMASK, d.OPTION_ROUTER, d.OPTION_DNSSERVER, d.OPTION_BROADCAST, d.OPTION_MTU, d.OPTION_LEASE_TIME, d.OPTION_T1, d.OPTION_T2 ] server_mac = mac_addr(self._parent.servermac) # IP fragment identifier trans_id = uint16.create(os.urandom(2)) def set_options(payload, option_dict, provide_options, message_type, remove_lease=False): """ Set DHCP options to output payload regarding the incoming request :param payload: output DHCP payload :param option_dict: incoming DHCP options in request :param provide_options: all DHCP options that are ready to sent to the client :param message_type: output DHCP message type :param remove_lease: remove all leases options. DHCPINFORM cannot contain leases options. See https://tools.ietf.org/html/rfc2131#section-3.4 """ message_type_opt = d.dhcp_option_message_type(value=message_type) if d.OPTION_REQUESTED_OPTIONS in option_dict: # First requested options, then required options, then others reqs = set(option_dict[d.OPTION_REQUESTED_OPTIONS].value) send_tags = [t for t in option_dict[d.OPTION_REQUESTED_OPTIONS].value if t == d.OPTION_MESSAGE_TYPE or t in provide_options] \ + [t for t in required_tags if (t in provide_options or t == d.OPTION_MESSAGE_TYPE) and t not in reqs] \ + [t for t in provide_options if t not in reqs and t not in required_tags] else: # Required options, then others send_tags = [t for t in required_tags if t in provide_options or t == d.OPTION_MESSAGE_TYPE] \ + [t for t in set(provide_options.keys()).difference(required_tags)] # If the client has sent an option for max message size, use it; or use the RFC required 576 bytes not_finished = d.build_options( payload, [ message_type_opt if t == d.OPTION_MESSAGE_TYPE else provide_options[t] for t in send_tags if not remove_lease or (t != d.OPTION_LEASE_TIME and t != d.OPTION_T1 and t != OPTION_T2) ], max(min(option_dict[d.OPTION_MAX_MESSAGE_SIZE].value, 1400), 576) if d.OPTION_MAX_MESSAGE_SIZE in option_dict else 576) async def send_packet(pid, packet): """ Send DHCP packet to specified port """ await self.execute_commands(conn, [ ofdef.ofp_packet_out(buffer_id=ofdef.OFP_NO_BUFFER, in_port=ofdef.OFPP_CONTROLLER, actions=[ ofdef.ofp_action_output( port=pid, max_len=ofdef.OFPCML_NO_BUFFER) ], data=packet._tobytes()) ]) while True: ev = await dhcp_packet_matcher msg = ev.message try: in_port = ofdef.ofp_port_no.create( ofdef.get_oxm(msg.match.oxm_fields, ofdef.OXM_OF_IN_PORT)) if in_port not in self._dhcpentries: # Not a DHCP-enabled port continue port_mac, port_ip, server_ip, provide_options = self._dhcpentries[ in_port] # Fragmented DHCP packets are not supported - this is allowed according # to RFC: server and clients are only needed to support at least 576 bytes # DHCP messages. l7_packet = ethernet_l7.create(msg.data) dhcp_packet = d.dhcp_payload.create(l7_packet.data) # We only process a DHCP request directly sent from the logical port if (dhcp_packet.op != d.BOOTREQUEST # A DHCP server packet or dhcp_packet.hlen != 6 or dhcp_packet.htype != 1 # Hardware address not ethernet (48-bit) or dhcp_packet.magic_cookie != d.BOOTP_MAGIC_COOKIE # Magic cookie not match or dhcp_packet.giaddr != 0): # A relayed DHCP message raise ValueError('Unsupported DHCP packet') # Reassemble DHCP options options = d.reassemble_options(dhcp_packet) option_dict = dict((o.tag, o) for o in options) if d.OPTION_MESSAGE_TYPE not in option_dict: raise ValueError('Message type not found') message_type = option_dict[d.OPTION_MESSAGE_TYPE].value is_nak = False if message_type == d.DHCPDISCOVER: # A DHCPDISCOVER should get a DHCPOFFER response if dhcp_packet.chaddr[:6].ljust( 6, b'\x00') != mac_addr.tobytes(port_mac): # MAC address not match, ignore this packet continue dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=0, yiaddr=port_ip, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) set_options(dhcp_reply, option_dict, provide_options, d.DHCPOFFER) elif message_type == d.DHCPREQUEST: # A DHCPREQUEST should get a DHCPACK reply if d.OPTION_SERVER_IDENTIFIER in option_dict and option_dict[ d.OPTION_SERVER_IDENTIFIER].value != server_ip: # Ignore packets to wrong address continue if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac) \ or (d.OPTION_REQUESTED_IP in option_dict and option_dict[d.OPTION_REQUESTED_IP].value != port_ip) \ or (dhcp_packet.ciaddr != 0 and dhcp_packet.ciaddr != port_ip): # Requested MAC or IP not matched, send a NACK dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=0, yiaddr=0, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) # Do not send more options in NACK d.build_options(dhcp_reply, [ d.dhcp_option_message_type(value=d.DHCPNAK), d.dhcp_option_address( tag=d.OPTION_SERVER_IDENTIFIER, value=server_ip) ], 576, 0) is_nak = True else: dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=dhcp_packet.ciaddr, yiaddr=port_ip, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK) elif message_type == d.DHCPDECLINE: # Address already in use? self._logger.warning('DHCP client reports DHCPDECLINE, there should be some problems.'\ ' Connection = %r(%016x), port = %d.', self._connection, self._connection.openflow_datapathid) elif message_type == d.DHCPRELEASE: # Safe to ignore, we do not use a dynamic IP address pool continue elif message_type == d.DHCPINFORM: # Client setup IP addresses itself, but requesting more information # DHCPINFORM reply cannot have lease options, and yiaddr = 0 dhcp_reply = d.dhcp_payload( op=d.BOOTREPLY, htype=1, hlen=6, hops=0, xid=dhcp_packet.xid, secs=0, flags=dhcp_packet.flags, ciaddr=dhcp_packet.ciaddr, yiaddr=0, siaddr=0, giaddr=dhcp_packet.giaddr, chaddr=dhcp_packet.chaddr, magic_cookie=d.BOOTP_MAGIC_COOKIE) set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK, True) trans_id = (trans_id + 1) & 0xffff if (dhcp_packet.flags & d.DHCPFLAG_BROADCAST) or is_nak: # client request broadcast, or DHCPNAK # RFC requires that DHCPNAK uses broadcast dl_dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff] ip_dst = 0xffffffff else: dl_dst = l7_packet.dl_src ip_dst = port_ip reply_packet = ip4_packet_l7((ip4_payload, ip4_udp_payload), dl_src=server_mac, dl_dst=dl_dst, identifier=trans_id, ttl=128, ip_src=server_ip, ip_dst=ip_dst, sport=67, dport=68, data=dhcp_reply._tobytes()) # Send packet to the incoming port self.subroutine(send_packet(in_port, reply_packet), True) except Exception: self._logger.info('Invalid DHCP packet received: %r', msg.data, exc_info=True)
def _dhcp_handler(self): conn = self._connection ofdef = self._connection.openflowdef l3 = self._parent._gettableindex('l3input', self._connection.protocol.vhost) dhcp_packet_matcher = OpenflowAsyncMessageEvent.createMatcher(ofdef.OFPT_PACKET_IN, None, None, l3, 1, self._connection, self._connection.connmark) required_tags = [d.OPTION_MESSAGE_TYPE, d.OPTION_SERVER_IDENTIFIER, d.OPTION_NETMASK, d.OPTION_ROUTER, d.OPTION_DNSSERVER, d.OPTION_BROADCAST, d.OPTION_MTU, d.OPTION_LEASE_TIME, d.OPTION_T1, d.OPTION_T2] server_mac = mac_addr(self._parent.servermac) trans_id = uint16.create(os.urandom(2)) def set_options(payload, option_dict, provide_options, message_type, remove_lease = False): message_type_opt = d.dhcp_option_message_type(value = message_type) if d.OPTION_REQUESTED_OPTIONS in option_dict: reqs = set(option_dict[d.OPTION_REQUESTED_OPTIONS].value) send_tags = [t for t in option_dict[d.OPTION_REQUESTED_OPTIONS].value if t == d.OPTION_MESSAGE_TYPE or t in provide_options] \ + [t for t in required_tags if (t in provide_options or t == d.OPTION_MESSAGE_TYPE) and t not in reqs] \ + [t for t in provide_options if t not in reqs and t not in required_tags] else: send_tags = [t for t in required_tags if t in provide_options or t == d.OPTION_MESSAGE_TYPE] \ + [t for t in set(provide_options.keys()).difference(required_tags)] d.build_options(payload, [message_type_opt if t == d.OPTION_MESSAGE_TYPE else provide_options[t] for t in send_tags if not remove_lease or (t != d.OPTION_LEASE_TIME and t != d.OPTION_T1 and t != OPTION_T2)], max(min(option_dict[d.OPTION_MAX_MESSAGE_SIZE].value, 1400), 576) if d.OPTION_MAX_MESSAGE_SIZE in option_dict else 576) def send_packet(pid, packet): for m in self.execute_commands(conn, [ofdef.ofp_packet_out( buffer_id = ofdef.OFP_NO_BUFFER, in_port = ofdef.OFPP_CONTROLLER, actions = [ofdef.ofp_action_output(port = pid, max_len = ofdef.OFPCML_NO_BUFFER)], data = packet._tobytes() )]): yield m while True: yield (dhcp_packet_matcher,) msg = self.event.message try: in_port = ofdef.ofp_port_no.create(ofdef.get_oxm(msg.match.oxm_fields, ofdef.OXM_OF_IN_PORT)) if in_port not in self._dhcpentries: continue port_mac, port_ip, server_ip, provide_options = self._dhcpentries[in_port] l7_packet = ethernet_l7.create(msg.data) dhcp_packet = d.dhcp_payload.create(l7_packet.data) if dhcp_packet.op != d.BOOTREQUEST or \ dhcp_packet.hlen != 6 or dhcp_packet.htype != 1 or \ dhcp_packet.magic_cookie != d.BOOTP_MAGIC_COOKIE or \ dhcp_packet.giaddr != 0: raise ValueError('Unsupported DHCP packet') options = d.reassemble_options(dhcp_packet) option_dict = dict((o.tag, o) for o in options) if d.OPTION_MESSAGE_TYPE not in option_dict: raise ValueError('Message type not found') message_type = option_dict[d.OPTION_MESSAGE_TYPE].value is_nak = False if message_type == d.DHCPDISCOVER: if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac): # Ignore this packet continue dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY, htype = 1, hlen = 6, hops = 0, xid = dhcp_packet.xid, secs = 0, flags = dhcp_packet.flags, ciaddr = 0, yiaddr = port_ip, siaddr = 0, giaddr = dhcp_packet.giaddr, chaddr = dhcp_packet.chaddr, magic_cookie = d.BOOTP_MAGIC_COOKIE ) set_options(dhcp_reply, option_dict, provide_options, d.DHCPOFFER) elif message_type == d.DHCPREQUEST: if d.OPTION_SERVER_IDENTIFIER in option_dict and option_dict[d.OPTION_SERVER_IDENTIFIER].value != server_ip: # Ignore packets to wrong address continue if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac) \ or (d.OPTION_REQUESTED_IP in option_dict and option_dict[d.OPTION_REQUESTED_IP].value != port_ip) \ or (dhcp_packet.ciaddr != 0 and dhcp_packet.ciaddr != port_ip): dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY, htype = 1, hlen = 6, hops = 0, xid = dhcp_packet.xid, secs = 0, flags = dhcp_packet.flags, ciaddr = 0, yiaddr = 0, siaddr = 0, giaddr = dhcp_packet.giaddr, chaddr = dhcp_packet.chaddr, magic_cookie = d.BOOTP_MAGIC_COOKIE ) d.build_options(dhcp_reply, [d.dhcp_option_message_type(value = d.DHCPNAK), d.dhcp_option_address(tag = d.OPTION_SERVER_IDENTIFIER, value = server_ip)], 576, 0) is_nak = True else: dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY, htype = 1, hlen = 6, hops = 0, xid = dhcp_packet.xid, secs = 0, flags = dhcp_packet.flags, ciaddr = dhcp_packet.ciaddr, yiaddr = port_ip, siaddr = 0, giaddr = dhcp_packet.giaddr, chaddr = dhcp_packet.chaddr, magic_cookie = d.BOOTP_MAGIC_COOKIE ) set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK) elif message_type == d.DHCPDECLINE: self._logger.warning('DHCP client reports DHCPDECLINE, there should be some problems.'\ ' Connection = %r(%016x), port = %d.', self._connection, self._connection.openflow_datapathid) elif message_type == d.DHCPRELEASE: # Safe to ignore continue elif message_type == d.DHCPINFORM: dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY, htype = 1, hlen = 6, hops = 0, xid = dhcp_packet.xid, secs = 0, flags = dhcp_packet.flags, ciaddr = dhcp_packet.ciaddr, yiaddr = 0, siaddr = 0, giaddr = dhcp_packet.giaddr, chaddr = dhcp_packet.chaddr, magic_cookie = d.BOOTP_MAGIC_COOKIE ) set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK, True) trans_id = (trans_id + 1) & 0xffff if (dhcp_packet.flags & d.DHCPFLAG_BROADCAST) or is_nak: dl_dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff] ip_dst = 0xffffffff else: dl_dst = l7_packet.dl_src ip_dst = port_ip reply_packet = ip4_packet_l7((ip4_payload, ip4_udp_payload), dl_src = server_mac, dl_dst = dl_dst, identifier = trans_id, ttl = 128, ip_src = server_ip, ip_dst = ip_dst, sport = 67, dport = 68, data = dhcp_reply._tobytes() ) self.subroutine(send_packet(in_port, reply_packet), True) except Exception: self._logger.info('Invalid DHCP packet received: %r', msg.data, exc_info = True)
def updateflow(self, connection, addvalues, removevalues, updatedvalues): try: allobjs = set(o for o in self._savedresult if o is not None and not o.isdeleted()) # Create DHCP entries lastlognetinfo = self._lastlognetinfo lastlogportinfo = self._lastlogportinfo lastserveraddresses = self._lastserveraddresses currentlognetinfo = dict((n, id) for n,id in self._lastlognets if n in allobjs) currentlogportinfo = dict((p, (id, currentlognetinfo[p.network], p.ip_address, p.mac_address, getattr(p.subnet, 'dhcp_server', self._parent.serveraddress))) for p,id in self._lastlogports if p in allobjs and p.network in currentlognetinfo and hasattr(p,'subnet')) currentserveraddresses = set((v[4], self._parent.servermac, p.network.id, True) for p,v in currentlogportinfo.items()) self._lastlognetinfo = currentlognetinfo self._lastlogportinfo = currentlogportinfo self._lastserveraddresses = currentserveraddresses dhcp_entries = {} t1 = self._parent.t1 t2 = self._parent.t2 for p in self._savedresult: if p is not None and not p.isdeleted() and p.isinstance(LogicalPort) and hasattr(p, 'subnet') \ and hasattr(p, 'ip_address') and hasattr(p, 'mac_address'): if p.network in currentlognetinfo and p in currentlogportinfo: try: pid, _, ip_address, mac_address, server_address = currentlogportinfo[p] if getattr(p.subnet, 'dhcp_enabled', False): continue cidr = p.subnet.cidr network, mask = parse_ip4_network(cidr) # options from default settings entries = {d.OPTION_LEASE_TIME: (lambda x: d.DHCPTIME_INFINITE if x is None else x)(self._parent.leasetime) } # options from network if hasattr(p.network, 'dns_nameservers'): entries[d.OPTION_DNSSERVER] = p.network.dns_nameservers if hasattr(p.network, 'domain_name'): entries[d.OPTION_DOMAINNAME] = p.network.domain_name if hasattr(p.network, 'mtu'): entries[d.OPTION_MTU] = p.network.mtu if hasattr(p.network, 'ntp_servers'): entries[d.OPTION_NTPSERVER] = p.network.ntp_servers if hasattr(p.network, 'lease_time'): entries[d.OPTION_LEASE_TIME] = p.network.lease_time if hasattr(p.network, 'extra_dhcp_options'): entries.update(p.network.extra_dhcp_options) options = d.create_dhcp_options(entries, True, True) # options from subnet entries = {d.OPTION_NETMASK : ip4_addr.formatter(get_netmask(mask)), d.OPTION_BROADCAST : ip4_addr.formatter(get_broadcast(network, mask))} if hasattr(p.subnet, 'gateway'): entries[d.OPTION_ROUTER] = p.subnet.gateway if hasattr(p.subnet, 'dns_nameservers'): entries[d.OPTION_DNSSERVER] = p.subnet.dns_nameservers if hasattr(p.subnet, 'domain_name'): entries[d.OPTION_DOMAINNAME] = p.subnet.domain_name if hasattr(p.subnet, 'mtu'): entries[d.OPTION_MTU] = p.subnet.mtu if hasattr(p.subnet, 'ntp_servers'): entries[d.OPTION_NTPSERVER] = p.subnet.ntp_servers if hasattr(p.subnet, 'lease_time'): entries[d.OPTION_LEASE_TIME] = p.subnet.lease_time # Routes is special if hasattr(p.subnet, 'host_routes'): routes = list(p.subnet.host_routes) if routes and not any(parse_ip4_network(r[0]) == (0,0) for r in routes): routes.append(['0.0.0.0/0', p.subnet.gateway]) entries[d.OPTION_CLASSLESSROUTE] = routes # TODO: add extra routes from routers if hasattr(p.subnet, 'extra_dhcp_options'): entries.update(p.subnet.extra_dhcp_options) options.update(d.create_dhcp_options(entries, True, True)) entries = {} if hasattr(p, 'hostname'): entries[d.OPTION_HOSTNAME] = p.hostname if hasattr(p, 'extra_dhcp_options'): entries.update(p.extra_dhcp_options) options.update(d.create_dhcp_options(entries, True, True)) entries = {d.OPTION_SERVER_IDENTIFIER: server_address} options.update(d.create_dhcp_options(entries, True, True)) options = dict((k,v) for k,v in options.items() if v is not None) if d.OPTION_LEASE_TIME not in options: options[d.OPTION_LEASE_TIME] = d.create_option_from_value(d.OPTION_LEASE_TIME, d.DHCPTIME_INFINITE) if options[d.OPTION_LEASE_TIME].value != d.DHCPTIME_INFINITE: leasetime = options[d.OPTION_LEASE_TIME].value if d.OPTION_T1 not in options and t1 is not None and t1 < leasetime: options[d.OPTION_T1] = d.create_option_from_value(d.OPTION_T1, t1) if d.OPTION_T2 not in options and t2 is not None and t2 < leasetime: options[d.OPTION_T1] = d.create_option_from_value(d.OPTION_T1, t2) dhcp_entries[pid] = (mac_addr(mac_address), ip4_addr(ip_address), ip4_addr(server_address), options) except Exception: self._logger.warning("Failed to create DHCP options for port id=%r. Will disable DHCP on this port", p.id, exc_info = True) self._dhcpentries = dhcp_entries # Create flows # For every logical port, create two flows ofdef = connection.openflowdef vhost = connection.protocol.vhost l3 = self._parent._gettableindex('l3input', vhost) cmds = [] if connection.protocol.disablenxext: def match_network(nid): return [ofdef.create_oxm(ofdef.OXM_OF_METADATA_W, ((nid & 0xffff) << 32) | (0xffff << 16), b'\x00\x00\xff\xff\x40\x00\x00\x00')] else: def match_network(nid): return [ofdef.create_oxm(ofdef.NXM_NX_REG5, nid), ofdef.create_oxm(ofdef.NXM_NX_REG7_W, 0x4000, 0x4000)] def _delete_flows(nid): return ofdef.ofp_flow_mod(cookie = 0x1, cookie_mask = 0xffffffffffffffff, table_id = l3, command = ofdef.OFPFC_DELETE, priority = ofdef.OFP_DEFAULT_PRIORITY, buffer_id = ofdef.OFP_NO_BUFFER, out_port = ofdef.OFPP_ANY, out_group = ofdef.OFPG_ANY, match = ofdef.ofp_match_oxm( oxm_fields = match_network(nid) + [ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ] ) ) def _delete_flows2(nid, serveraddr): return ofdef.ofp_flow_mod(cookie = 0x1, cookie_mask = 0xffffffffffffffff, table_id = l3, command = ofdef.OFPFC_DELETE, priority = ofdef.OFP_DEFAULT_PRIORITY, buffer_id = ofdef.OFP_NO_BUFFER, out_port = ofdef.OFPP_ANY, out_group = ofdef.OFPG_ANY, match = ofdef.ofp_match_oxm( oxm_fields = match_network(nid) + [ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_IPV4_DST, ip4_addr_bytes(serveraddr)), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ] ) ) def _create_flows(nid): return ofdef.ofp_flow_mod(cookie = 0x1, cookie_mask = 0xffffffffffffffff, table_id = l3, command = ofdef.OFPFC_ADD, priority = ofdef.OFP_DEFAULT_PRIORITY, buffer_id = ofdef.OFP_NO_BUFFER, out_port = ofdef.OFPP_ANY, out_group = ofdef.OFPG_ANY, match = ofdef.ofp_match_oxm( oxm_fields = match_network(nid) + [ofdef.create_oxm(ofdef.OXM_OF_ETH_DST_W, b'\x01\x00\x00\x00\x00\x00', b'\x01\x00\x00\x00\x00\x00'), ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ] ), instructions = [ ofdef.ofp_instruction_actions( actions = [ ofdef.ofp_action_output(port = ofdef.OFPP_CONTROLLER, max_len = ofdef.OFPCML_NO_BUFFER ) ] ) ] ) def _create_flows2(nid, serveraddr): return ofdef.ofp_flow_mod(cookie = 0x1, cookie_mask = 0xffffffffffffffff, table_id = l3, command = ofdef.OFPFC_ADD, priority = ofdef.OFP_DEFAULT_PRIORITY, buffer_id = ofdef.OFP_NO_BUFFER, out_port = ofdef.OFPP_ANY, out_group = ofdef.OFPG_ANY, match = ofdef.ofp_match_oxm( oxm_fields = match_network(nid) + [ofdef.create_oxm(ofdef.OXM_OF_ETH_TYPE, ofdef.ETHERTYPE_IP), ofdef.create_oxm(ofdef.OXM_OF_IP_PROTO, ofdef.IPPROTO_UDP), ofdef.create_oxm(ofdef.OXM_OF_IPV4_DST, ip4_addr_bytes(serveraddr)), ofdef.create_oxm(ofdef.OXM_OF_UDP_DST, 67) ] ), instructions = [ ofdef.ofp_instruction_actions( actions = [ ofdef.ofp_action_output(port = ofdef.OFPP_CONTROLLER, max_len = ofdef.OFPCML_NO_BUFFER ) ] ) ] ) for n in removevalues: if n in lastlognetinfo: nid = lastlognetinfo[n] cmds.append(_delete_flows(nid)) lastnetdict = dict((n.id,n) for n in lastlognetinfo) for serveraddr in lastserveraddresses: if serveraddr not in currentserveraddresses: addr, _, networkid, _ = serveraddr if networkid in lastnetdict: n = lastnetdict[networkid] nid = lastlognetinfo[n] cmds.append(_delete_flows2(nid, addr)) for m in self.execute_commands(connection, cmds): yield m # Remove old ARP entries; Add new ARP entries remove_arps = lastserveraddresses.difference(currentserveraddresses) if remove_arps: for m in callAPI(self, 'arpresponder', 'removeproxyarp', {'connection': connection, 'arpentries': remove_arps}): yield m add_arps = currentserveraddresses.difference(lastserveraddresses) if add_arps: for m in callAPI(self, 'arpresponder', 'createproxyarp', {'connection': connection, 'arpentries': add_arps}): yield m del cmds[:] for n in addvalues: if n in currentlognetinfo: nid = currentlognetinfo[n] cmds.append(_create_flows(nid)) currnetdict = dict((n.id,n) for n in currentlognetinfo) for serveraddr in currentserveraddresses: if serveraddr not in lastserveraddresses: addr, _, networkid, _ = serveraddr if networkid in currnetdict: n = currnetdict[networkid] nid = currentlognetinfo[n] cmds.append(_create_flows2(nid, addr)) for m in self.execute_commands(connection, cmds): yield m except Exception: self._logger.warning("Unexpected exception in DHCPUpdater. Will ignore and continue.", exc_info = True)