def handle_socket(self) -> None: """Retrieves the next, waiting DHCP packet, parses it and calls the handler of the associated request. """ try: data, source_address = self._socket.recvfrom(2048) if len(data) == 0: self._log.warning("unexpectedly received EOF!") return packet = DhcpPacket() packet.source_address = source_address packet.DecodePacket(data) if (not packet.IsDhcpPacket()) or ( not packet.IsOption("dhcp_message_type") ): self._log.debug("Ignoring invalid packet") return dhcp_type = packet.GetOption("dhcp_message_type")[0] if dhcp_type not in self._DHCP_TYPE_HANDLERS: self._log.debug("Ignoring packet of unexpected DHCP type %d", dhcp_type) return xid = int.from_bytes(packet.GetOption('xid'), "big") if xid not in self._requests: self._log.debug("Ignoring answer with xid %r", xid) return request = self._requests[xid] clb_name = self._DHCP_TYPE_HANDLERS[dhcp_type] if not hasattr(request, clb_name): self._log.error("request has no callback '%s'", clb_name) return clb = getattr(request, clb_name) clb(packet) except Exception: self._log.exception('handling DHCP packet failed')
def handle_dhcp_ack(self, packet: DhcpPacket) -> None: """Called by the requestor as soon as a DHCP ACK packet is received for our XID. In case the packet matches what we currently expect, the packet is parsed and the success handler called. The request instance (self) is removed from the requestor and will therefore be destroyed soon. """ if self._state != self.AR_REQUEST: return self._log.debug("Received ACK") if not self._valid_source_address(packet): return if self._timeout_obj: self._timeout_mgr.del_timeout_object(self._timeout_obj) self._requestor.del_request(self) result = {} # type: Dict[str, Any] result['domain'] = packet.GetOption('domain_name').decode("ascii") translate_ips = { 'yiaddr': 'ip_address', 'subnet_mask': 'subnet_mask', 'router': 'gateway', } for opt_name in translate_ips: if not packet.IsOption(opt_name): continue val = packet.GetOption(opt_name) if len(val) == 4: result[translate_ips[opt_name]] = str(IPv4Address(val)) dns = [] # type: List[str] result['dns'] = dns dns_list = packet.GetOption('domain_name_server') while len(dns_list) >= 4: dns.append(str(IPv4Address(dns_list[:4]))) dns_list = dns_list[4:] if packet.IsOption('classless_static_route'): static_routes = parse_classless_static_routes( list(packet.GetOption('classless_static_route')) ) if static_routes is not None: if 'gateway' in result: # We MUST ignore a regular default route if static routes # are sent. del result['gateway'] # Find and filter out default route (if any). And set it as # the new gateway parameter. result['static_routes'] = [] for network, netmask, gateway in static_routes: if network == '0.0.0.0' and netmask == '0.0.0.0': result['gateway'] = gateway else: result['static_routes'].append((network, netmask, gateway)) del static_routes # Calculate lease timeouts (with RFC T1/T2 if not found in packet) lease_delta = int.from_bytes(packet.GetOption('ip_address_lease_time'), "big") result['lease_timeout'] = self._start_time + lease_delta if packet.IsOption('renewal_time_value'): renewal_delta = int.from_bytes( packet.GetOption('renewal_time_value'), "big" ) else: renewal_delta = int(lease_delta * 0.5) + random.randint(-5, 5) result['renewal_timeout'] = self._start_time + renewal_delta if packet.IsOption('rebinding_time_value'): rebinding_delta = int.from_bytes( packet.GetOption('rebinding_time_value'), "big" ) else: rebinding_delta = int(lease_delta * 0.875) + random.randint(-5, 5) result['rebinding_timeout'] = self._start_time + rebinding_delta self._success_handler(result)