def _send_icmp6_multicast_listener_report(self) -> None: """Send out ICMPv6 Multicast Listener Report for given list of addresses""" # Need to use set here to avoid re-using duplicate multicast entries from stack_ip6_multicast list, # also All Multicast Nodes address is not being advertised as this is not necessary if icmp6_mlr2_multicast_address_record := { Icmp6MulticastAddressRecord( record_type=ICMP6_MART_CHANGE_TO_EXCLUDE, multicast_address=_) for _ in self.ip6_multicast if _ not in {Ip6Address("ff02::1")} }: self._phtx_icmp6( ip6_src=self.ip6_unicast[0] if self.ip6_unicast else Ip6Address(0), ip6_dst=Ip6Address("ff02::16"), ip6_hop=1, icmp6_type=ICMP6_MLD2_REPORT, icmp6_mlr2_multicast_address_record= icmp6_mlr2_multicast_address_record, ) if __debug__: log( "stack", "Sent out ICMPv6 Multicast Listener Report message for " + f"{[_.multicast_address for _ in icmp6_mlr2_multicast_address_record]}" )
def __init__(self, raw_record: bytes) -> None: """Class constructor""" self.raw_record = raw_record self.record_type = self.raw_record[0] self.aux_data_len = self.raw_record[1] self.number_of_sources = struct.unpack("!H", self.raw_record[2:4])[0] self.multicast_address = Ip6Address(self.raw_record[4:20]) self.source_address = [ Ip6Address(self.raw_record[20 + 16 * _:20 + 16 * (_ + 1)]) for _ in range(self.number_of_sources) ] self.aux_data = self.raw_record[20 + 16 * self.number_of_sources:]
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 na_target_address(self) -> Ip6Address: """Read ND NA 'Taret address' field""" if "_cache__na_target_address" not in self.__dict__: assert self.type == ICMP6_NEIGHBOR_ADVERTISEMENT self._cache__na_target_address = Ip6Address(self._frame[8:24]) return self._cache__na_target_address
def ns_target_address(self) -> Ip6Address: """Read ND NS 'Target adress' field""" if "_cache__ns_target_address" not in self.__dict__: assert self.type == ICMP6_NEIGHBOR_SOLICITATION self._cache__ns_target_address = Ip6Address(self._frame[8:24]) return self._cache__ns_target_address
def _validate_src_ip6_address(self, ip6_src: Ip6Address, ip6_dst: Ip6Address, tracker: Tracker) -> Optional[Ip6Address]: """Make sure source ip address is valid, supplement with valid one as appropriate""" # Check if the the source IP address belongs to this stack or its unspecified if ip6_src not in {*self.ip6_unicast, *self.ip6_multicast, Ip6Address(0)}: if __debug__: log( "ip6", f"{tracker} - <WARN>Unable to sent out IPv6 packet, stack doesn't own IPv6 address {ip6_src}, dropping</>" ) return None # If packet is a response to multicast then replace source address with link local address of the stack if ip6_src in self.ip6_multicast: if self.ip6_unicast: ip6_src = self.ip6_unicast[0] if __debug__: log( "ip6", f"{tracker} - Packet is response to multicast, replaced source with stack link local IPv6 address {ip6_src}" ) else: if __debug__: log( "ip6", f"{tracker} - <WARN>Unable to sent out IPv6 packet, no stack link local unicast IPv6 address available</>" ) return None # If source is unspecified try to find best match for given destination if ip6_src.is_unspecified: return pick_local_ip6_address(ip6_dst) return ip6_src
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 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 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 __init__(self, frame: bytes) -> None: self.code = frame[0] self.len = frame[1] << 3 self.flag_l = bool(frame[3] & 0b10000000) self.flag_a = bool(frame[3] & 0b01000000) self.flag_r = bool(frame[3] & 0b00100000) self.valid_lifetime = struct.unpack_from("!L", frame, 4)[0] self.preferr_lifetime = struct.unpack_from("!L", frame, 8)[0] self.prefix = Ip6Network( (Ip6Address(frame[16:32]), Ip6Mask(f"/{frame[2]}")))
def _send_icmp6_nd_router_solicitation(self) -> None: """Send out ICMPv6 ND Router Solicitation""" self._phtx_icmp6( ip6_src=self.ip6_unicast[0], ip6_dst=Ip6Address("ff02::2"), ip6_hop=255, icmp6_type=ICMP6_ROUTER_SOLICITATION, icmp6_nd_options=[Icmp6NdOptSLLA(self.mac_unicast)], ) if __debug__: log("stack", "Sent out ICMPv6 ND Router Solicitation")
def pick_local_ip6_address(remote_ip6_address: Ip6Address) -> Ip6Address: """Pick appropriate source IPv6 address based on provided destination IPv6 address""" # If destination belongs to any of local networks, if so pick source address from that network for ip6_host in stack.packet_handler.ip6_host: if remote_ip6_address in ip6_host.network: return ip6_host.address # If destination is external pick source from first network that has default gateway set for ip6_host in stack.packet_handler.ip6_host: if ip6_host.gateway: return ip6_host.address # In case everything else fails return unspecified return Ip6Address(0)
def _send_icmp6_nd_dad_message(self, ip6_unicast_candidate: Ip6Address) -> None: """Send out ICMPv6 ND Duplicate Address Detection message""" self._phtx_icmp6( ip6_src=Ip6Address(0), ip6_dst=ip6_unicast_candidate.solicited_node_multicast, ip6_hop=255, icmp6_type=ICMP6_NEIGHBOR_SOLICITATION, icmp6_ns_target_address=ip6_unicast_candidate, ) if __debug__: log("stack", f"Sent out ICMPv6 ND DAD message for {ip6_unicast_candidate}")
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 _send_icmp6_neighbor_solicitation( self, icmp6_ns_target_address: Ip6Address) -> None: """Enqueue ICMPv6 Neighbor Solicitation packet with TX ring""" # Pick appropriate source address ip6_src = Ip6Address(0) for ip6_host in stack.packet_handler.ip6_host: if icmp6_ns_target_address in ip6_host.network: ip6_src = ip6_host.address # Send out ND Solicitation message stack.packet_handler._phtx_icmp6( ip6_src=ip6_src, ip6_dst=icmp6_ns_target_address.solicited_node_multicast, ip6_hop=255, icmp6_type=ICMP6_NEIGHBOR_SOLICITATION, icmp6_ns_target_address=icmp6_ns_target_address, icmp6_nd_options=[ Icmp6NdOptSLLA(stack.packet_handler.mac_unicast) ], )
def src(self) -> Ip6Address: """Read 'Source address' field""" if "_cache__src" not in self.__dict__: self._cache__src = Ip6Address(self._frame[8:24]) return self._cache__src
def _parse_stack_ip6_host_candidate( self, configured_host_candidate: list[tuple[str, Optional[str]]] ) -> list[Ip6Host]: """Parse IPv6 candidate address list""" valid_host_candidate: list[Ip6Host] = [] for str_host, str_gateway in configured_host_candidate: if __debug__: log("stack", f"Parsing ('{str_host}', '{str_gateway}') entry") try: host = Ip6Host(str_host) except Ip6HostFormatError: 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 and not host.address.is_link_local: if __debug__: log( "stack", f"<WARN>Invalid host address '{host.address}' type, skipping</>" ) continue if host.address in [_.address for _ in valid_host_candidate]: if __debug__: log( "stack", f"<WARN>Duplicate host address '{host.address}' configured, skipping</>" ) continue if host.address.is_link_local and str_gateway: if __debug__: log( "stack", "<WARN>Gateway cannot be configured for link local address skipping</>" ) continue if str_gateway is not None: try: gateway: Optional[Ip6Address] = Ip6Address(str_gateway) assert gateway is not None if not (gateway.is_link_local or (gateway in host.network and gateway != host.address)): if __debug__: log( "stack", f"<WARN>Invalid gateway '{gateway}' configured for host address '{host}', skipping</>" ) continue except Ip6AddressFormatError: 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_host_candidate.append(host) if __debug__: log("stack", f"Parsed ('{host}', '{host.gateway}') entry") return valid_host_candidate
def __init__(self, tap: Optional[int]) -> None: """Class constructor""" stack.packet_handler = self # MAC and IPv6 Multicast lists hold duplicate entries by design. This is to accommodate IPv6 Solicited Node Multicast mechanism where multiple # IPv6 unicast addresses can be tied to the same SNM address (and the same multicast MAC). This is important when removing one of unicast addresses, # so the other ones keep it's SNM entry in multicast list. Its the simplest solution and imho perfectly valid one in this case. self.mac_unicast: MacAddress = MacAddress(config.MAC_ADDRESS) self.mac_multicast: list[MacAddress] = [] self.mac_broadcast: MacAddress = MacAddress(0xFFFFFFFFFFFF) self.ip6_host: list[Ip6Host] = [] self.ip6_multicast: list[Ip6Address] = [] self.ip4_host: list[Ip4Host] = [] self.ip4_multicast: list[Ip4Address] = [] # Used for the ARP DAD process self.arp_probe_unicast_conflict: set[Ip4Address] = set() # Used for the ICMPv6 ND DAD process self.ip6_unicast_candidate: Optional[Ip6Address] = None self.event_icmp6_nd_dad: Semaphore = threading.Semaphore(0) self.icmp6_nd_dad_tlla: Optional[Ip6Address] = None # Used for the IcMPv6 ND RA address auto configuration self.icmp6_ra_prefixes: list[tuple[Ip6Network, Ip6Address]] = [] self.event_icmp6_ra: Semaphore = threading.Semaphore(0) # Used to keep IPv4 and IPv6 packet ID last value self.ip4_id: int = 0 self.ip6_id: int = 0 # Used to defragment IPv4 and IPv6 packets self.ip4_frag_flows: dict[int, bytes] = {} self.ip6_frag_flows: dict[int, bytes] = {} # Skip rest of the initialisations for the unit test / mock run if tap is None: return # Start subsystems self.rx_ring: RxRing = RxRing(tap) self.tx_ring: TxRing = TxRing(tap) self.arp_cache: ArpCache = ArpCache() self.nd_cache: NdCache = NdCache() # Start packet handler so we can receive packets from network threading.Thread(target=self.__thread_packet_handler).start() if __debug__: log("stack", "Started packet handler") if config.IP6_SUPPORT: # Assign All IPv6 Nodes multicast address self._assign_ip6_multicast(Ip6Address("ff02::1")) # Create list of IPv6 unicast/multicast addresses stack should listen on self.ip6_host_candidate = self._parse_stack_ip6_host_candidate( config.IP6_HOST_CANDIDATE) self._create_stack_ip6_addressing() if config.IP4_SUPPORT: # Create list of IPv4 unicast/multicast/broadcast addresses stack should listen on, use DHCP if enabled if config.IP4_HOST_DHCP: dhcp4_client = Dhcp4Client(self.mac_unicast) ip4_host_dhcp = dhcp4_client.fetch() else: ip4_host_dhcp = (None, None) self.ip4_host_candidate = self._parse_stack_ip4_host_candidate( config.IP4_HOST_CANDIDATE + ([ip4_host_dhcp] if ip4_host_dhcp[0] is not None else [])) self._create_stack_ip4_addressing() # Log all the addresses stack will listen on if __debug__: log( "stack", f"<INFO>Stack listening on unicast MAC address: {self.mac_unicast}</>" ) log( "stack", f"<INFO>Stack listening on multicast MAC addresses: {', '.join([str(_) for _ in set(self.mac_multicast)])}</>" ) log( "stack", f"<INFO>Stack listening on broadcast MAC address: {self.mac_broadcast}</>" ) if config.IP6_SUPPORT: log( "stack", f"<INFO>Stack listening on unicast IPv6 addresses: {', '.join([str(_) for _ in self.ip6_unicast])}</>" ) log( "stack", f"<INFO>Stack listening on multicast IPv6 addresses: {', '.join([str(_) for _ in set(self.ip6_multicast)])})</>" ) if config.IP4_SUPPORT: log( "stack", f"<INFO>Stack listening on unicast IPv4 addresses: {', '.join([str(_) for _ in self.ip4_unicast])}</>" ) log( "stack", f"<INFO>Stack listening on multicast IPv4 addresses: {', '.join([str(_) for _ in self.ip4_multicast])}</>" ) log( "stack", f"<INFO>Stack listening on broadcast IPv4 addresses: {', '.join([str(_) for _ in self.ip4_broadcast])}</>" )
def _phrx_icmp6(self, packet_rx: PacketRx) -> None: """Handle inbound ICMPv6 packets""" Icmp6Parser(packet_rx) if packet_rx.parse_failed: if __debug__: log("icmp6", f"{packet_rx.tracker} - <CRIT>{packet_rx.parse_failed}</>") return if __debug__: log("icmp6", f"{packet_rx.tracker} - {packet_rx.icmp6}") # ICMPv6 Neighbor Solicitation packet if packet_rx.icmp6.type == ICMP6_NEIGHBOR_SOLICITATION: # Check if request is for one of stack's IPv6 unicast addresses if packet_rx.icmp6.ns_target_address not in self.ip6_unicast: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Received ICMPv6 Neighbor Solicitation packet from {packet_rx.ip6.src}, " + "not matching any of stack's IPv6 unicast addresses, dropping", ) return if __debug__: log( "icmp6", f"{packet_rx.tracker} - <INFO>Received ICMPv6 Neighbor Solicitation packet from {packet_rx.ip6.src}, sending reply</>" ) # Update ICMPv6 ND cache if not (packet_rx.ip6.src.is_unspecified or packet_rx.ip6.src. is_multicast) and packet_rx.icmp6.nd_opt_slla: self.nd_cache.add_entry(packet_rx.ip6.src, packet_rx.icmp6.nd_opt_slla) # Determine if request is part of DAD request by examining its source address ip6_nd_dad = packet_rx.ip6.src.is_unspecified # Send response self._phtx_icmp6( ip6_src=packet_rx.icmp6.ns_target_address, ip6_dst=Ip6Address("ff02::1") if ip6_nd_dad else packet_rx.ip6. src, # use ff02::1 destination addriess when responding to DAD equest ip6_hop=255, icmp6_type=ICMP6_NEIGHBOR_ADVERTISEMENT, icmp6_na_flag_s= not ip6_nd_dad, # no S flag when responding to DAD request icmp6_na_flag_o= ip6_nd_dad, # O flag when respondidng to DAD request (this is not necessary but Linux uses it) icmp6_na_target_address=packet_rx.icmp6.ns_target_address, icmp6_nd_options=[Icmp6NdOptTLLA(self.mac_unicast)], echo_tracker=packet_rx.tracker, ) return # ICMPv6 Neighbor Advertisement packet if packet_rx.icmp6.type == ICMP6_NEIGHBOR_ADVERTISEMENT: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Received ICMPv6 Neighbor Advertisement packet for {packet_rx.icmp6.na_target_address} from {packet_rx.ip6.src}" ) # Run ND Duplicate Address Detection check if packet_rx.icmp6.na_target_address == self.ip6_unicast_candidate: self.icmp6_nd_dad_tlla = packet_rx.icmp6.nd_opt_tlla self.event_icmp6_nd_dad.release() return # Update ICMPv6 ND cache if packet_rx.icmp6.nd_opt_tlla: self.nd_cache.add_entry(packet_rx.icmp6.na_target_address, packet_rx.icmp6.nd_opt_tlla) return return # ICMPv6 Router Solicitaion packet (this is not currently used by the stack) if packet_rx.icmp6.type == ICMP6_ROUTER_SOLICITATION: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Received ICMPv6 Router Solicitation packet from {packet_rx.ip6.src}" ) return # ICMPv6 Router Advertisement packet if packet_rx.icmp6.type == ICMP6_ROUTER_ADVERTISEMENT: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Received ICMPv6 Router Advertisement packet from {packet_rx.ip6.src}" ) # Make note of prefixes that can be used for address autoconfiguration self.icmp6_ra_prefixes = [(_, packet_rx.ip6.src) for _ in packet_rx.icmp6.nd_opt_pi] self.event_icmp6_ra.release() return # ICMPv6 Echo Request packet if packet_rx.icmp6.type == ICMP6_ECHO_REQUEST: if __debug__: if __debug__: log( "icmp6", f"{packet_rx.tracker} - <INFO>Received ICMPv6 Echo Request packet from {packet_rx.ip6.src}, sending reply</>" ) self._phtx_icmp6( ip6_src=packet_rx.ip6.dst, ip6_dst=packet_rx.ip6.src, ip6_hop=255, icmp6_type=ICMP6_ECHO_REPLY, icmp6_ec_id=packet_rx.icmp6.ec_id, icmp6_ec_seq=packet_rx.icmp6.ec_seq, icmp6_ec_data=packet_rx.icmp6.ec_data, echo_tracker=packet_rx.tracker, ) return # ICMPv6 Unreachable packet if packet_rx.icmp6.type == ICMP6_UNREACHABLE: if __debug__: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Received ICMPv6 Unreachable packet from {packet_rx.ip6.src}, will try to match UDP socket" ) # Quick and dirty way to validate received data and pull useful information from it # TODO - This will not work in case of IPv6 extension headers present frame = packet_rx.icmp6.un_data if len(frame) >= IP6_HEADER_LEN + UDP_HEADER_LEN and frame[ 0] >> 4 == 6 and frame[6] == IP6_NEXT_HEADER_UDP: # Create UdpMetadata object and try to find matching UDP socket udp_offset = IP6_HEADER_LEN packet = UdpMetadata( local_ip_address=Ip6Address(frame[8:24]), remote_ip_address=Ip6Address(frame[24:40]), 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__: if __debug__: log( "icmp6", f"{packet_rx.tracker} - <INFO>Found matching listening socket {socket} for Unreachable packet from {packet_rx.ip6.src}</>", ) socket.notify_unreachable() return if __debug__: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Unreachable data doesn't match any UDP socket" ) return if __debug__: if __debug__: log( "icmp6", f"{packet_rx.tracker} - Unreachable data doesn't pass basic IPv4/UDP integrity check" ) return
def dst(self) -> Ip6Address: """Read 'Destination address' field""" if "_cache__dst" not in self.__dict__: self._cache__dst = Ip6Address(self._frame[24:40]) return self._cache__dst
def __init__( self, type: int, code: int = 0, un_data: Optional[bytes] = None, ec_id: Optional[int] = None, ec_seq: Optional[int] = None, ec_data: Optional[bytes] = None, ra_hop: Optional[int] = None, ra_flag_m: Optional[bool] = None, ra_flag_o: Optional[bool] = None, ra_router_lifetime: Optional[int] = None, ra_reachable_time: Optional[int] = None, ra_retrans_timer: Optional[int] = None, ns_target_address: Optional[Ip6Address] = None, na_flag_r: Optional[bool] = None, na_flag_s: Optional[bool] = None, na_flag_o: Optional[bool] = None, na_target_address: Optional[Ip6Address] = None, nd_options: Optional[list[Icmp6NdOptSLLA | Icmp6NdOptTLLA | Icmp6NdOptPI]] = None, mlr2_multicast_address_record: Optional[ list[Icmp6MulticastAddressRecord]] = None, echo_tracker: Optional[Tracker] = None, ) -> None: """Class constructor""" self._tracker = Tracker("TX", echo_tracker) self._type = type self._code = code self._nd_options: list[ Icmp6NdOptSLLA | Icmp6NdOptTLLA | Icmp6NdOptPI] = [] if nd_options is None else nd_options self._un_reserved: int self._un_data: bytes self._ec_id: int self._ec_seq: int self._ec_data: bytes self._rs_reserved: int self._ra_hop: int self._ra_flag_m: bool self._ra_flag_o: bool self._ra_router_lifetime: int self._ra_reachable_time: int self._ra_retrans_timer: int self._ns_reserved: int self._ns_target_address: Ip6Address self._na_flag_r: bool self._na_flag_s: bool self._na_flag_o: bool self._na_reserved: int self._na_target_address: Ip6Address self._mlr2_reserved: int self._mlr2_multicast_address_record: list[Icmp6MulticastAddressRecord] self._mlr2_number_of_multicast_address_records: int if self._type == ICMP6_UNREACHABLE: self._un_reserved = 0 self._un_data = b"" if un_data is None else un_data[:520] elif self._type == ICMP6_ECHO_REQUEST: self._ec_id = 0 if ec_id is None else ec_id self._ec_seq = 0 if ec_seq is None else ec_seq self._ec_data = b"" if ec_data is None else ec_data elif self._type == ICMP6_ECHO_REPLY: self._ec_id = 0 if ec_id is None else ec_id self._ec_seq = 0 if ec_seq is None else ec_seq self._ec_data = b"" if ec_data is None else ec_data elif self._type == ICMP6_ROUTER_SOLICITATION: self._rs_reserved = 0 elif self._type == ICMP6_ROUTER_ADVERTISEMENT: self._ra_hop = 0 if ra_hop is None else ra_hop self._ra_flag_m = False if ra_flag_m is None else ra_flag_m self._ra_flag_o = False if ra_flag_o is None else ra_flag_o self._ra_router_lifetime = 0 if ra_router_lifetime is None else ra_router_lifetime self._ra_reachable_time = 0 if ra_reachable_time is None else ra_reachable_time self._ra_retrans_timer = 0 if ra_retrans_timer is None else ra_retrans_timer elif self._type == ICMP6_NEIGHBOR_SOLICITATION: self._ns_reserved = 0 self._ns_target_address = Ip6Address( 0) if ns_target_address is None else ns_target_address elif self._type == ICMP6_NEIGHBOR_ADVERTISEMENT: self._na_flag_r = False if na_flag_r is None else na_flag_r self._na_flag_s = False if na_flag_s is None else na_flag_s self._na_flag_o = False if na_flag_o is None else na_flag_o self._na_reserved = 0 self._na_target_address = Ip6Address( 0) if na_target_address is None else na_target_address elif self._type == ICMP6_MLD2_REPORT: self._mlr2_reserved = 0 self._mlr2_multicast_address_record = [] if mlr2_multicast_address_record is None else mlr2_multicast_address_record self._mlr2_number_of_multicast_address_records = len( self._mlr2_multicast_address_record)
def _packet_sanity_check(self, ip6_src: Ip6Address, ip6_dst: Ip6Address, ip6_hop: int) -> str: """Packet sanity check to be run on parsed packet to make sure frame's fields contain sane values""" if not config.PACKET_SANITY_CHECK: return "" if self.type == ICMP6_UNREACHABLE: if self.code not in {0, 1, 2, 3, 4, 5, 6}: return "ICMPv6 sanity - 'code' must be [0-6] (RFC 4861)" elif self.type == ICMP6_PACKET_TOO_BIG: if not self.code == 0: return "ICMPv6 sanity - 'code' should be 0 (RFC 4861)" elif self.type == ICMP6_TIME_EXCEEDED: if self.code not in {0, 1}: return "ICMPv6 sanity - 'code' must be [0-1] (RFC 4861)" elif self.type == ICMP6_PARAMETER_PROBLEM: if self.code not in {0, 1, 2}: return "ICMPv6 sanity - 'code' must be [0-2] (RFC 4861)" elif self.type in {ICMP6_ECHO_REQUEST, ICMP6_ECHO_REPLY}: if not self.code == 0: return "ICMPv6 sanity - 'code' should be 0 (RFC 4861)" elif self.type == ICMP6_MLD2_QUERY: if not self.code == 0: return "ICMPv6 sanity - 'code' must be 0 (RFC 3810)" if not ip6_hop == 1: return "ICMPv6 sanity - 'hop' must be 255 (RFC 3810)" elif self.type == ICMP6_ROUTER_SOLICITATION: if not self.code == 0: return "ICMPv6 sanity - 'code' must be 0 (RFC 4861)" if not ip6_hop == 255: return "ICMPv6 sanity - 'hop' must be 255 (RFC 4861)" if not (ip6_src.is_unicast or ip6_src.is_unspecified): return "ICMPv6 sanity - 'src' must be unicast or unspecified (RFC 4861)" if not ip6_dst == Ip6Address("ff02::2"): return "ICMPv6 sanity - 'dst' must be all-routers (RFC 4861)" if ip6_src.is_unspecified and self.nd_opt_slla: return "ICMPv6 sanity - 'nd_opt_slla' must not be included if 'src' is unspecified (RFC 4861)" elif self.type == ICMP6_ROUTER_ADVERTISEMENT: if not self.code == 0: return "ICMPv6 sanity - 'code' must be 0 (RFC 4861)" if not ip6_hop == 255: return "ICMPv6 sanity - 'hop' must be 255 (RFC 4861)" if not ip6_src.is_link_local: return "ICMPv6 sanity - 'src' must be link local (RFC 4861)" if not (ip6_dst.is_unicast or ip6_dst == Ip6Address("ff02::1")): return "ICMPv6 sanity - 'dst' must be unicast or all-nodes (RFC 4861)" elif self.type == ICMP6_NEIGHBOR_SOLICITATION: if not self.code == 0: return "ICMPv6 sanity - 'code' must be 0 (RFC 4861)" if not ip6_hop == 255: return "ICMPv6 sanity - 'hop' must be 255 (RFC 4861)" if not (ip6_src.is_unicast or ip6_src.is_unspecified): return "ICMPv6 sanity - 'src' must be unicast or unspecified (RFC 4861)" if ip6_dst not in { self.ns_target_address, self.ns_target_address.solicited_node_multicast }: return "ICMPv6 sanity - 'dst' must be 'ns_target_address' or it's solicited-node multicast (RFC 4861)" if not self.ns_target_address.is_unicast: return "ICMPv6 sanity - 'ns_target_address' must be unicast (RFC 4861)" if ip6_src.is_unspecified and self.nd_opt_slla is not None: return "ICMPv6 sanity - 'nd_opt_slla' must not be included if 'src' is unspecified" elif self.type == ICMP6_NEIGHBOR_ADVERTISEMENT: if not self.code == 0: return "ICMPv6 sanity - 'code' must be 0 (RFC 4861)" if not ip6_hop == 255: return "ICMPv6 sanity - 'hop' must be 255 (RFC 4861)" if not ip6_src.is_unicast: return "ICMPv6 sanity - 'src' must be unicast (RFC 4861)" if self.na_flag_s is True and not (ip6_dst.is_unicast or ip6_dst == Ip6Address("ff02::1")): return "ICMPv6 sanity - if 'na_flag_s' is set then 'dst' must be unicast or all-nodes (RFC 4861)" if self.na_flag_s is False and not ip6_dst == Ip6Address( "ff02::1"): return "ICMPv6 sanity - if 'na_flag_s' is not set then 'dst' must be all-nodes (RFC 4861)" elif self.type == ICMP6_MLD2_REPORT: if not self.code == 0: return "ICMPv6 sanity - 'code' must be 0 (RFC 3810)" if not ip6_hop == 1: return "ICMPv6 sanity - 'hop' must be 1 (RFC 3810)" return ""