Repository: secdev/scapy
Pull request: Rewrite arch/linux
This rewrites most of the arch/linux code that handles the loading of `conf.ifaces`, `conf.route` and `conf.route6`, in order to use a RTNETLINK socket (instead of the current reading of `/proc/net/XXX`).

Among those:
- the read_routes(6) functions
- the linux interfaces provider
- arch/linux util functions

This:
- adds support for multiple IPv4 addresses to interfaces
- handles interfaces aliases, etc. natively without needing additional tricks
- allows us to have filter routes by routing tables, also giving us access to 'local', rather than only all tables. We can filter out bgp tables and other stuff we don't want.

fixes #4201
closes #3275
(maybe other issues too?)

## Note to all maintainers

This has been tested on 32 big endian architecture thanks to the folks at packit. (woooo)

This might need thorough **TESTING** on other platforms (that we don't include in the CI). I hope I didn't break anything on *BSD.

Problem statement:
IPv4 routing table is missing entries for additional addresses on the loopback interface
### Brief description

Scapy IPv4 routing table is missing additional addresses configured on the `lo` interface.

### Scapy version

2.5.0

### Python version

3.11.2

### Operating system

Debian bookworm 12.2 running kernel 6.1.52-1

### Additional environment information

_No response_

### How to reproduce

 Given the following IPv4 and IPv6 addresses assigned to `lo`:
```
vgutierrez@ncredir4001:~$ ip addr show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet 198.35.26.98/32 scope global lo:LVS
       valid_lft forever preferred_lft forever
    inet6 2620:0:863:ed1a::3/128 scope global 
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
```

scapy provides an IPv6 routing table that includes `2620:0:863:ed1a::3/128`:
```
>>> conf.route6
Destination                     Next Hop  Iface   Src candidates              Metric
2620:0:863:101::/64             ::        ens13   2620:0:863:101:10:128:0:32  256   
fe80::/64                       ::        ens13   fe80::a800:ff:fe39:c6fc     256   
fe80::/64                       ::        ipip60  fe80::cbd:4aff:fe6b:a7c5    256   
::1/128                         ::        lo      ::1                         0     
2620:0:863:101:10:128:0:32/128  ::        ens13   2620:0:863:101:10:128:0:32  0     
2620:0:863:ed1a::3/128          ::        lo      ::1                         0     
fe80::cbd:4aff:fe6b:a7c5/128    ::        ipip60  fe80::cbd:4aff:fe6b:a7c5    0     
fe80::a800:ff:fe39:c6fc/128     ::        ens13   fe80::a800:ff:fe39:c6fc     0     
::/0                            fe80::1   ens13   2620:0:863:101:10:128:0:32  1024 
```

But the IPv4 route table is missing `198.35.26.98/32`:
```
>>> conf.route
Network     Netmask        Gateway     Iface  Output IP    Metric
0.0.0.0     0.0.0.0        10.128.0.1  ens13  10.128.0.32  0     
10.128.0.0  255.255.255.0  0.0.0.0     ens13  10.128.0.32  0     
127.0.0.0   255.0.0.0      0.0.0.0     lo     127.0.0.1    1
```

In fact, if we compare scapy IPv4 route table against the kernel local routing table, quite some entries seem to be missing:
```
$ ip route show table local
local 10.128.0.32 dev ens13 proto kernel scope host src 10.128.0.32 
broadcast 10.128.0.255 dev ens13 proto kernel scope link src 10.128.0.32 
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 
local 127.0.0.42 dev ipip0 proto kernel scope host src 127.0.0.42 
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 
local 198.35.26.98 dev lo proto kernel scope host src 198.35.26.98 
```

### Actual result

_No response_

### Expected result

_No response_

### Related resources

_No response_

Hints from issue discussion:

Commit message: Rewrite arch/linux: interfaces/routes loading

This rewrites much of the arch/linux code, in order to use a RTNETLINK
socket instead of reading /proc/net/XXX.

Among those:
- the read_routes(6) functions
- the linux interfaces provider
- arch/linux util functions

This adds support for multiple IPv4 addresses to interfaces, among with
a generally much better handling of routes.

fixes #4201

Patch to review:
diff --git a/.config/mypy/mypy_check.py b/.config/mypy/mypy_check.py
index 5db49a4a0c3..371d93c3487 100644
--- a/.config/mypy/mypy_check.py
+++ b/.config/mypy/mypy_check.py
@@ -63,57 +63,56 @@
     "--ignore-missing-imports",
     # config
     "--follow-imports=skip",  # Remove eventually
-    "--config-file=" + os.path.abspath(
-        os.path.join(
-            localdir,
-            "mypy.ini"
-        )
-    ),
+    "--config-file=" + os.path.abspath(os.path.join(localdir, "mypy.ini")),
     "--show-traceback",
-] + ([
-    "--platform=" + PLATFORM
-] if PLATFORM else [])
+] + (["--platform=" + PLATFORM] if PLATFORM else [])
 
 if PLATFORM.startswith("linux"):
-    ARGS.extend([
-        "--always-true=LINUX",
-        "--always-false=OPENBSD",
-        "--always-false=FREEBSD",
-        "--always-false=NETBSD",
-        "--always-false=DARWIN",
-        "--always-false=WINDOWS",
-        "--always-false=BSD",
-    ])
+    ARGS.extend(
+        [
+            "--always-true=LINUX",
+            "--always-false=OPENBSD",
+            "--always-false=FREEBSD",
+            "--always-false=NETBSD",
+            "--always-false=DARWIN",
+            "--always-false=WINDOWS",
+            "--always-false=BSD",
+        ]
+    )
     FILES = [x for x in FILES if not x.startswith("scapy/arch/windows")]
 elif PLATFORM.startswith("win32"):
-    ARGS.extend([
-        "--always-false=LINUX",
-        "--always-false=OPENBSD",
-        "--always-false=FREEBSD",
-        "--always-false=NETBSD",
-        "--always-false=DARWIN",
-        "--always-true=WINDOWS",
-        "--always-false=WINDOWS_XP",
-        "--always-false=BSD",
-    ])
+    ARGS.extend(
+        [
+            "--always-false=LINUX",
+            "--always-false=OPENBSD",
+            "--always-false=FREEBSD",
+            "--always-false=NETBSD",
+            "--always-false=DARWIN",
+            "--always-true=WINDOWS",
+            "--always-false=WINDOWS_XP",
+            "--always-false=BSD",
+        ]
+    )
     FILES = [
-        x for x in FILES if (
-            x not in {
+        x
+        for x in FILES
+        if (
+            x
+            not in {
                 # Disabled on Windows
-                "scapy/arch/linux.py",
                 "scapy/arch/unix.py",
                 "scapy/arch/solaris.py",
                 "scapy/contrib/cansocket_native.py",
                 "scapy/contrib/isotp/isotp_native_socket.py",
             }
-        ) and not x.startswith("scapy/arch/bpf")
+        )
+        and not x.startswith("scapy/arch/bpf")
+        and not x.startswith("scapy/arch/linux")
     ]
 else:
     raise ValueError("Unknown platform")
 
 # Run mypy over the files
-ARGS += [
-    os.path.abspath(f) for f in FILES
-]
+ARGS += [os.path.abspath(f) for f in FILES]
 
 mypy_main(args=ARGS)
diff --git a/.config/mypy/mypy_enabled.txt b/.config/mypy/mypy_enabled.txt
index fda3f8e33c9..a5001e3509f 100644
--- a/.config/mypy/mypy_enabled.txt
+++ b/.config/mypy/mypy_enabled.txt
@@ -16,7 +16,8 @@ scapy/arch/bpf/core.py
 scapy/arch/bpf/supersocket.py
 scapy/arch/common.py
 scapy/arch/libpcap.py
-scapy/arch/linux.py
+scapy/arch/linux/__init__.py
+scapy/arch/linux/rtnetlink.py
 scapy/arch/solaris.py
 scapy/arch/unix.py
 scapy/arch/windows/__init__.py
diff --git a/scapy/arch/__init__.py b/scapy/arch/__init__.py
index 16eab8ecec7..0a692a6cf46 100644
--- a/scapy/arch/__init__.py
+++ b/scapy/arch/__init__.py
@@ -125,7 +125,6 @@ def get_if_raw_addr6(iff):
 # Next step is to import following architecture specific functions:
 # def attach_filter(s, filter, iface)
 # def get_if(iff,cmd)
-# def get_if_index(iff)
 # def get_if_raw_addr(iff)
 # def get_if_raw_hwaddr(iff)
 # def in6_getifaddr()
diff --git a/scapy/arch/common.py b/scapy/arch/common.py
index d16d5dea104..95d192bf983 100644
--- a/scapy/arch/common.py
+++ b/scapy/arch/common.py
@@ -7,12 +7,15 @@
 Functions common to different architectures
 """
 
+import socket
 import ctypes
+
 from scapy.config import conf
 from scapy.data import MTU, ARPHDR_ETHER, ARPHRD_TO_DLT
 from scapy.error import Scapy_Exception
-from scapy.interfaces import network_name
+from scapy.interfaces import network_name, resolve_iface, NetworkInterface
 from scapy.libs.structures import bpf_program
+from scapy.pton_ntop import inet_pton
 from scapy.utils import decode_locale_str
 
 # Type imports
@@ -33,7 +36,6 @@
     "RUNNING",
     "NOARP",
     "PROMISC",
-    "NOTRAILERS",
     "ALLMULTI",
     "MASTER",
     "SLAVE",
@@ -47,6 +49,15 @@
 ]
 
 
+def get_if_raw_addr(iff):
+    # type: (Union[NetworkInterface, str]) -> bytes
+    """Return the raw IPv4 address of interface"""
+    iff = resolve_iface(iff)
+    if not iff.ip:
+        return b"\x00" * 4
+    return inet_pton(socket.AF_INET, iff.ip)
+
+
 # BPF HANDLERS
 
 
diff --git a/scapy/arch/linux.py b/scapy/arch/linux/__init__.py
similarity index 56%
rename from scapy/arch/linux.py
rename to scapy/arch/linux/__init__.py
index 566916ff2ad..d08d1bcc026 100644
--- a/scapy/arch/linux.py
+++ b/scapy/arch/linux/__init__.py
@@ -11,9 +11,7 @@
 from fcntl import ioctl
 from select import select
 
-import array
 import ctypes
-import itertools
 import os
 import socket
 import struct
@@ -21,22 +19,18 @@
 import sys
 import time
 
-import scapy.utils
-import scapy.utils6
-from scapy.compat import raw, plain_str
+from scapy.compat import raw
 from scapy.consts import LINUX
 from scapy.arch.common import (
-    _iff_flags,
     compile_filter,
 )
-from scapy.arch.unix import get_if, get_if_raw_hwaddr
+from scapy.arch.unix import get_if
 from scapy.config import conf
 from scapy.data import MTU, ETH_P_ALL, SOL_PACKET, SO_ATTACH_FILTER, \
     SO_TIMESTAMPNS
 from scapy.error import (
     ScapyInvalidPlatformException,
     Scapy_Exception,
-    log_loading,
     log_runtime,
     warning,
 )
@@ -48,18 +42,23 @@
 )
 from scapy.libs.structures import sock_fprog
 from scapy.packet import Packet, Padding
-from scapy.pton_ntop import inet_ntop
 from scapy.supersocket import SuperSocket
 
 # re-export
-from scapy.arch.unix import read_nameservers  # noqa: F401
+from scapy.arch.common import get_if_raw_addr  # noqa: F401
+from scapy.arch.unix import read_nameservers, get_if_raw_hwaddr  # noqa: F401
+from scapy.arch.linux.rtnetlink import (  # noqa: F401
+    read_routes,
+    read_routes6,
+    in6_getifaddr,
+    _get_if_list,
+)
 
 # Typing imports
 from typing import (
     Any,
     Callable,
     Dict,
-    List,
     NoReturn,
     Optional,
     Tuple,
@@ -118,34 +117,8 @@
 PACKET_FASTROUTE = 6  # Fastrouted frame
 # Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space
 
-# Utils
-
-
-def get_if_raw_addr(iff):
-    # type: (_GlobInterfaceType) -> bytes
-    r"""
-    Return the raw IPv4 address of an interface.
-    If unavailable, returns b"\0\0\0\0"
-    """
-    try:
-        return get_if(iff, SIOCGIFADDR)[20:24]
-    except IOError:
-        return b"\0\0\0\0"
-
-
-def _get_if_list():
-    # type: () -> List[str]
-    """
-    Function to read the interfaces from /proc/net/dev
-    """
-    try:
-        with open("/proc/net/dev", "r", errors='replace') as f:
-            return [line.split(':', 1)[0].strip()
-                    for line in itertools.islice(f, 2, None)]
-    except IOError:
-        log_loading.critical("Can't open /proc/net/dev !")
-        return []
 
+# Utils
 
 def attach_filter(sock, bpf_filter, iface):
     # type: (socket.socket, str, _GlobInterfaceType) -> None
@@ -179,239 +152,14 @@ def set_promisc(s, iff, val=1):
     s.setsockopt(SOL_PACKET, cmd, mreq)
 
 
-def get_alias_address(iface_name,  # type: str
-                      ip_mask,  # type: int
-                      gw_str,  # type: str
-                      metric  # type: int
-                      ):
-    # type: (...) -> Optional[Tuple[int, int, str, str, str, int]]
-    """
-    Get the correct source IP address of an interface alias
-    """
-
-    # Detect the architecture
-    if scapy.consts.IS_64BITS:
-        offset, name_len = 16, 40
-    else:
-        offset, name_len = 32, 32
-
-    # Retrieve interfaces structures
-    sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-    names_ar = array.array('B', b'\0' * 4096)
-    ifreq = ioctl(sck.fileno(), SIOCGIFCONF,
-                  struct.pack("iL", len(names_ar), names_ar.buffer_info()[0]))
-
-    # Extract interfaces names
-    out = struct.unpack("iL", ifreq)[0]
-    names_b = names_ar.tobytes()
-    names = [names_b[i:i + offset].split(b'\0', 1)[0] for i in range(0, out, name_len)]  # noqa: E501
-
-    # Look for the IP address
-    for ifname_b in names:
-        ifname = plain_str(ifname_b)
-        # Only look for a matching interface name
-        if not ifname.startswith(iface_name):
-            continue
-
-        # Retrieve and convert addresses
-        ifreq = ioctl(sck, SIOCGIFADDR, struct.pack("16s16x", ifname_b))
-        ifaddr = struct.unpack(">I", ifreq[20:24])[0]  # type: int
-        ifreq = ioctl(sck, SIOCGIFNETMASK, struct.pack("16s16x", ifname_b))
-        msk = struct.unpack(">I", ifreq[20:24])[0]  # type: int
-
-        # Get the full interface name
-        if ':' in ifname:
-            ifname = ifname[:ifname.index(':')]
-        else:
-            continue
-
-        # Check if the source address is included in the network
-        if (ifaddr & msk) == ip_mask:
-            sck.close()
-            return (ifaddr & msk, msk, gw_str, ifname,
-                    scapy.utils.ltoa(ifaddr), metric)
-
-    sck.close()
-    return None
-
-
-def read_routes():
-    # type: () -> List[Tuple[int, int, str, str, str, int]]
-    """
-    Read routes from /proc/net/route
-    """
-    try:
-        with open("/proc/net/route", "r", errors="replace") as f, \
-             socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
-            routes = []
-            # Loopback route
-            try:
-                ifreq = ioctl(s, SIOCGIFADDR,
-                              struct.pack("16s16x", conf.loopback_name.encode("utf8")))
-                addrfamily = struct.unpack("h", ifreq[16:18])[0]
-                if addrfamily == socket.AF_INET:
-                    ifreq2 = ioctl(
-                        s, SIOCGIFNETMASK,
-                        struct.pack("16s16x", conf.loopback_name.encode("utf8"))
-                    )
-                    msk = socket.ntohl(struct.unpack("I", ifreq2[20:24])[0])
-                    dst = socket.ntohl(struct.unpack("I", ifreq[20:24])[0]) & msk
-                    ifaddr = scapy.utils.inet_ntoa(ifreq[20:24])
-                    routes.append((dst, msk, "0.0.0.0", conf.loopback_name,
-                                   ifaddr, 1))
-                else:
-                    warning("Interface %s: unknown address family (%i)" % (
-                        conf.loopback_name, addrfamily
-                    ))
-            except IOError as err:
-                if err.errno == 99:
-                    warning("Interface %s: no address assigned" % conf.loopback_name)
-                else:
-                    warning("Interface %s: failed to get address config (%s)" % (
-                        conf.loopback_name, str(err))
-                    )
-
-            # Load routes
-            for line in (x.split() for x in itertools.islice(f, 1, None)):
-                # line = iff, dst, gw, flags, _, _, metric, msk, _, _, _
-                iff, gw = line[0], line[2]
-                dst, flags, msk = tuple(
-                    int(x, 16) for x in [line[1], line[3], line[7]]
-                )
-                metric = int(line[6])
-                # Check iface flags
-                if flags & RTF_UP == 0:
-                    continue
-                if flags & RTF_REJECT:
-                    continue
-                try:
-                    ifreq = ioctl(s, SIOCGIFADDR,
-                                  struct.pack("16s16x", iff.encode("utf8")))
-                except IOError:
-                    # interface is present in routing tables but does not
-                    # have any assigned IP
-                    ifaddr = "0.0.0.0"
-                    ifaddr_int = 0
-                else:
-                    addrfamily = struct.unpack("h", ifreq[16:18])[0]
-                    if addrfamily == socket.AF_INET:
-                        ifaddr = scapy.utils.inet_ntoa(ifreq[20:24])
-                        ifaddr_int = struct.unpack("!I", ifreq[20:24])[0]
-                    else:
-                        warning("Interface %s: unknown address family (%i)", iff, addrfamily)  # noqa: E501
-                        continue
-
-                # Attempt to detect an interface alias based on addresses
-                # inconsistencies
-                dst = socket.htonl(dst) & 0xffffffff
-                msk = socket.htonl(msk) & 0xffffffff
-                gw = scapy.utils.inet_ntoa(struct.pack("I", int(gw, 16)))
-
-                route = (dst, msk, gw, iff, ifaddr, metric)
-                if ifaddr_int & msk != dst:
-                    tmp_route = get_alias_address(iff, dst, gw, metric)
-                    if tmp_route:
-                        route = tmp_route
-                routes.append(route)
-
-            return routes
-
-    except IOError:
-        log_loading.critical("Can't open /proc/net/route !")
-        return []
-
-############
-#   IPv6   #
-############
-
-
-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.
-    """
-    try:
-        with open("/proc/net/if_inet6", "r", errors='replace') as f:
-            ret = []  # type: List[Tuple[str, int, str]]
-            for addr, _, _, scope, _, ifname in (x.split() for x in f):
-                addr = scapy.utils6.in6_ptop(
-                    b':'.join(
-                        struct.unpack('4s4s4s4s4s4s4s4s', addr.encode())
-                    ).decode()
-                )
-                # (addr, scope, iface)
-                ret.append((addr, int(scope, 16), ifname))
-            return ret
-    except IOError:
-        return []
-
-
-def read_routes6():
-    # type: () -> List[Tuple[str, int, str, str, List[str], int]]
-    """
-    Read routes from /proc/net/ipv6_route
-    """
-
-    # 1. destination network
-    # 2. destination prefix length
-    # 3. source network displayed
-    # 4. source prefix length
-    # 5. next hop
-    # 6. metric
-    # 7. reference counter (?!?)
-    # 8. use counter (?!?)
-    # 9. flags
-    # 10. device name
-
-    def proc2r(p):
-        # type: (str) -> str
-        ret = struct.unpack('4s4s4s4s4s4s4s4s', p.encode())
-        addr = b':'.join(ret).decode()
-        return scapy.utils6.in6_ptop(addr)
-
-    try:
-        with open("/proc/net/ipv6_route", "r", errors='replace') as f:
-            routes = []
-            lifaddr = in6_getifaddr()
-            for line in (x.split() for x in itertools.islice(f, 1, None)):
-                # line = d, dp, _, _, nh, metric, _, _, fl, dev
-                d, nh, dev = line[0], line[4], line[9]
-                dp, metric, flags = [int(x, 16) for x in [line[1], line[5], line[8]]]
-
-                if flags & RTF_UP == 0:
-                    continue
-                if flags & RTF_REJECT:
-                    continue
-
-                d = proc2r(d)
-                nh = proc2r(nh)
-
-                cset = []  # candidate set (possible source addresses)
-                if dev == conf.loopback_name:
-                    if d == '::':
-                        continue
-                    cset = ['::1']
-                else:
-                    devaddrs = (x for x in lifaddr if x[2] == dev)
-                    cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs)
-
-                if len(cset) != 0:
-                    routes.append((d, dp, nh, dev, cset, metric))
-            return routes
-    except IOError:
-        return []
-
-
 def get_if_index(iff):
     # type: (_GlobInterfaceType) -> int
     return int(struct.unpack("I", get_if(iff, SIOCGIFINDEX)[16:20])[0])
 
 
+# Interface provider
+
+
 class LinuxInterfaceProvider(InterfaceProvider):
     name = "sys"
 
@@ -421,35 +169,15 @@ def _is_valid(self, dev):
 
     def load(self):
         # type: () -> Dict[str, NetworkInterface]
-        from scapy.fields import FlagValue
         data = {}
-        ips = in6_getifaddr()
-        for i in _get_if_list():
-            try:
-                ifflags = struct.unpack("16xH14x", get_if(i, SIOCGIFFLAGS))[0]
-                index = get_if_index(i)
-                mac = scapy.utils.str2mac(
-                    get_if_raw_hwaddr(i, siocgifhwaddr=SIOCGIFHWADDR)[1]
-                )
-                ip = None  # type: Optional[str]
-                ip = inet_ntop(socket.AF_INET, get_if_raw_addr(i))
-            except IOError:
-                warning("Interface %s does not exist!", i)
-                continue
-            if ip == "0.0.0.0":
-                ip = None
-            ifflags = FlagValue(ifflags, _iff_flags)
-            if_data = {
-                "name": i,
-                "network_name": i,
-                "description": i,
-                "flags": ifflags,
-                "index": index,
-                "ip": ip,
-                "ips": [x[0] for x in ips if x[2] == i] + [ip] if ip else [],
-                "mac": mac
-            }
-            data[i] = NetworkInterface(self, if_data)
+        for iface in _get_if_list().values():
+            if_data = iface.copy()
+            if_data.update({
+                "network_name": iface["name"],
+                "description": iface["name"],
+                "ips": [x["address"] for x in iface["ips"]]
+            })
+            data[iface["name"]] = NetworkInterface(self, if_data)
         return data
 
 
diff --git a/scapy/arch/linux/rtnetlink.py b/scapy/arch/linux/rtnetlink.py
new file mode 100644
index 00000000000..d162e4116c3
--- /dev/null
+++ b/scapy/arch/linux/rtnetlink.py
@@ -0,0 +1,851 @@
+# 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 files implements the rtnetlink API that is used to read the network
+configuration of the machine.
+"""
+
+import socket
+import os
+
+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,
+    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 extract_padding(self, s: bytes) -> Tuple[bytes, Optional[bytes]]:
+        return s[: self.nlmsg_len - 16], s[self.nlmsg_len - 16 :]
+
+
+# LINK messages
+
+
+class ifla_af_spec_inet_rtattr(Packet):
+    fields_desc = [
+        FieldLenField("rta_len", None, length_of="rta_data", fmt="=H"),
+        EnumField(
+            "rta_type",
+            0,
+            {
+                0x00: "IFLA_INET_UNSPEC",
+                0x01: "IFLA_INET_CONF",
+            },
+            fmt="=H",
+        ),
+        PadField(
+            MultipleTypeField(
+                [],
+                XStrLenField(
+                    "rta_data",
+                    b"\x00\x00\x00\x00",
+                    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"),
+        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"\x00\x00\x00\x00",
+                    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"),
+        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"\x00\x00\x00\x00",
+                    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"),
+        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),  # type: ignore
+        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", 0xFFFFFFFF, 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"),
+        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),  # type: ignore
+        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"),
+        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],
+                    ),
+                    # 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),  # type: ignore
+        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",
+            },
+        ),
+        # 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),
+    ]
+
+
+# Utils
+
+
+def _sr1_rtrequest(pkt: Packet) -> List[Packet]:
+    """
+    Send / Receive a rtnetlink request
+    """
+    # Create socket
+    sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
+    pid = os.getpid()
+    sock.bind((pid, 0))  # bind to kernel
+    # 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:
+                    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.
+    """
+    pid = os.getpid()
+    results = _sr1_rtrequest(
+        rtmsghdr(
+            nlmsg_type="RTM_GETADDR",
+            nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
+            nlmsg_seq=1,
+            nlmsg_pid=pid,
+        )
+        / ifaddrmsg(
+            ifa_family=af_family,
+            data=[
+                ifaddrmsg_rtattr(),
+                ifaddrmsg_rtattr(),
+                ifaddrmsg_rtattr(),
+            ],
+        )
+    )
+    ips: Dict[int, List[Dict[str, Any]]] = {}
+    for msg in results:
+        ifindex = msg.ifa_index
+        address = None
+        family = msg.ifa_family
+        for attr in msg.data:
+            if attr.rta_type == 0x01:  # IFA_ADDRESS
+                address = attr.rta_data
+                break
+        if address is not None:
+            data = {
+                "af_family": family,
+                "index": ifindex,
+                "address": address,
+            }
+            if family == 10:  # ipv6
+                data["scope"] = scapy.utils6.in6_getscope(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.
+    """
+    pid = os.getpid()
+    results = _sr1_rtrequest(
+        rtmsghdr(
+            nlmsg_type="RTM_GETLINK",
+            nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
+            nlmsg_seq=1,
+            nlmsg_pid=pid,
+        )
+        / ifinfomsg(
+            data=[
+                ifinfomsg_rtattr(rta_type="IFLA_UNSPEC"),
+            ],
+        )
+    )
+    lifips = _get_ips()
+    interfaces = {}
+    for msg in results:
+        ifindex = msg.ifi_index
+        ifname = None
+        mac = "00:00:00:00:00:00"
+        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,
+                "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.
+    """
+    pid = os.getpid()
+    results = _sr1_rtrequest(
+        rtmsghdr(
+            nlmsg_type="RTM_GETROUTE",
+            nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
+            nlmsg_seq=1,
+            nlmsg_pid=pid,
+        )
+        / rtmsg(
+            rtm_family=af_family,
+            data=[
+                rtmsg_rtattr(rta_type="RTA_TABLE", rta_data="RT_TABLE_UNSPEC"),
+            ],
+        )
+    )
+    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:
+        # 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))
+    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:
+        # 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))
+    return routes
diff --git a/scapy/arch/windows/__init__.py b/scapy/arch/windows/__init__.py
index f432a231eb4..a80f7b32eef 100755
--- a/scapy/arch/windows/__init__.py
+++ b/scapy/arch/windows/__init__.py
@@ -35,13 +35,16 @@
 )
 from scapy.interfaces import NetworkInterface, InterfaceProvider, \
     dev_from_index, resolve_iface, network_name
-from scapy.pton_ntop import inet_ntop, inet_pton
+from scapy.pton_ntop import inet_ntop
 from scapy.utils import atol, itom, mac2str, str2mac
 from scapy.utils6 import construct_source_candidate_set, in6_getscope
 from scapy.data import ARPHDR_ETHER, load_manuf
 from scapy.compat import plain_str
 from scapy.supersocket import SuperSocket
 
+# re-export
+from scapy.arch.common import get_if_raw_addr  # noqa: F401
+
 # Typing imports
 from typing import (
     Any,
@@ -705,15 +708,6 @@ def get_ips(v6=False):
     return res
 
 
-def get_if_raw_addr(iff):
-    # type: (Union[NetworkInterface, str]) -> bytes
-    """Return the raw IPv4 address of interface"""
-    iff = resolve_iface(iff)
-    if not iff.ip:
-        return b"\x00" * 4
-    return inet_pton(socket.AF_INET, iff.ip)
-
-
 def get_ip_from_name(ifname, v6=False):
     # type: (str, bool) -> str
     """Backward compatibility: indirectly calls get_ips
diff --git a/scapy/fields.py b/scapy/fields.py
index 7f9da8e10b8..325537d59a9 100644
--- a/scapy/fields.py
+++ b/scapy/fields.py
@@ -623,7 +623,7 @@ class PadField(_FieldContainer):
     __slots__ = ["fld", "_align", "_padwith"]
 
     def __init__(self, fld, align, padwith=None):
-        # type: (Field[Any, Any], int, Optional[bytes]) -> None
+        # type: (AnyField, int, Optional[bytes]) -> None
         self.fld = fld
         self._align = align
         self._padwith = padwith or b"\x00"
diff --git a/scapy/route.py b/scapy/route.py
index 9304be793d6..f01d351fb8f 100644
--- a/scapy/route.py
+++ b/scapy/route.py
@@ -145,8 +145,8 @@ def ifadd(self, iff, addr):
         the_net = the_rawaddr & the_msk
         self.routes.append((the_net, the_msk, '0.0.0.0', iff, the_addr, 1))
 
-    def route(self, dst=None, verbose=conf.verb):
-        # type: (Optional[str], int) -> Tuple[str, str, str]
+    def route(self, dst=None, verbose=conf.verb, _internal=False):
+        # type: (Optional[str], int, bool) -> Tuple[str, str, str]
         """Returns the IPv4 routes to a host.
         parameters:
          - dst: the IPv4 of the destination host
@@ -195,6 +195,10 @@ def route(self, dst=None, verbose=conf.verb):
         paths.sort(key=lambda x: (-x[0], x[1]))
         # Return interface
         ret = paths[0][2]
+        # Check if source is 0.0.0.0. This is a 'via' route with no src.
+        if ret[1] == "0.0.0.0" and not _internal:
+            # Then get the source from route(gw)
+            ret = (ret[0], self.route(ret[2], _internal=True)[1], ret[2])
         self.cache[dst] = ret
         return ret
 
diff --git a/scapy/utils6.py b/scapy/utils6.py
index ed3f93a0462..5343cdd7106 100644
--- a/scapy/utils6.py
+++ b/scapy/utils6.py
@@ -85,6 +85,8 @@ def cset_sort(x, y):
             cset = (x for x in laddr if x[1] == IPV6_ADDR_SITELOCAL)
     elif addr == '::' and plen == 0:
         cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL)
+    elif addr == '::1':
+        cset = (x for x in laddr if x[1] == IPV6_ADDR_LOOPBACK)
     addrs = [x[0] for x in cset]
     # TODO convert the cmd use into a key
     addrs.sort(key=cmp_to_key(cset_sort))  # Sort with global addresses first
diff --git a/test/linux.uts b/test/linux.uts
index 33728a4c059..89a3b867e2b 100644
--- a/test/linux.uts
+++ b/test/linux.uts
@@ -55,8 +55,10 @@ if exit_status == 0:
     conf.route.resync()
     print(conf.route.routes)
     assert conf.route.route("192.0.2.43") == ("scapy0.42", "203.0.113.42", "203.0.113.41")
-    route_specific = (3221226027, 4294967295, "203.0.113.41", "scapy0.42", "203.0.113.42", 0)
+    route_specific = (3221226027, 4294967295, "203.0.113.41", "scapy0.42", "0.0.0.0", 0)
     assert route_specific in conf.route.routes
+    assert conf.route.route("203.0.113.42") == ('scapy0.42', '203.0.113.42', '0.0.0.0')
+    assert conf.route.route("203.0.113.43") == ('scapy0.42', '203.0.113.42', '0.0.0.0')
     exit_status = os.system("ip link del name dev scapy0")
 else:
     assert True


Code review comments:
diff_hunk: @@ -0,0 +1,851 @@
+# 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 files implements the rtnetlink API that is used to read the network
+configuration of the machine.
+"""
+
+import socket
+import os
+
+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,
+    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 extract_padding(self, s: bytes) -> Tuple[bytes, Optional[bytes]]:
+        return s[: self.nlmsg_len - 16], s[self.nlmsg_len - 16 :]
+
+
+# LINK messages
+
+
+class ifla_af_spec_inet_rtattr(Packet):
+    fields_desc = [
+        FieldLenField("rta_len", None, length_of="rta_data", fmt="=H"),
+        EnumField(
+            "rta_type",
+            0,
+            {
+                0x00: "IFLA_INET_UNSPEC",
+                0x01: "IFLA_INET_CONF",
+            },
+            fmt="=H",
+        ),
+        PadField(
+            MultipleTypeField(
+                [],
+                XStrLenField(
+                    "rta_data",
+                    b"\x00\x00\x00\x00",
+                    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"),
+        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"\x00\x00\x00\x00",
+                    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"),
+        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"\x00\x00\x00\x00",
+                    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"),
+        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),  # type: ignore
+        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", 0xFFFFFFFF, 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"),
+        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),  # type: ignore
+        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"),
+        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],
+                    ),
+                    # 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),  # type: ignore
+        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",
+            },
+        ),
+        # 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),
+    ]
+
+
+# Utils
+
+
+def _sr1_rtrequest(pkt: Packet) -> List[Packet]:
+    """
+    Send / Receive a rtnetlink request
+    """
+    # Create socket
+    sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
+    pid = os.getpid()
+    sock.bind((pid, 0))  # bind to kernel
+    # 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:
+                    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.
+    """
+    pid = os.getpid()
+    results = _sr1_rtrequest(
+        rtmsghdr(
+            nlmsg_type="RTM_GETADDR",
+            nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH",
+            nlmsg_seq=1,
+            nlmsg_pid=pid,
+        )
+        / ifaddrmsg(
+            ifa_family=af_family,
+            data=[
+                ifaddrmsg_rtattr(),
path: scapy/arch/linux/rtnetlink.py
text: @user1:
We do we need to specify 3  ifaddrmsg_rtattr()?

@author:
We don't. fixed