Exemplo n.º 1
0
    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]}"
                )
Exemplo n.º 2
0
    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:]
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
    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]")
Exemplo n.º 8
0
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
Exemplo n.º 9
0
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
Exemplo n.º 10
0
 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]}")))
Exemplo n.º 11
0
    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")
Exemplo n.º 12
0
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)
Exemplo n.º 13
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}")
Exemplo n.º 14
0
    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")
Exemplo n.º 15
0
    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)
            ],
        )
Exemplo n.º 16
0
    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
Exemplo n.º 17
0
    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
Exemplo n.º 18
0
    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])}</>"
                )
Exemplo n.º 19
0
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
Exemplo n.º 20
0
    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
Exemplo n.º 21
0
    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)
Exemplo n.º 22
0
    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 ""