def __init__(self, raw_option=None, opt_srv_id=None): if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_srv_id = IPv4Address(raw_option[2:6]) else: self.opt_code = DHCP_OPT_SRV_ID self.opt_len = DHCP_OPT_SRV_ID_LEN self.opt_srv_id = IPv4Address(opt_srv_id)
def __init__(self, raw_option=None, opt_req_ip4_addr=None): if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_req_ip4_addr = IPv4Address(raw_option[2:6]) else: self.opt_code = DHCP_OPT_REQ_IP4_ADDR self.opt_len = DHCP_OPT_REQ_IP4_ADDR_LEN self.opt_req_ip4_addr = IPv4Address(opt_req_ip4_addr)
def __init__(self, raw_option=None, opt_dns=None): if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_dns = [IPv4Address(raw_option[_ : _ + 4]) for _ in range(2, 2 + self.opt_len, 4)] else: self.opt_code = DHCP_OPT_DNS self.opt_len = len(opt_dns) * 4 self.opt_dns = [IPv4Address(_) for _ in opt_dns]
def __init__(self, raw_option=None, opt_subnet_mask=None): if raw_option: self.opt_code = raw_option[0] self.opt_len = raw_option[1] self.opt_subnet_mask = IPv4Address(raw_option[2:6]) else: self.opt_code = DHCP_OPT_SUBNET_MASK self.opt_len = DHCP_OPT_SUBNET_MASK_LEN self.opt_subnet_mask = IPv4Address(opt_subnet_mask)
def __send_dhcp_packet(dhcp_packet_tx): socket.send_to( UdpMetadata( local_ip_address=IPv4Address("0.0.0.0"), local_port=68, remote_ip_address=IPv4Address("255.255.255.255"), remote_port=67, raw_data=dhcp_packet_tx.get_raw_packet(), ))
def raw_header(self): """ Packet header in raw format """ return struct.pack( "!HH BBH 6s 4s 6s 4s", self.arp_hrtype, self.arp_prtype, self.arp_hrlen, self.arp_prlen, self.arp_oper, bytes.fromhex(self.arp_sha.replace(":", "")), IPv4Address(self.arp_spa).packed, bytes.fromhex(self.arp_tha.replace(":", "")), IPv4Address(self.arp_tpa).packed, )
def ip_pick_version(ip_address): """ Return correct IPv6Address or IPv4Address based on address string provided """ try: return IPv6Address(ip_address) except AddressValueError: return IPv4Address(ip_address)
def phrx_arp(self, ether_packet_rx, arp_packet_rx): """ Handle inbound ARP packets """ # Validate ARP packet sanity if arp_packet_rx.sanity_check_failed: return self.logger.opt(ansi=True).info(f"<green>{arp_packet_rx.tracker}</green> - {arp_packet_rx}") if arp_packet_rx.arp_oper == ps_arp.ARP_OP_REQUEST: # Check if request contains our IP address in SPA field, this indicates IP address conflict if arp_packet_rx.arp_spa in self.ip4_unicast: self.logger.warning(f"IP ({arp_packet_rx.arp_spa}) conflict detected with host at {arp_packet_rx.arp_sha}") return # Check if the request is for one of our IP addresses, if so the craft ARP reply packet and send it out if arp_packet_rx.arp_tpa in self.ip4_unicast: self.phtx_arp( ether_src=self.mac_unicast, ether_dst=arp_packet_rx.arp_sha, arp_oper=ps_arp.ARP_OP_REPLY, arp_sha=self.mac_unicast, arp_spa=arp_packet_rx.arp_tpa, arp_tha=arp_packet_rx.arp_sha, arp_tpa=arp_packet_rx.arp_spa, echo_tracker=arp_packet_rx.tracker, ) # Update ARP cache with the maping learned from the received ARP request that was destined to this stack if ARP_CACHE_UPDATE_FROM_DIRECT_REQUEST: self.logger.debug(f"Adding/refreshing ARP cache entry from direct request - {arp_packet_rx.arp_spa} -> {arp_packet_rx.arp_sha}") self.arp_cache.add_entry(arp_packet_rx.arp_spa, arp_packet_rx.arp_sha) return # Handle ARP reply elif arp_packet_rx.arp_oper == ps_arp.ARP_OP_REPLY: # Check for ARP reply that is response to our ARP probe, that indicates that IP address we trying to claim is in use if ether_packet_rx.ether_dst == self.mac_unicast: if ( arp_packet_rx.arp_spa in [_.ip for _ in self.ip4_address_candidate] and arp_packet_rx.arp_tha == self.mac_unicast and arp_packet_rx.arp_tpa == IPv4Address("0.0.0.0") ): self.logger.warning(f"ARP Probe detected conflict for IP {arp_packet_rx.arp_spa} with host at {arp_packet_rx.arp_sha}") self.arp_probe_unicast_conflict.add(arp_packet_rx.arp_spa) return # Update ARP cache with maping received as direct ARP reply if ether_packet_rx.ether_dst == self.mac_unicast: self.logger.debug(f"Adding/refreshing ARP cache entry from direct reply - {arp_packet_rx.arp_spa} -> {arp_packet_rx.arp_sha}") self.arp_cache.add_entry(arp_packet_rx.arp_spa, arp_packet_rx.arp_sha) return # Update ARP cache with maping received as gratuitous ARP reply if ether_packet_rx.ether_dst == "ff:ff:ff:ff:ff:ff" and arp_packet_rx.arp_spa == arp_packet_rx.arp_tpa and ARP_CACHE_UPDATE_FROM_GRATUITOUS_REPLY: self.logger.debug(f"Adding/refreshing ARP cache entry from gratuitous reply - {arp_packet_rx.arp_spa} -> {arp_packet_rx.arp_sha}") self.arp_cache.add_entry(arp_packet_rx.arp_spa, arp_packet_rx.arp_sha) return
def __init__(self, parent_packet=None, arp_sha=None, arp_spa=None, arp_tpa=None, arp_tha="00:00:00:00:00:00", arp_oper=ARP_OP_REQUEST, echo_tracker=None): """ Class constructor """ self.logger = loguru.logger.bind(object_name="ps_arp.") self.sanity_check_failed = False # Packet parsing if parent_packet: self.tracker = parent_packet.tracker raw_packet = parent_packet.raw_data if not self.__pre_parse_sanity_check(raw_packet): self.sanity_check_failed = True return raw_header = raw_packet[:ARP_HEADER_LEN] self.arp_hrtype = struct.unpack("!H", raw_header[0:2])[0] self.arp_prtype = struct.unpack("!H", raw_header[2:4])[0] self.arp_hrlen = raw_header[4] self.arp_prlen = raw_header[5] self.arp_oper = struct.unpack("!H", raw_header[6:8])[0] self.arp_sha = ":".join([f"{_:0>2x}" for _ in raw_header[8:14]]) self.arp_spa = IPv4Address(raw_header[14:18]) self.arp_tha = ":".join([f"{_:0>2x}" for _ in raw_header[18:24]]) self.arp_tpa = IPv4Address(raw_header[24:28]) if not self.__post_parse_sanity_check(): self.sanity_check_failed = True # Packet building else: self.tracker = Tracker("TX", echo_tracker) self.arp_hrtype = 1 self.arp_prtype = 0x0800 self.arp_hrlen = 6 self.arp_prlen = 4 self.arp_oper = arp_oper self.arp_sha = arp_sha self.arp_spa = IPv4Address(arp_spa) self.arp_tha = arp_tha self.arp_tpa = IPv4Address(arp_tpa)
def send_arp_probe(self, ip4_unicast): """ Send out ARP probe to detect possible IP conflict """ self.phtx_arp( ether_src=self.mac_unicast, ether_dst="ff:ff:ff:ff:ff:ff", arp_oper=ps_arp.ARP_OP_REQUEST, arp_sha=self.mac_unicast, arp_spa=IPv4Address("0.0.0.0"), arp_tha="00:00:00:00:00:00", arp_tpa=ip4_unicast, ) self.logger.debug(f"Sent out ARP probe for {ip4_unicast}")
def __send_arp_request(self, arp_tpa): """ Enqueue ARP request packet with TX ring """ self.packet_handler.phtx_arp( ether_src=self.packet_handler.mac_unicast, ether_dst="ff:ff:ff:ff:ff:ff", arp_oper=ps_arp.ARP_OP_REQUEST, arp_sha=self.packet_handler.mac_unicast, arp_spa=self.packet_handler.ip4_unicast[0] if self.packet_handler.ip4_unicast else IPv4Address("0.0.0.0"), arp_tha="00:00:00:00:00:00", arp_tpa=arp_tpa, )
def parse_stack_ip4_address_candidate(self, configured_ip4_address_candidate): """ Parse IPv4 candidate addresses configured in stack.py module """ valid_address_candidate = [] for address, gateway in configured_ip4_address_candidate: self.logger.debug(f"Parsing ('{address}', '{gateway}') entry") try: address = IPv4Interface(address) except AddressValueError: self.logger.warning( f"Invalid host address '{address}' format, skiping...") continue if address.is_multicast or address.is_reserved or address.is_loopback or address.is_unspecified: self.logger.warning( f"Invalid host address '{address.ip}' type, skiping...") continue if address.ip == address.network_address or address.ip == address.broadcast_address: self.logger.warning( f"Invalid host address '{address.ip}' configured for network '{address.network}', skiping..." ) continue if address.ip in [_.ip for _ in valid_address_candidate]: self.logger.warning( f"Duplicate host address '{address.ip}' configured, skiping..." ) continue if gateway is not None: try: gateway = IPv4Address(gateway) if gateway not in address.network or gateway == address.network_address or gateway == address.broadcast_address or gateway == address.ip: self.logger.warning( f"Invalid gateway '{gateway}' configured for interface address '{address}', skiping..." ) gateway = None except AddressValueError: self.logger.warning( f"Invalid gateway '{gateway}' format configured for interface address '{address}' skiping..." ) gateway = None address.gateway = gateway valid_address_candidate.append(address) self.logger.debug( f"Parsed ('{address}', '{address.gateway}') entry") return valid_address_candidate
)) self.logger.debug( f"Sent out DHCP Request message to {dhcp_packet_rx.dhcp_srv_id}") # Wait for DHCP Ack if not (packet := socket.receive_from(timeout=5)): self.logger.warning("Timeout waiting for DHCP Ack message") return None, None dhcp_packet_rx = ps_dhcp.DhcpPacket(packet.raw_data) if dhcp_packet_rx.dhcp_msg_type != ps_dhcp.DHCP_ACK: self.logger.warning("Didn't receive DHCP Offer message") socket.close() return None, None self.logger.debug( 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}" ) socket.close() return ( IPv4Interface( str(dhcp_packet_rx.dhcp_yiaddr) + "/" + str( IPv4Address._make_netmask( str(dhcp_packet_rx.dhcp_subnet_mask))[1])), dhcp_packet_rx.dhcp_router[0], )
def __init__( self, parent_packet=None, ip4_src=None, ip4_dst=None, ip4_ttl=config.ip4_default_ttl, ip4_dscp=0, ip4_ecn=0, ip4_packet_id=0, ip4_flag_df=False, ip4_flag_mf=False, ip4_frag_offset=0, ip4_options=None, child_packet=None, ip4_proto=None, raw_data=b"", tracker=None, ): """ Class constructor """ self.logger = loguru.logger.bind(object_name="ps_ip4.") self.sanity_check_failed = False # Packet parsing if parent_packet: self.tracker = parent_packet.tracker raw_packet = parent_packet.raw_data if not self.__pre_parse_sanity_check(raw_packet): self.sanity_check_failed = True return raw_header = raw_packet[:IP4_HEADER_LEN] raw_options = raw_packet[IP4_HEADER_LEN:(raw_packet[0] & 0b00001111) << 2] self.raw_data = raw_packet[(raw_packet[0] & 0b00001111) << 2:struct .unpack("!H", raw_header[2:4])[0]] self.ip4_ver = raw_header[0] >> 4 self.ip4_hlen = (raw_header[0] & 0b00001111) << 2 self.ip4_dscp = (raw_header[1] & 0b11111100) >> 2 self.ip4_ecn = raw_header[1] & 0b00000011 self.ip4_plen = struct.unpack("!H", raw_header[2:4])[0] self.ip4_packet_id = struct.unpack("!H", raw_header[4:6])[0] self.ip4_flag_reserved = bool( struct.unpack("!H", raw_header[6:8])[0] & 0b1000000000000000) self.ip4_flag_df = bool( struct.unpack("!H", raw_header[6:8])[0] & 0b0100000000000000) self.ip4_flag_mf = bool( struct.unpack("!H", raw_header[6:8])[0] & 0b0010000000000000) self.ip4_frag_offset = (struct.unpack("!H", raw_header[6:8])[0] & 0b0001111111111111) << 3 self.ip4_ttl = raw_header[8] self.ip4_proto = raw_header[9] self.ip4_cksum = struct.unpack("!H", raw_header[10:12])[0] self.ip4_src = IPv4Address(raw_header[12:16]) self.ip4_dst = IPv4Address(raw_header[16:20]) self.ip4_options = [] opt_cls = {} i = 0 while i < len(raw_options): if raw_options[i] == IP4_OPT_EOL: self.ip4_options.append(Ip4OptEol()) break if raw_options[i] == IP4_OPT_NOP: self.ip4_options.append(Ip4OptNop()) i += IP4_OPT_NOP_LEN continue self.ip4_options.append( opt_cls.get(raw_options[i], Ip4OptUnk)( raw_options[i:i + raw_options[i + 1]])) i += self.raw_options[i + 1] if not self.__post_parse_sanity_check(): self.sanity_check_failed = True # Packet building else: if tracker: self.tracker = tracker else: self.tracker = child_packet.tracker self.ip4_ver = 4 self.ip4_hlen = None self.ip4_dscp = ip4_dscp self.ip4_ecn = ip4_ecn self.ip4_plen = None self.ip4_packet_id = ip4_packet_id self.ip4_flag_reserved = 0 self.ip4_flag_df = ip4_flag_df self.ip4_flag_mf = ip4_flag_mf self.ip4_frag_offset = ip4_frag_offset self.ip4_ttl = ip4_ttl self.ip4_cksum = 0 self.ip4_src = IPv4Address(ip4_src) self.ip4_dst = IPv4Address(ip4_dst) self.ip4_options = [] if ip4_options is None else ip4_options self.ip4_hlen = IP4_HEADER_LEN + len(self.raw_options) assert self.ip4_hlen % 4 == 0, "IP header len is not multiplcation of 4 bytes, check options" if child_packet: assert child_packet.protocol in { "ICMPv4", "UDP", "TCP" }, f"Not supported protocol: {child_packet.protocol}" if child_packet.protocol == "ICMPv4": self.ip4_proto = IP4_PROTO_ICMP4 self.raw_data = child_packet.get_raw_packet() self.ip4_plen = self.ip4_hlen + len(self.raw_data) if child_packet.protocol == "UDP": self.ip4_proto = IP4_PROTO_UDP self.ip4_plen = self.ip4_hlen + child_packet.udp_plen self.raw_data = child_packet.get_raw_packet( self.ip_pseudo_header) if child_packet.protocol == "TCP": self.ip4_proto = IP4_PROTO_TCP self.ip4_plen = self.ip4_hlen + child_packet.tcp_hlen + len( child_packet.raw_data) self.raw_data = child_packet.get_raw_packet( self.ip_pseudo_header) else: self.ip4_proto = ip4_proto self.raw_data = raw_data self.ip4_plen = self.ip4_hlen + len(self.raw_data)
def phrx_udp(self, ip_packet_rx, udp_packet_rx): """ Handle inbound UDP packets """ # Validate UDP packet sanity if udp_packet_rx.sanity_check_failed: return self.logger.opt(ansi=True).info(f"<green>{udp_packet_rx.tracker}</green> - {udp_packet_rx}") # Set universal names for src and dst IP addresses whether packet was delivered by IPv6 or IPv4 protocol ip_packet_rx.ip_dst = ip_packet_rx.ip6_dst if ip_packet_rx.protocol == "IPv6" else ip_packet_rx.ip4_dst ip_packet_rx.ip_src = ip_packet_rx.ip6_src if ip_packet_rx.protocol == "IPv6" else ip_packet_rx.ip4_src # Create UdpMetadata object and try to find matching UDP socket packet = UdpMetadata( local_ip_address=ip_packet_rx.ip_dst, local_port=udp_packet_rx.udp_dport, remote_ip_address=ip_packet_rx.ip_src, remote_port=udp_packet_rx.udp_sport, raw_data=udp_packet_rx.raw_data, tracker=udp_packet_rx.tracker, ) for socket_id in packet.socket_id_patterns: socket = stack.udp_sockets.get(socket_id, None) if socket: loguru.logger.bind(object_name="socket.").debug(f"{packet.tracker} - Found matching listening socket {socket_id}") socket.process_packet(packet) return # Silently drop packet if it has all zero source IP address if ip_packet_rx.ip_src in {IPv4Address("0.0.0.0"), IPv6Address("::")}: self.logger.debug( f"Received UDP packet from {ip_packet_rx.ip_src}, port {udp_packet_rx.udp_sport} to {ip_packet_rx.ip_dst}, port {udp_packet_rx.udp_dport}, droping" ) return # Respond with ICMPv4 Port Unreachable message if no matching socket has been found self.logger.debug(f"Received UDP packet from {ip_packet_rx.ip_src} to closed port {udp_packet_rx.udp_dport}, sending ICMPv4 Port Unreachable") if ip_packet_rx.protocol == "IPv6": self.phtx_icmp6( ip6_src=ip_packet_rx.ip6_dst, ip6_dst=ip_packet_rx.ip6_src, icmp6_type=ps_icmp6.ICMP6_UNREACHABLE, icmp6_code=ps_icmp6.ICMP6_UNREACHABLE__PORT, icmp6_un_raw_data=ip_packet_rx.get_raw_packet(), echo_tracker=udp_packet_rx.tracker, ) if ip_packet_rx.protocol == "IPv4": self.phtx_icmp4( ip4_src=ip_packet_rx.ip_dst, ip4_dst=ip_packet_rx.ip_src, icmp4_type=ps_icmp4.ICMP4_UNREACHABLE, icmp4_code=ps_icmp4.ICMP4_UNREACHABLE__PORT, icmp4_un_raw_data=ip_packet_rx.get_raw_packet(), echo_tracker=udp_packet_rx.tracker, ) return
def __init__( self, raw_packet=None, dhcp_op=BOOT_REQUEST, dhcp_xid=None, dhcp_flag_b=False, dhcp_ciaddr=IPv4Address("0.0.0.0"), dhcp_yiaddr=IPv4Address("0.0.0.0"), dhcp_siaddr=IPv4Address("0.0.0.0"), dhcp_giaddr=IPv4Address("0.0.0.0"), dhcp_chaddr=None, dhcp_subnet_mask=None, dhcp_router=None, dhcp_dns=None, dhcp_host_name=None, dhcp_domain_name=None, dhcp_req_ip4_addr=None, dhcp_addr_lease_time=None, dhcp_srv_id=None, dhcp_param_req_list=None, dhcp_msg_type=None, ): """ Class constructor """ # Packet parsing if raw_packet: raw_header = raw_packet[:DHCP_HEADER_LEN] raw_options = raw_packet[DHCP_HEADER_LEN:] self.dhcp_op = raw_header[0] self.dhcp_htype = raw_header[1] self.dhcp_hlen = 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 = IPv4Address(raw_header[12:16]) self.dhcp_yiaddr = IPv4Address(raw_header[16:20]) self.dhcp_siaddr = IPv4Address(raw_header[20:24]) self.dhcp_giaddr = IPv4Address(raw_header[24:28]) self.dhcp_chaddr = raw_header[28 : 28 + self.dhcp_hlen] self.dhcp_sname = raw_header[44:108] self.dhcp_file = raw_header[108:236] self.dhcp_options = [] opt_cls = { DHCP_OPT_SUBNET_MASK: DhcpOptSubnetMask, DHCP_OPT_ROUTER: DhcpOptRouter, DHCP_OPT_DNS: DhcpOptDns, DHCP_OPT_HOST_NAME: DhcpOptHostName, DHCP_OPT_DOMAIN_NAME: DhcpOptDomainName, DHCP_OPT_REQ_IP4_ADDR: DhcpOptReqIpAddr, DHCP_OPT_ADDR_LEASE_TIME: DhcpOptAddrLeaseTime, DHCP_OPT_PARAM_REQ_LIST: DhcpOptParamReqList, DHCP_OPT_SRV_ID: DhcpOptSrvId, DHCP_OPT_MSG_TYPE: DhcpOptMsgType, } i = 0 while i < len(raw_options): if raw_options[i] == DHCP_OPT_END: self.dhcp_options.append(DhcpOptEnd()) break if raw_options[i] == DHCP_OPT_PAD: self.dhcp_options.append(DhcpOptPad()) i += DHCP_OPT_PAD_LEN continue self.dhcp_options.append(opt_cls.get(raw_options[i], DhcpOptUnk)(raw_options[i : i + raw_options[i + 1] + 2])) i += self.raw_options[i + 1] + 2 # Packet building else: self.dhcp_op = dhcp_op self.dhcp_htype = 1 self.dhcp_hlen = 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(DhcpOptSubnetMask(opt_subnet_mask=dhcp_subnet_mask)) if dhcp_router: self.dhcp_options.append(DhcpOptRouter(opt_router=dhcp_router)) if dhcp_dns: self.dhcp_options.append(DhcpOptDns(opt_dns=dhcp_dns)) if dhcp_host_name: self.dhcp_options.append(DhcpOptHostName(opt_host_name=dhcp_host_name)) if dhcp_domain_name: self.dhcp_options.append(DhcpOptDomainName(opt_domain_name=dhcp_domain_name)) if dhcp_req_ip4_addr: self.dhcp_options.append(DhcpOptReqIpAddr(opt_req_ip4_addr=dhcp_req_ip4_addr)) if dhcp_addr_lease_time: self.dhcp_options.append(DhcpOptAddrLeaseTime(opt_addr_lease_time=dhcp_addr_lease_time)) if dhcp_srv_id: self.dhcp_options.append(DhcpOptSrvId(opt_srv_id=dhcp_srv_id)) if dhcp_param_req_list: self.dhcp_options.append(DhcpOptParamReqList(opt_param_req_list=dhcp_param_req_list)) if dhcp_msg_type: self.dhcp_options.append(DhcpOptMsgType(opt_msg_type=dhcp_msg_type)) self.dhcp_options.append(DhcpOptEnd())
def validate_src_ip4_address(self, ip4_src, ip4_dst): """ 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 comunication) if ip4_src not in { *self.ip4_unicast, *self.ip4_multicast, *self.ip4_broadcast, IPv4Address("0.0.0.0") }: self.logger.warning( f"Unable to sent out IPv4 packet, stack doesn't own IPv4 address {ip4_src}" ) 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] self.logger.debug( f"Packet is response to multicast, replaced source with stack primary IPv4 address {ip4_src}" ) else: self.logger.warning( "Unable to sent out IPv4 packet, no stack primary unicast IPv4 address available" ) 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] self.logger.debug( f"Packet is response to limited broadcast, replaced source with stack primary IPv4 address {ip4_src}" ) else: self.logger.warning( "Unable to sent out IPv4 packet, no stack primary unicast IPv4 address available" ) 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 = [ _.ip for _ in self.ip4_address if _.broadcast_address == ip4_src ] if ip4_src: ip4_src = ip4_src[0] self.logger.debug( f"Packet is response to directed broadcast, replaced source with apropriate IPv4 address {ip4_src}" ) else: self.logger.warning( "Unable to sent out IPv4 packet, no appropriate stack unicast IPv4 address available" ) return None # If source is unspecified check if destination belongs to any of local networks, if so pick source address from that network if ip4_src.is_unspecified: for ip4_address in self.ip4_address: if ip4_dst in ip4_address.network: return ip4_address.ip # If source unspcified and destination is external pick source from first network that has default gateway set if ip4_src.is_unspecified: for ip4_address in self.ip4_address: if ip4_address.gateway: return ip4_address.ip return ip4_src
def phtx_ip4(self, child_packet, ip4_dst, ip4_src, ip4_ttl=config.ip4_default_ttl): """ Handle outbound IP packets """ # Check if IPv4 protocol support is enabled, if not then silently drop the packet if not config.ip4_support: return # Make sure source and destination addresses are the right object type ip4_src = IPv4Address(ip4_src) ip4_dst = IPv4Address(ip4_dst) # Validate source address ip4_src = validate_src_ip4_address(self, ip4_src, ip4_dst) if not ip4_src: return # Validate destination address ip4_dst = validate_dst_ip4_address(self, ip4_dst) if not ip4_dst: return # Generate new IPv4 ID self.ip4_packet_id += 1 if self.ip4_packet_id > 65535: self.ip4_packet_id = 1 # Check if packet can be sent out without fragmentation, if so send it out if ps_ip4.IP4_HEADER_LEN + len(child_packet.raw_packet) <= config.mtu: ip4_packet_tx = ps_ip4.Ip4Packet(ip4_src=ip4_src, ip4_dst=ip4_dst, ip4_packet_id=self.ip4_packet_id, child_packet=child_packet) self.logger.debug(f"{ip4_packet_tx.tracker} - {ip4_packet_tx}") self.phtx_ether(child_packet=ip4_packet_tx) return # Fragment packet and send all fragments out self.logger.debug( "Packet exceedes available MTU, IP fragmentation needed...") if child_packet.protocol == "ICMPv4": ip4_proto = ps_ip4.IP4_PROTO_ICMP4 raw_data = child_packet.get_raw_packet() if child_packet.protocol in {"UDP", "TCP"}: ip4_proto = ps_ip4.IP4_PROTO_UDP if child_packet.protocol == "UDP" else ps_ip4.IP4_PROTO_TCP raw_data = child_packet.get_raw_packet( struct.pack( "! 4s 4s BBH", ip4_src.packed, ip4_dst.packed, 0, ip4_proto, len(child_packet.raw_packet), )) raw_data_mtu = (config.mtu - ps_ether.ETHER_HEADER_LEN - ps_ip4.IP4_HEADER_LEN) & 0b1111111111111000 raw_data_fragments = [ raw_data[_:raw_data_mtu + _] for _ in range(0, len(raw_data), raw_data_mtu) ] pointer = 0 offset = 0 for raw_data_fragment in raw_data_fragments: ip4_packet_tx = ps_ip4.Ip4Packet( ip4_src=ip4_src, ip4_dst=ip4_dst, ip4_proto=ip4_proto, ip4_packet_id=self.ip4_packet_id, ip4_flag_mf=pointer < len(raw_data_fragments) - 1, ip4_frag_offset=offset, ip4_ttl=ip4_ttl, raw_data=raw_data_fragment, tracker=child_packet.tracker, ) pointer += 1 offset += len(raw_data_fragment) self.logger.debug(f"{ip4_packet_tx.tracker} - {ip4_packet_tx}") self.phtx_ether(child_packet=ip4_packet_tx) return