# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information
# Copyright (C) Gabriel Potter

"""
This file implements the rtnetlink API that is used to read the network
configuration of the machine.
"""

import socket
import struct
import time

import scapy.utils6

from scapy.consts import BIG_ENDIAN
from scapy.config import conf
from scapy.error import log_loading
from scapy.packet import (
    Packet,
    bind_layers,
)
from scapy.utils import atol, itom

from scapy.fields import (
    ByteEnumField,
    ByteField,
    EnumField,
    Field,
    FieldLenField,
    FlagsField,
    IP6Field,
    IPField,
    LenField,
    MACField,
    MayEnd,
    MultipleTypeField,
    PacketListField,
    PadField,
    StrLenField,
    XStrLenField,
)

from scapy.arch.common import _iff_flags

# Typing imports
from typing import (
    Any,
    Dict,
    List,
    Optional,
    Tuple,
    Type,
)

# from <linux/netlink.h> and <linux/rtnetlink.h>


# Common header


class rtmsghdr(Packet):
    fields_desc = [
        LenField("nlmsg_len", None, fmt="=L"),
        EnumField(
            "nlmsg_type",
            0,
            {
                # netlink.h
                3: "NLMSG_DONE",
                # rtnetlink.h
                16: "RTM_NEWLINK",
                17: "RTM_DELLINK",
                18: "RTM_GETLINK",
                19: "RTM_SETLINK",
                20: "RTM_NEWADDR",
                21: "RTM_DELADDR",
                22: "RTM_GETADDR",
                # 23: unused
                24: "RTM_NEWROUTE",
                25: "RTM_DELROUTE",
                26: "RTM_GETROUTE",
                # 27: unused
            },
            fmt="=H",
        ),
        FlagsField(
            "nlmsg_flags",
            0,
            16 if BIG_ENDIAN else -16,
            {
                0x01: "NLM_F_REQUEST",
                0x02: "NLM_F_MULTI",
                0x04: "NLM_F_ACK",
                0x08: "NLM_F_ECHO",
                0x10: "NLM_F_DUMP_INTR",
                0x20: "NLM_F_DUMP_FILTERED",
                # GET modifiers
                0x100: "NLM_F_ROOT",
                0x200: "NLM_F_MATCH",
                0x400: "NLM_F_ATOMIC",
            },
        ),
        Field("nlmsg_seq", 0, fmt="=L"),
        Field("nlmsg_pid", 0, fmt="=L"),
    ]

    def post_build(self, pkt: bytes, pay: bytes) -> bytes:
        pkt += pay
        if self.nlmsg_len is None:
            pkt = struct.pack("=L", len(pkt)) + pkt[4:]
        return pkt

    def extract_padding(self, s: bytes) -> Tuple[bytes, Optional[bytes]]:
        return s[: self.nlmsg_len - 16], s[self.nlmsg_len - 16 :]

    def answers(self, other: Packet) -> bool:
        return bool(other.nlmsg_seq == self.nlmsg_seq)


# DONE


class nlmsgerr_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField(
            "rta_type",
            0,
            {},
            fmt="=H",
        ),
        PadField(
            MultipleTypeField(
                [],
                StrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class nlmsgerr(Packet):
    fields_desc = [
        MayEnd(Field("status", 0, fmt="=L")),
        # Pay
        PacketListField("data", [], nlmsgerr_rtattr),
    ]


bind_layers(rtmsghdr, nlmsgerr, nlmsg_type=3)


# LINK messages


class ifla_af_spec_inet_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField(
            "rta_type",
            0,
            {
                0x00: "IFLA_INET_UNSPEC",
                0x01: "IFLA_INET_CONF",
            },
            fmt="=H",
        ),
        PadField(
            MultipleTypeField(
                [],
                XStrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class ifla_af_spec_inet6_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField(
            "rta_type",
            0,
            {
                0x00: "IFLA_INET6_UNSPEC",
                0x01: "IFLA_INET6_FLAGS",
                0x02: "IFLA_INET6_CONF",
                0x03: "IFLA_INET6_STATS",
                0x04: "IFLA_INET6_MCAST",
                0x05: "IFLA_INET6_CACHEINFO",
                0x06: "IFLA_INET6_ICMP6STATS",
                0x07: "IFLA_INET6_TOKEN",
                0x08: "IFLA_INET6_ADDR_GEN_MODE",
                0x09: "IFLA_INET6_RA_MTU",
            },
            fmt="=H",
        ),
        PadField(
            MultipleTypeField(
                [],
                XStrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class ifla_af_spec_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField("rta_type", 0, socket.AddressFamily, fmt="=H"),
        PadField(
            MultipleTypeField(
                [
                    (
                        # AF_INET
                        PacketListField(
                            "rta_data",
                            [],
                            ifla_af_spec_inet_rtattr,
                            length_from=lambda pkt: pkt.rta_len - 4,
                        ),
                        lambda pkt: pkt.rta_type == 2,
                    ),
                    (
                        # AF_INET6
                        PacketListField(
                            "rta_data",
                            [],
                            ifla_af_spec_inet6_rtattr,
                            length_from=lambda pkt: pkt.rta_len - 4,
                        ),
                        lambda pkt: pkt.rta_type == 10,
                    ),
                ],
                XStrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class ifinfomsg_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField(
            "rta_type",
            0,
            {
                0x00: "IFLA_UNSPEC",
                0x01: "IFLA_ADDRESS",
                0x02: "IFLA_BROADCAST",
                0x03: "IFLA_IFNAME",
                0x04: "IFLA_MTU",
                0x05: "IFLA_LINK",
                0x06: "IFLA_QDISC",
                0x07: "IFLA_STATS",
                0x08: "IFLA_COST",
                0x09: "IFLA_PRIORITY",
                0x0A: "IFLA_MASTER",
                0x0B: "IFLA_WIRELESS",
                0x0C: "IFLA_PROTINFO",
                0x0D: "IFLA_TXQLEN",
                0x0E: "IFLA_MAP",
                0x0F: "IFLA_WEIGHT",
                0x10: "IFLA_OPERSTATE",
                0x11: "IFLA_LINKMODE",
                0x12: "IFLA_LINKINFO",
                0x13: "IFLA_NET_NS_PID",
                0x14: "IFLA_IFALIAS",
                0x15: "IFLA_NUM_VS",
                0x16: "IFLA_VFINFO_LIST",
                0x17: "IFLA_STATS64",
                0x18: "IFLA_VF_PORTS",
                0x19: "IFLA_PORT_SELF",
                0x1A: "IFLA_AF_SPEC",
                0x1B: "IFLA_GROUP",
                0x1C: "IFLA_NET_NS_FD",
                0x1D: "IFLA_EXT_MASK",
                0x1E: "IFLA_PROMISCUITY",
                0x1F: "IFLA_NUM_TX_QUEUES",
                0x20: "IFLA_NUM_RX_QUEUES",
                0x21: "IFLA_CARRIER",
                0x22: "IFLA_PHYS_PORT_ID",
                0x23: "IFLA_CARRIER_CHANGES",
                0x24: "IFLA_PHYS_SWITCH_ID",
                0x25: "IFLA_LINK_NETNSID",
                0x26: "IFLA_PHYS_PORT_NAME",
                0x27: "IFLA_PROTO_DOWN",
                0x28: "IFLA_GSO_MAX_SEGS",
                0x29: "IFLA_GSO_MAX_SIZE",
                0x2A: "IFLA_PAD",
                0x2B: "IFLA_XDP",
                0x2C: "IFLA_EVENT",
                0x2D: "IFLA_NEW_NETNSID",
                0x2E: "IFLA_IF_NETNSID",
                0x2F: "IFLA_CARRIER_UP_COUNT",
                0x30: "IFLA_CARRIER_DOWN_COUNT",
                0x31: "IFLA_NEW_IFINDEX",
                0x32: "IFLA_MIN_MTU",
                0x33: "IFLA_MAX_MTU",
                0x34: "IFLA_PROP_LIST",
                0x35: "IFLA_ALT_IFNAME",
                0x36: "IFLA_PERM_ADDRESS",
                0x37: "IFLA_PROTO_DOWN_REASON",
                0x38: "IFLA_PARENT_DEV_NAME",
                0x39: "IFLA_PARENT_DEV_BUS_NAME",
                0x3A: "IFLA_GRO_MAX_SIZE",
                0x3B: "IFLA_TSO_MAX_SIZE",
                0x3C: "IFLA_TSO_MAX_SEGS",
                0x3D: "IFLA_ALLMULTI",
            },
            fmt="=H",
        ),
        PadField(
            MultipleTypeField(
                [
                    (
                        # IFLA_ADDRESS
                        MACField("rta_data", "00:00:00:00:00:00"),
                        lambda pkt: pkt.rta_type in [0x01, 0x36],
                    ),
                    (
                        # IFLA_IFNAME
                        StrLenField(
                            "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4
                        ),
                        lambda pkt: pkt.rta_type in [0x03],
                    ),
                    (
                        # IFLA_AF_SPEC
                        PacketListField(
                            "rta_data",
                            [],
                            ifla_af_spec_rtattr,
                            length_from=lambda pkt: pkt.rta_len - 4,
                        ),
                        lambda pkt: pkt.rta_type == 0x1A,
                    ),
                ],
                XStrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class ifinfomsg(Packet):
    fields_desc = [
        ByteEnumField("ifi_family", 0, socket.AddressFamily),
        ByteField("res", 0),
        Field("ifi_type", 0, fmt="=H"),
        Field("ifi_index", 0, fmt="=i"),
        FlagsField(
            "ifi_flags",
            0,
            32 if BIG_ENDIAN else -32,
            _iff_flags,
        ),
        Field("ifi_change", 0, fmt="=I"),
        # Pay
        PacketListField("data", [], ifinfomsg_rtattr),
    ]


bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=16)
bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=17)
bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=18)
bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=19)


# ADDR messages


class ifaddrmsg_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField(
            "rta_type",
            0,
            {
                0x00: "IFA_UNSPEC",
                0x01: "IFA_ADDRESS",
                0x02: "IFA_LOCAL",
                0x03: "IFA_LABEL",
                0x04: "IFA_BROADCAST",
                0x05: "IFA_ANYCAST",
                0x06: "IFA_CACHEINFO",
                0x07: "IFA_MULTICAST",
                0x08: "IFA_FLAGS",
                0x09: "IFA_RT_PRIORITY",
                0x0A: "IFA_TARGET_NETNSID",
                0x0B: "IFA_PROTO",
            },
            fmt="=H",
        ),
        PadField(
            MultipleTypeField(
                [
                    # IFA_ADDRESS, IFA_LOCAL, IFA_BROADCAST
                    (
                        IPField("rta_data", "0.0.0.0"),
                        lambda pkt: pkt.parent
                        and pkt.parent.ifa_family == 2
                        and pkt.rta_type in [0x01, 0x02, 0x04],
                    ),
                    (
                        IP6Field("rta_data", "::"),
                        lambda pkt: pkt.parent
                        and pkt.parent.ifa_family == 10
                        and pkt.rta_type in [0x01, 0x02, 0x04],
                    ),
                    (
                        # IFA_LABEL
                        StrLenField(
                            "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4
                        ),
                        lambda pkt: pkt.rta_type in [0x03],
                    ),
                ],
                XStrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class ifaddrmsg(Packet):
    fields_desc = [
        ByteEnumField("ifa_family", 0, socket.AddressFamily),
        ByteField("ifa_prefixlen", 0),
        FlagsField(
            "ifa_flags",
            0,
            -8,
            {
                0x01: "IFA_F_SECONDARY",
                0x02: "IFA_F_NODAD",
                0x04: "IFA_F_OPTIMISTIC",
                0x08: "IFA_F_DADFAILED",
                0x10: "IFA_F_HOMEADDRESS",
                0x20: "IFA_F_DEPRECATED",
                0x40: "IFA_F_TENTATIVE",
                0x80: "IFA_F_PERMANENT",
            },
        ),
        ByteField("ifa_scope", 0),
        Field("ifa_index", 0, fmt="=L"),
        # Pay
        PacketListField("data", [], ifaddrmsg_rtattr),
    ]


bind_layers(rtmsghdr, ifaddrmsg, nlmsg_type=20)
bind_layers(rtmsghdr, ifaddrmsg, nlmsg_type=21)
bind_layers(rtmsghdr, ifaddrmsg, nlmsg_type=22)


# ROUTE messages


RT_CLASS = {
    0: "RT_TABLE_UNSPEC",
    252: "RT_TABLE_COMPAT",
    253: "RT_TABLE_DEFAULT",
    254: "RT_TABLE_MAIN",
    255: "RT_TABLE_LOCAL",
}


class rtmsg_rtattr(Packet):
    fields_desc = [
        FieldLenField(
            "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4
        ),
        EnumField(
            "rta_type",
            0,
            {
                0x00: "RTA_UNSPEC",
                0x01: "RTA_DST",
                0x02: "RTS_SRC",
                0x03: "RTS_IIF",
                0x04: "RTS_OIF",
                0x05: "RTA_GATEWAY",
                0x06: "RTA_PRIORITY",
                0x07: "RTA_PREFSRC",
                0x08: "RTA_METRICS",
                0x09: "RTA_MULTIPATH",
                0x0B: "RTA_FLOW",
                0x0C: "RTA_CACHEINFO",
                0x0F: "RTA_TABLE",
                0x10: "RTA_MARK",
                0x11: "RTA_MFC_STATS",
                0x12: "RTA_VIA",
                0x13: "RTA_NEWDST",
                0x14: "RTA_PREF",
                0x15: "RTA_ENCAP_TYPE",
                0x16: "RTA_ENCAP",
                0x17: "RTA_EXPIRES",
                0x18: "RTA_PAD",
                0x19: "RTA_UID",
                0x1A: "RTA_TTL_PROPAGATE",
                0x1B: "RTA_IP_PROTO",
                0x1C: "RTA_SPORT",
                0x1D: "RTA_DPORT",
                0x1E: "RTA_NH_ID",
            },
            fmt="=H",
        ),
        PadField(
            MultipleTypeField(
                [
                    # RTA_DST, RTA_SRC, RTA_PREFSRC, RTA_GATEWAY
                    (
                        IPField("rta_data", "0.0.0.0"),
                        lambda pkt: pkt.parent
                        and pkt.parent.rtm_family == 2
                        and pkt.rta_type in [0x01, 0x02, 0x05, 0x07],
                    ),
                    (
                        IP6Field("rta_data", "::"),
                        lambda pkt: pkt.parent
                        and pkt.parent.rtm_family == 10
                        and pkt.rta_type in [0x01, 0x02, 0x05, 0x07],
                    ),
                    # RTS_OIF, RTA_PRIORITY
                    (
                        Field("rta_data", 0, fmt="=I"),
                        lambda pkt: pkt.rta_type in [0x04, 0x06, 0x10],
                    ),
                    # RTA_TABLE
                    (
                        EnumField("rta_data", 0, RT_CLASS, fmt="=I"),
                        lambda pkt: pkt.rta_type in [0x0F],
                    ),
                ],
                XStrLenField(
                    "rta_data",
                    b"",
                    length_from=lambda pkt: pkt.rta_len - 4,
                ),
            ),
            align=4,
        ),
    ]

    def default_payload_class(self, payload: bytes) -> Type[Packet]:
        return conf.padding_layer


class rtmsg(Packet):
    fields_desc = [
        ByteEnumField("rtm_family", 0, socket.AddressFamily),
        ByteField("rtm_dst_len", 0),
        ByteField("rtm_src_len", 0),
        ByteField("rtm_tos", 0),
        ByteEnumField(
            "rtm_table",
            0,
            RT_CLASS,
        ),
        ByteEnumField(
            "rtm_protocol",
            0,
            {
                0x00: "RTPROT_UNSPEC",
                0x01: "RTPROT_REDIRECT",
                0x02: "RTPROT_KERNEL",
                0x03: "RTPROT_BOOT",
                0x04: "RTPROT_STATIC",
            },
        ),
        ByteEnumField(
            "rtm_scope",
            0,
            {
                0: "RT_SCOPE_UNIVERSE",
                200: "RT_SCOPE_SITE",
                253: "RT_SCOPE_LINK",
                254: "RT_SCOPE_HOST",
                255: "RT_SCOPE_NOWHERE",
            },
        ),
        ByteEnumField(
            "rtm_type",
            0,
            {
                0x00: "RTN_UNSPEC",
                0x01: "RTN_UNICAST",
                0x02: "RTN_LOCAL",
                0x03: "RTN_BROADCAST",
                0x04: "RTN_ANYCAST",
                0x05: "RTN_MULTICAST",
                0x06: "RTN_BLACKHOLE",
                0x07: "RTN_UNREACHABLE",
                0x08: "RTN_PROHIBIT",
                0x09: "RTN_THROW",
                0x0A: "RTN_NAT",
                0x0B: "RTN_XRESOLVE",
            },
        ),
        FlagsField(
            "rtm_flags",
            0,
            32 if BIG_ENDIAN else -32,
            {
                0x100: "RTM_F_NOTIFY",
                0x200: "RTM_F_CLONED",
                0x400: "RTM_F_EQUALIZE",
                0x800: "RTM_F_PREFIX",
                0x1000: "RTM_F_LOOKUP_TABLE",
                0x2000: "RTM_F_FIB_MATCH",
                0x4000: "RTM_F_OFFLOAD",
                0x8000: "RTM_F_TRAP",
                0x20000000: "RTM_F_OFFLOAD_FAILED",
            },
        ),
        # Pay
        PacketListField("data", [], rtmsg_rtattr),
    ]


bind_layers(rtmsghdr, rtmsg, nlmsg_type=24)
bind_layers(rtmsghdr, rtmsg, nlmsg_type=25)
bind_layers(rtmsghdr, rtmsg, nlmsg_type=26)


class rtmsghdrs(Packet):
    fields_desc = [
        PacketListField(
            "msgs",
            [],
            rtmsghdr,
            # 65535 / len(rtmsghdr)
            max_count=4096,
        ),
    ]


# Utils


SOL_NETLINK = 270
NETLINK_EXT_ACK = 11
NETLINK_GET_STRICT_CHK = 12


def _sr1_rtrequest(pkt: Packet) -> List[Packet]:
    """
    Send / Receive a rtnetlink request
    """
    # Create socket
    sock = socket.socket(
        socket.AF_NETLINK,
        socket.SOCK_RAW | socket.SOCK_CLOEXEC,
        socket.NETLINK_ROUTE,
    )
    # Configure socket
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 32768)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1048576)
    try:
        sock.setsockopt(SOL_NETLINK, NETLINK_EXT_ACK, 1)
    except OSError:
        # Linux 4.12+ only
        pass
    sock.bind((0, 0))  # bind to kernel
    try:
        sock.setsockopt(SOL_NETLINK, NETLINK_GET_STRICT_CHK, 1)
    except OSError:
        # Linux 4.20+ only
        pass
    # Request routes
    sock.send(bytes(rtmsghdrs(msgs=[pkt])))
    results: List[Packet] = []
    try:
        while True:
            msgs = rtmsghdrs(sock.recv(65535))
            if not msgs:
                log_loading.warning("Failed to read the routes using RTNETLINK !")
                return []
            for msg in msgs.msgs:
                # Keep going until we find the end of the MULTI format
                if not msg.nlmsg_flags.NLM_F_MULTI or msg.nlmsg_type == 3:
                    if msg.nlmsg_type == 3 and nlmsgerr in msg and msg.status != 0:
                        # NLMSG_DONE with errors
                        if msg.data and msg.data[0].rta_type == 1:
                            log_loading.debug(
                                "Scapy RTNETLINK error on %s: '%s'. Please report !",
                                pkt.sprintf("%nlmsg_type%"),
                                msg.data[0].rta_data.decode(),
                            )
                            return []
                    return results
                results.append(msg)
    finally:
        sock.close()


def _get_ips(af_family=socket.AF_UNSPEC):
    # type: (socket.AddressFamily) -> Dict[int, List[Dict[str, Any]]]
    """
    Return a mapping of all interfaces IP using a NETLINK socket.
    """
    results = _sr1_rtrequest(
        rtmsghdr(
            nlmsg_type="RTM_GETADDR",
            nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
            nlmsg_seq=int(time.time()),
        )
        / ifaddrmsg(
            ifa_family=af_family,
            data=[],
        )
    )
    ips: Dict[int, List[Dict[str, Any]]] = {}
    for msg in results:
        ifindex = msg.ifa_index
        address = None
        family = msg.ifa_family
        local = None
        for attr in msg.data:
            if attr.rta_type == 0x01:  # IFA_ADDRESS
                address = attr.rta_data
            elif attr.rta_type == 0x02:  # IFA_LOCAL
                local = attr.rta_data
        # include/uapi/linux/if_addr.h: for point-to-point links, IFA_LOCAL is the local
        # interface address and IFA_ADDRESS is the peer address
        local_address = local if local is not None else address
        if local_address is not None:
            data = {
                "af_family": family,
                "index": ifindex,
                "address": local_address,
            }
            if family == 10:  # ipv6
                data["scope"] = scapy.utils6.in6_getscope(local_address)
            ips.setdefault(ifindex, list()).append(data)
    return ips


def _get_if_list():
    # type: () -> Dict[int, Dict[str, Any]]
    """
    Read the interfaces list using a NETLINK socket.
    """
    results = _sr1_rtrequest(
        rtmsghdr(
            nlmsg_type="RTM_GETLINK",
            nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
            nlmsg_seq=int(time.time()),
        )
        / ifinfomsg(
            data=[],
        )
    )
    lifips = _get_ips()
    interfaces = {}
    for msg in results:
        ifindex = msg.ifi_index
        ifname = None
        mac = "00:00:00:00:00:00"
        itype = msg.ifi_type
        ifflags = msg.ifi_flags
        ips = []
        for attr in msg.data:
            if attr.rta_type == 0x01:  # IFLA_ADDRESS
                mac = attr.rta_data
            elif attr.rta_type == 0x03:  # IFLA_NAME
                ifname = attr.rta_data[:-1].decode()
        if ifname is not None:
            if ifindex in lifips:
                ips = lifips[ifindex]
            interfaces[ifindex] = {
                "name": ifname,
                "index": ifindex,
                "flags": ifflags,
                "mac": mac,
                "type": itype,
                "ips": ips,
            }
    return interfaces


def in6_getifaddr():
    # type: () -> List[Tuple[str, int, str]]
    """
    Returns a list of 3-tuples of the form (addr, scope, iface) where
    'addr' is the address of scope 'scope' associated to the interface
    'iface'.

    This is the list of all addresses of all interfaces available on
    the system.
    """
    ips = _get_ips(af_family=socket.AF_INET6)
    ifaces = _get_if_list()
    result = []
    for intip in ips.values():
        for ip in intip:
            if ip["index"] in ifaces:
                result.append((ip["address"], ip["scope"], ifaces[ip["index"]]["name"]))
    return result


def _read_routes(af_family):
    # type: (socket.AddressFamily) -> List[Packet]
    """
    Read routes using a NETLINK socket.
    """
    results = []
    for rttable in ["RT_TABLE_LOCAL", "RT_TABLE_MAIN"]:
        results.extend(
            _sr1_rtrequest(
                rtmsghdr(
                    nlmsg_type="RTM_GETROUTE",
                    nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
                    nlmsg_seq=int(time.time()),
                )
                / rtmsg(
                    rtm_family=af_family,
                    data=[
                        rtmsg_rtattr(rta_type="RTA_TABLE", rta_data=rttable),
                    ],
                )
            )
        )
    return [msg for msg in results if msg.nlmsg_type == 24]  # RTM_NEWROUTE


def read_routes():
    # type: () -> List[Tuple[int, int, str, str, str, int]]
    """
    Read IPv4 routes for current process
    """
    routes = []
    ifaces = _get_if_list()
    results = _read_routes(socket.AF_INET)
    for msg in results:
        # Omit stupid answers (some OS conf appears to lead to this)
        if msg.rtm_family != socket.AF_INET:
            continue
        # Process the RTM_NEWROUTE
        net = 0
        mask = itom(msg.rtm_dst_len)
        gw = "0.0.0.0"
        iface = ""
        addr = "0.0.0.0"
        metric = 0
        for attr in msg.data:
            if attr.rta_type == 0x01:  # RTA_DST
                net = atol(attr.rta_data)
            elif attr.rta_type == 0x04:  # RTS_OIF
                index = attr.rta_data
                if index in ifaces:
                    iface = ifaces[index]["name"]
                else:
                    iface = str(index)
            elif attr.rta_type == 0x05:  # RTA_GATEWAY
                gw = attr.rta_data
            elif attr.rta_type == 0x06:  # RTA_PRIORITY
                metric = attr.rta_data
            elif attr.rta_type == 0x07:  # RTA_PREFSRC
                addr = attr.rta_data
        routes.append((net, mask, gw, iface, addr, metric))
    # Add multicast routes, as those are missing by default
    for _iface in ifaces.values():
        if _iface['flags'].MULTICAST:
            try:
                addr = next(
                    x["address"]
                    for x in _iface["ips"]
                    if x["af_family"] == socket.AF_INET
                )
            except StopIteration:
                continue
            routes.append((
                0xe0000000, 0xf0000000, "0.0.0.0", _iface["name"], addr, 250
            ))
    return routes


def read_routes6():
    # type: () -> List[Tuple[str, int, str, str, List[str], int]]
    """
    Read IPv6 routes for current process
    """
    routes = []
    ifaces = _get_if_list()
    results = _read_routes(socket.AF_INET6)
    lifaddr = _get_ips(af_family=socket.AF_INET6)
    for msg in results:
        # Omit stupid answers (some OS conf appears to lead to this)
        if msg.rtm_family != socket.AF_INET6:
            continue
        # Process the RTM_NEWROUTE
        prefix = "::"
        plen = msg.rtm_dst_len
        nh = "::"
        index = 0
        iface = ""
        metric = 0
        for attr in msg.data:
            if attr.rta_type == 0x01:  # RTA_DST
                prefix = attr.rta_data
            elif attr.rta_type == 0x04:  # RTS_OIF
                index = attr.rta_data
                if index in ifaces:
                    iface = ifaces[index]["name"]
                else:
                    iface = str(index)
            elif attr.rta_type == 0x05:  # RTA_GATEWAY
                nh = attr.rta_data
            elif attr.rta_type == 0x06:  # RTA_PRIORITY
                metric = attr.rta_data
        devaddrs = ((x["address"], x["scope"], iface) for x in lifaddr.get(index, []))
        cset = scapy.utils6.construct_source_candidate_set(prefix, plen, devaddrs)
        if cset:
            routes.append((prefix, plen, nh, iface, cset, metric))
    # Add multicast routes, as those are missing by default
    for _iface in ifaces.values():
        if _iface['flags'].MULTICAST:
            addrs = [
                x["address"]
                for x in _iface["ips"]
                if x["af_family"] == socket.AF_INET6
            ]
            if not addrs:
                continue
            routes.append((
                "ff00::", 8, "::", _iface["name"], addrs, 250
            ))
    return routes
