def __init__(self, raw_option: Optional[bytes] = None, opt_req_ip_addr: Optional[Ip4Address] = None) -> None: if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_req_ip_addr = Ip4Address(raw_option[2:6]) else: assert opt_req_ip_addr is not None self.opt_code = DHCP4_OPT_REQ_IP4_ADDR self.opt_len = DHCP4_OPT_REQ_IP4_ADDR_LEN self.opt_req_ip_addr = Ip4Address(opt_req_ip_addr)
def __init__(self, raw_option: Optional[bytes] = None, opt_dns: Optional[list[Ip4Address]] = None) -> None: if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_dns = [ Ip4Address(raw_option[_:_ + 4]) for _ in range(2, 2 + self.opt_len, 4) ] else: assert opt_dns is not None self.opt_code = DHCP4_OPT_DNS self.opt_len = len(opt_dns) * 4 self.opt_dns = [Ip4Address(_) for _ in opt_dns]
def _set_ip_addresses( self, remote_address: tuple[str, int], local_ip_address: IpAddress, local_port: int, remote_port: int ) -> tuple[Union[Ip6Address, Ip4Address], Union[Ip6Address, Ip4Address]]: """Validate remote address and pick appropriate local ip address as needed""" try: remote_ip_address: Union[Ip6Address, Ip4Address] = Ip6Address( remote_address[0]) if self._family is AF_INET6 else Ip4Address( remote_address[0]) except (Ip6AddressFormatError, Ip4AddressFormatError): raise gaierror( "[Errno -2] Name or service not known - [Malformed remote IP address]" ) # This contraption here is to mimic behavior of BSD socket implementation if remote_ip_address.is_unspecified: if self._type is SOCK_STREAM: raise ConnectionRefusedError( "[Errno 111] Connection refused - [Unspecified remote IP address]" ) if self._type is SOCK_DGRAM: self._unreachable = True if local_ip_address.is_unspecified: local_ip_address = pick_local_ip_address(remote_ip_address) if local_ip_address.is_unspecified and not (local_port == 68 and remote_port == 67): raise gaierror( "[Errno -2] Name or service not known - [Malformed remote IP address]" ) assert isinstance(local_ip_address, Ip4Address) or isinstance( local_ip_address, Ip6Address) return local_ip_address, remote_ip_address
def bind(self, address: tuple[str, int]) -> None: """Bind the socket to local address""" # 'bind' call will bind socket to specific / unspecified local ip address and specific local port # in case provided port equals zero port value will be picked automatically # Check if "bound" already if self._local_port in range(1, 65536): raise OSError("[Errno 22] Invalid argument - [Socket bound to specific port already]") local_ip_address: IpAddress if self._family is AF_INET6: try: if (local_ip_address := Ip6Address(address[0])) not in set(stack.packet_handler.ip6_unicast) | {Ip6Address(0)}: raise OSError("[Errno 99] Cannot assign requested address - [Local IP address not owned by stack]") except Ip6AddressFormatError: raise gaierror("[Errno -2] Name or service not known - [Malformed local IP address]") if self._family is AF_INET4: try: if (local_ip_address := Ip4Address(address[0])) not in set(stack.packet_handler.ip4_unicast) | {Ip4Address(0)}: raise OSError("[Errno 99] Cannot assign requested address - [Local IP address not owned by stack]") except Ip4AddressFormatError: raise gaierror("[Errno -2] Name or service not known - [Malformed local IP address]") # Sanity check on local port number if address[1] not in range(0, 65536): raise OverflowError("bind(): port must be 0-65535. - [Port out of range]") # Confirm or pick local port number if (local_port := address[1]) > 0: if self._is_address_in_use(local_ip_address, local_port): raise OSError("[Errno 98] Address already in use - [Local address already in use]")
def str_to_ip(ip_address: str) -> Optional[Union[Ip6Address, Ip4Address]]: """Convert string to appropriate IP address""" try: return Ip6Address(ip_address) except Ip6AddressFormatError: try: return Ip4Address(ip_address) except Ip4AddressFormatError: return None
def ip_version(ip_address: str) -> Optional[int]: """Return version of IP address string""" try: return Ip6Address(ip_address).version except Ip6AddressFormatError: try: return Ip4Address(ip_address).version except Ip4AddressFormatError: return None
def __init__(self, raw_option: Optional[bytes] = None, opt_srv_id: Optional[Ip4Address] = None) -> None: if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_srv_id = Ip4Address(raw_option[2:6]) else: assert opt_srv_id is not None self.opt_code = DHCP4_OPT_SRV_ID self.opt_len = DHCP4_OPT_SRV_ID_LEN self.opt_srv_id = opt_srv_id
def pick_local_ip4_address(remote_ip4_address: Ip4Address) -> Ip4Address: """Pick appropriate source IPv4 address based on provided destination IPv4 address""" # If destination belongs to any of local networks, if so pick source address from that network for ip4_host in stack.packet_handler.ip4_host: if remote_ip4_address in ip4_host.network: return ip4_host.address # If destination is external pick source from first network that has default gateway set for ip4_host in stack.packet_handler.ip4_host: if ip4_host.gateway: return ip4_host.address # In case everything else fails return unspecified return Ip4Address(0)
def _send_arp_probe(self, ip4_unicast: Ip4Address) -> None: """Send out ARP probe to detect possible IP conflict""" self._phtx_arp( ether_src=self.mac_unicast, ether_dst=MacAddress(0xFFFFFFFFFFFF), arp_oper=ARP_OP_REQUEST, arp_sha=self.mac_unicast, arp_spa=Ip4Address(0), arp_tha=MacAddress(0), arp_tpa=ip4_unicast, ) if __debug__: log("stack", f"Sent out ARP probe for {ip4_unicast}")
def __init__(self, family: AddressFamily) -> None: """Class constructor""" super().__init__() self._family: AddressFamily = family self._type: SocketType = SOCK_DGRAM self._local_port: int = 0 self._remote_port: int = 0 self._packet_rx_md: list[UdpMetadata] = [] self._packet_rx_md_ready: Semaphore = threading.Semaphore(0) self._unreachable: bool = False self._local_ip_address: IpAddress self._remote_ip_address: IpAddress if self._family is AF_INET6: self._local_ip_address = Ip6Address(0) self._remote_ip_address = Ip6Address(0) if self._family is AF_INET4: self._local_ip_address = Ip4Address(0) self._remote_ip_address = Ip4Address(0) if __debug__: log("socket", f"<g>[{self}]</> - Created socket")
def _parse_stack_ip4_host_candidate( self, configured_ip4_address_candidate: list[tuple[str, Optional[str]]] ) -> list[Ip4Host]: """Parse IPv4 candidate host addresses configured in config.py module""" valid_address_candidate: list[Ip4Host] = [] for str_host, str_gateway in configured_ip4_address_candidate: if __debug__: log("stack", f"Parsing ('{str_host}', '{str_gateway}') entry") try: host = Ip4Host(str_host) except Ip4HostFormatError: if __debug__: log( "stack", f"<WARN>Invalid host address '{str_host}' format, skipping</>" ) continue if not host.address.is_private and not host.address.is_global: if __debug__: log( "stack", f"<WARN>Invalid host address '{host.address}' type, skipping</>" ) continue if host.address == host.network.address or host.address == host.network.broadcast: if __debug__: log( "stack", f"<WARN>Invalid host address '{host.address}' configured for network '{host.network}', skipping</>" ) continue if host.address in [_.address for _ in valid_address_candidate]: if __debug__: log( "stack", f"<WARN>Duplicate host address '{host.address}' configured, skipping</>" ) continue if str_gateway is not None: try: gateway: Optional[Ip4Address] = Ip4Address(str_gateway) if gateway not in host.network or gateway in { host.network.address, host.network.broadcast, host.address }: if __debug__: log( "stack", f"<WARN>Invalid gateway '{gateway}' configuren for host address '{host}', skipping</>" ) continue except Ip4AddressFormatError: if __debug__: log( "stack", f"<WARN>Invalid gateway '{str_gateway}' format configured for host address '{host}' skipping</>" ) continue else: gateway = None host.gateway = gateway valid_address_candidate.append(host) if __debug__: log("stack", f"Parsed ('{host}', '{host.gateway}') entry") return valid_address_candidate
def _validate_src_ip4_address(self, ip4_src: Ip4Address, ip4_dst: Ip4Address, tracker: Tracker) -> Optional[Ip4Address]: """Make sure source ip address is valid, supplemt with valid one as appropriate""" # Check if the the source IP address belongs to this stack or its set to all zeros (for DHCP client communication) if ip4_src not in { *self.ip4_unicast, *self.ip4_multicast, *self.ip4_broadcast, Ip4Address(0) }: if __debug__: log( "ip4", f"{tracker} - <WARN>Unable to sent out IPv4 packet, stack doesn't own IPv4 address {ip4_src}, dropping</>" ) return None # If packet is a response to multicast then replace source address with primary address of the stack if ip4_src in self.ip4_multicast: if self.ip4_unicast: ip4_src = self.ip4_unicast[0] if __debug__: log( "ip4", f"{tracker} - Packet is response to multicast, replaced source with stack primary IPv4 address {ip4_src}" ) else: if __debug__: log( "ip4", f"{tracker} - <WARN>Unable to sent out IPv4 packet, no stack primary unicast IPv4 address available, dropping</>" ) return None # If packet is a response to limited broadcast then replace source address with primary address of the stack if ip4_src.is_limited_broadcast: if self.ip4_unicast: ip4_src = self.ip4_unicast[0] if __debug__: log( "ip4", f"{tracker} - Packet is response to limited broadcast, replaced source with stack primary IPv4 address {ip4_src}" ) else: if __debug__: log( "ip4", f"{tracker} - <WARN>Unable to sent out IPv4 packet, no stack primary unicast IPv4 address available, dropping</>" ) return None # If packet is a response to directed braodcast then replace source address with first stack address that belongs to appropriate subnet if ip4_src in self.ip4_broadcast: ip4_src_list = [ _.address for _ in self.ip4_host if _.network.broadcast == ip4_src ] if ip4_src_list: ip4_src = ip4_src_list[0] if __debug__: log( "ip4", f"{tracker} - Packet is response to directed broadcast, replaced source with appropriate IPv4 address {ip4_src}" ) else: if __debug__: log( "ip4", f"{tracker} - <WARN>Unable to sent out IPv4 packet, no appropriate stack unicast IPv4 address available, dropping</>" ) return None # If source is unspecified try to find best match for given destination if ip4_src.is_unspecified: return pick_local_ip4_address(ip4_dst) return ip4_src
def ip4_broadcast(self) -> list[Ip4Address]: """Return list of stack's IPv4 broadcast addresses""" ip4_broadcast = [_.network.broadcast for _ in self.ip4_host] ip4_broadcast.append(Ip4Address(0xFFFFFFFF)) return ip4_broadcast
def spa(self) -> Ip4Address: """Read 'Sender protocol address' field""" if "_cache__spa" not in self.__dict__: self._cache__spa = Ip4Address(self._frame[14:18]) return self._cache__spa
def tpa(self) -> Ip4Address: """Read 'Target protocol address' field""" if "_cache__tpa" not in self.__dict__: self._cache__tpa = Ip4Address(self._frame[24:28]) return self._cache__tpa
def dst(self) -> Ip4Address: """Read 'Destination address' field""" if "_cache__dst" not in self.__dict__: self._cache__dst = Ip4Address(self._frame[16:20]) return self._cache__dst
def src(self) -> Ip4Address: """Read 'Source address' field""" if "_cache__src" not in self.__dict__: self._cache__src = Ip4Address(self._frame[12:16]) return self._cache__src
def fetch(self) -> Union[tuple[str, Optional[str]], tuple[None, None]]: """IPv4 DHCP client""" s = socket.socket(family=socket.AF_INET4, type=socket.SOCK_DGRAM) s.bind(("0.0.0.0", 68)) s.connect(("255.255.255.255", 67)) dhcp_xid = random.randint(0, 0xFFFFFFFF) # Send DHCP Discover s.send( dhcp4.ps.Dhcp4Packet( dhcp_op=dhcp4.ps.DHCP4_OP_REQUEST, dhcp_xid=dhcp_xid, dhcp_ciaddr=Ip4Address("0.0.0.0"), dhcp_yiaddr=Ip4Address("0.0.0.0"), dhcp_siaddr=Ip4Address("0.0.0.0"), dhcp_giaddr=Ip4Address("0.0.0.0"), dhcp_chaddr=bytes(self._mac_address), dhcp_msg_type=dhcp4.ps.DHCP4_MSG_DISCOVER, dhcp_param_req_list=[ dhcp4.ps.DHCP4_OPT_SUBNET_MASK, dhcp4.ps.DHCP4_OPT_ROUTER ], dhcp_host_name="PyTCP", ).raw_packet) if __debug__: log("dhcp4", "Sent out DHCP Discover message") # Wait for DHCP Offer try: dhcp_packet_rx = dhcp4.ps.Dhcp4Packet(s.recv(timeout=5)) except socket.ReceiveTimeout: if __debug__: log("dhcp4", "Didn't receive DHCP Offer message - timeout") s.close() return None, None if dhcp_packet_rx.dhcp_msg_type != dhcp4.ps.DHCP4_MSG_OFFER: if __debug__: log("dhcp4", "Didn't receive DHCP Offer message - message type error") s.close() return None, None dhcp_srv_id = dhcp_packet_rx.dhcp_srv_id dhcp_yiaddr = dhcp_packet_rx.dhcp_yiaddr if __debug__: log( "dhcp4", f"ClientUdpDhcp: Received DHCP Offer from {dhcp_packet_rx.dhcp_srv_id}" + f"IP: {dhcp_packet_rx.dhcp_yiaddr}, Mask: {dhcp_packet_rx.dhcp_subnet_mask}, Router: {dhcp_packet_rx.dhcp_router}" + f"DNS: {dhcp_packet_rx.dhcp_dns}, Domain: {dhcp_packet_rx.dhcp_domain_name}", ) # Send DHCP Request s.send( dhcp4.ps.Dhcp4Packet( dhcp_op=dhcp4.ps.DHCP4_OP_REQUEST, dhcp_xid=dhcp_xid, dhcp_ciaddr=Ip4Address("0.0.0.0"), dhcp_yiaddr=Ip4Address("0.0.0.0"), dhcp_siaddr=Ip4Address("0.0.0.0"), dhcp_giaddr=Ip4Address("0.0.0.0"), dhcp_chaddr=bytes(self._mac_address), dhcp_msg_type=dhcp4.ps.DHCP4_MSG_REQUEST, dhcp_srv_id=dhcp_srv_id, dhcp_req_ip_addr=dhcp_yiaddr, dhcp_param_req_list=[ dhcp4.ps.DHCP4_OPT_SUBNET_MASK, dhcp4.ps.DHCP4_OPT_ROUTER ], dhcp_host_name="PyTCP", ).raw_packet) if __debug__: log( "dhcp4", f"Sent out DHCP Request message to {dhcp_packet_rx.dhcp_srv_id}" ) # Wait for DHCP Ack try: dhcp_packet_rx = dhcp4.ps.Dhcp4Packet(s.recv(timeout=5)) except socket.ReceiveTimeout: if __debug__: log("dhcp4", "Didn't receive DHCP ACK message - timeout") s.close() return None, None if dhcp_packet_rx.dhcp_msg_type != dhcp4.ps.DHCP4_MSG_ACK: if __debug__: log("dhcp4", "Didn't receive DHCP ACK message - message type error") s.close() return None, None if __debug__: log( "dhcp4", f"Received DHCP Offer from {dhcp_packet_rx.dhcp_srv_id}" + f"IP: {dhcp_packet_rx.dhcp_yiaddr}, Mask: {dhcp_packet_rx.dhcp_subnet_mask}, Router: {dhcp_packet_rx.dhcp_router}" + f"DNS: {dhcp_packet_rx.dhcp_dns}, Domain: {dhcp_packet_rx.dhcp_domain_name}", ) s.close() assert dhcp_packet_rx.dhcp_subnet_mask is not None return ( str(dhcp_packet_rx.dhcp_yiaddr) + str(dhcp_packet_rx.dhcp_subnet_mask), str(dhcp_packet_rx.dhcp_router[0]) if dhcp_packet_rx.dhcp_router is not None else None, )
def _phrx_icmp4(self, packet_rx: PacketRx) -> None: """Handle inbound ICMPv4 packets""" Icmp4Parser(packet_rx) if packet_rx.parse_failed: if __debug__: log("icmp4", f"{packet_rx.tracker} - <CRIT>{packet_rx.parse_failed}</>") return if __debug__: log("icmp4", f"{packet_rx.tracker} - {packet_rx.icmp4}") # ICMPv4 Echo Request packet if packet_rx.icmp4.type == ICMP4_ECHO_REQUEST: if __debug__: log( "icmp4", f"{packet_rx.tracker} - <INFO>Received ICMPv4 Echo Request packet from {packet_rx.ip4.src}, sending reply</>" ) self._phtx_icmp4( ip4_src=packet_rx.ip4.dst, ip4_dst=packet_rx.ip4.src, icmp4_type=ICMP4_ECHO_REPLY, icmp4_ec_id=packet_rx.icmp4.ec_id, icmp4_ec_seq=packet_rx.icmp4.ec_seq, icmp4_ec_data=packet_rx.icmp4.ec_data, echo_tracker=packet_rx.tracker, ) return # ICMPv4 Unreachable packet if packet_rx.icmp4.type == ICMP4_UNREACHABLE: if __debug__: log( "icmp4", f"{packet_rx.tracker} - Received ICMPv4 Unreachable packet from {packet_rx.ip4.src}, will try to match UDP socket" ) # Quick and dirty way to validate received data and pull useful information from it frame = packet_rx.icmp4.un_data if (len(frame) >= IP4_HEADER_LEN and frame[0] >> 4 == 4 and len(frame) >= ((frame[0] & 0b00001111) << 2) and frame[9] == IP4_PROTO_UDP and len(frame) >= ((frame[0] & 0b00001111) << 2) + UDP_HEADER_LEN): # Create UdpMetadata object and try to find matching UDP socket udp_offset = (frame[0] & 0b00001111) << 2 packet = UdpMetadata( local_ip_address=Ip4Address(frame[12:16]), remote_ip_address=Ip4Address(frame[16:20]), local_port=struct.unpack("!H", frame[udp_offset + 0:udp_offset + 2])[0], remote_port=struct.unpack("!H", frame[udp_offset + 2:udp_offset + 4])[0], ) for socket_pattern in packet.socket_patterns: socket = stack.sockets.get(socket_pattern, None) if socket: if __debug__: log( "icmp4", f"{packet_rx.tracker} - <INFO>Found matching listening socket {socket}, for Unreachable packet from {packet_rx.ip4.src}</>", ) socket.notify_unreachable() return if __debug__: log( "icmp4", f"{packet_rx.tracker} - Unreachable data doesn't match any UDP socket" ) return if __debug__: log( "icmp4", f"{packet_rx.tracker} - Unreachable data doesn't pass basic IPv4/UDP integrity check" ) return
def __init__( self, raw_packet: Optional[bytes] = None, dhcp_op: Optional[int] = None, dhcp_xid: Optional[int] = None, dhcp_flag_b: Optional[bool] = False, dhcp_ciaddr: Optional[Ip4Address] = None, dhcp_yiaddr: Optional[Ip4Address] = None, dhcp_siaddr: Optional[Ip4Address] = None, dhcp_giaddr: Optional[Ip4Address] = None, dhcp_chaddr: Optional[bytes] = None, dhcp_subnet_mask: Optional[Ip4Mask] = None, dhcp_router: Optional[list[Ip4Address]] = None, dhcp_dns: Optional[list[Ip4Address]] = None, dhcp_host_name: Optional[str] = None, dhcp_domain_name: Optional[str] = None, dhcp_req_ip_addr: Optional[Ip4Address] = None, dhcp_addr_lease_time: Optional[int] = None, dhcp_srv_id: Optional[Ip4Address] = None, dhcp_param_req_list: Optional[list[int]] = None, dhcp_msg_type: Optional[int] = None, ) -> None: """Class constructor""" # Packet parsing if raw_packet: raw_header = raw_packet[:DHCP4_HEADER_LEN] raw_options = raw_packet[DHCP4_HEADER_LEN:] self.dhcp_op = raw_header[0] self.dhcp_hwtype = raw_header[1] self.dhcp_hwlen = raw_header[2] self.dhcp_hops = raw_header[3] self.dhcp_xid = struct.unpack("!L", raw_header[4:8])[0] self.dhcp_secs = struct.unpack("!H", raw_header[8:10])[0] self.dhcp_flag_b = bool( struct.unpack("!H", raw_header[10:12])[0] & 0b1000000000000000) self.dhcp_ciaddr = Ip4Address(raw_header[12:16]) self.dhcp_yiaddr = Ip4Address(raw_header[16:20]) self.dhcp_siaddr = Ip4Address(raw_header[20:24]) self.dhcp_giaddr = Ip4Address(raw_header[24:28]) self.dhcp_chaddr = raw_header[28:28 + self.dhcp_hwlen] self.dhcp_sname = raw_header[44:108] self.dhcp_file = raw_header[108:236] self.dhcp_options: list[Dhcp4OptSubnetMask | Dhcp4OptRouter | Dhcp4OptDns | Dhcp4OptHostName | Dhcp4OptDomainName | Dhcp4OptReqIpAddr | Dhcp4OptAddrLeaseTime | Dhcp4OptParamReqList | Dhcp4OptSrvId | Dhcp4OptMsgType | Dhcp4OptPad | Dhcp4OptEnd] = [] opt_cls = { DHCP4_OPT_SUBNET_MASK: Dhcp4OptSubnetMask, DHCP4_OPT_ROUTER: Dhcp4OptRouter, DHCP4_OPT_DNS: Dhcp4OptDns, DHCP4_OPT_HOST_NAME: Dhcp4OptHostName, DHCP4_OPT_DOMAIN_NAME: Dhcp4OptDomainName, DHCP4_OPT_REQ_IP4_ADDR: Dhcp4OptReqIpAddr, DHCP4_OPT_ADDR_LEASE_TIME: Dhcp4OptAddrLeaseTime, DHCP4_OPT_PARAM_REQ_LIST: Dhcp4OptParamReqList, DHCP4_OPT_SRV_ID: Dhcp4OptSrvId, DHCP4_OPT_MSG_TYPE: Dhcp4OptMsgType, } i = 0 while i < len(raw_options): if raw_options[i] == DHCP4_OPT_END: self.dhcp_options.append(Dhcp4OptEnd()) break if raw_options[i] == DHCP4_OPT_PAD: self.dhcp_options.append(Dhcp4OptPad()) i += DHCP4_OPT_PAD_LEN continue self.dhcp_options.append( opt_cls.get(raw_options[i], Dhcp4OptUnk)( raw_options[i:i + raw_options[i + 1] + 2])) i += self.raw_options[i + 1] + 2 # Packet building else: assert dhcp_op is not None assert dhcp_xid is not None assert dhcp_flag_b is not None assert dhcp_ciaddr is not None assert dhcp_yiaddr is not None assert dhcp_siaddr is not None assert dhcp_giaddr is not None assert dhcp_chaddr is not None self.dhcp_op = dhcp_op self.dhcp_hwtype = 1 self.dhcp_hwlen = 6 self.dhcp_hops = 0 self.dhcp_xid = dhcp_xid self.dhcp_secs = 0 self.dhcp_flag_b = dhcp_flag_b self.dhcp_ciaddr = dhcp_ciaddr self.dhcp_yiaddr = dhcp_yiaddr self.dhcp_siaddr = dhcp_siaddr self.dhcp_giaddr = dhcp_giaddr self.dhcp_chaddr = dhcp_chaddr self.dhcp_sname = b"\0" * 64 self.dhcp_file = b"\0" * 128 self.dhcp_options = [] if dhcp_subnet_mask: self.dhcp_options.append( Dhcp4OptSubnetMask(opt_subnet_mask=dhcp_subnet_mask)) if dhcp_router: self.dhcp_options.append( Dhcp4OptRouter(opt_router=dhcp_router)) if dhcp_dns: self.dhcp_options.append(Dhcp4OptDns(opt_dns=dhcp_dns)) if dhcp_host_name: self.dhcp_options.append( Dhcp4OptHostName(opt_host_name=dhcp_host_name)) if dhcp_domain_name: self.dhcp_options.append( Dhcp4OptDomainName(opt_domain_name=dhcp_domain_name)) if dhcp_req_ip_addr: self.dhcp_options.append( Dhcp4OptReqIpAddr(opt_req_ip_addr=dhcp_req_ip_addr)) if dhcp_addr_lease_time: self.dhcp_options.append( Dhcp4OptAddrLeaseTime( opt_addr_lease_time=dhcp_addr_lease_time)) if dhcp_srv_id: self.dhcp_options.append(Dhcp4OptSrvId(opt_srv_id=dhcp_srv_id)) if dhcp_param_req_list: self.dhcp_options.append( Dhcp4OptParamReqList( opt_param_req_list=dhcp_param_req_list)) if dhcp_msg_type: self.dhcp_options.append( Dhcp4OptMsgType(opt_msg_type=dhcp_msg_type)) self.dhcp_options.append(Dhcp4OptEnd())