diff --git a/share/man/man4/rtnetlink.4 b/share/man/man4/rtnetlink.4 index a06807809691..dc40b277d934 100644 --- a/share/man/man4/rtnetlink.4 +++ b/share/man/man4/rtnetlink.4 @@ -1,521 +1,534 @@ .\" .\" Copyright (C) 2022 Alexander Chernikov . .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd November 1, 2022 .Dt RTNETLINK 4 .Os .Sh NAME .Nm RTNetlink .Nd Network configuration-specific Netlink family .Sh SYNOPSIS .In netlink/netlink.h .In netlink/netlink_route.h .Ft int .Fn socket AF_NETLINK SOCK_DGRAM NETLINK_ROUTE .Sh DESCRIPTION The .Dv NETLINK_ROUTE family aims to be the primary configuration mechanism for all network-related tasks. Currently it supports configuring interfaces, interface addresses, routes, nexthops and arp/ndp neighbors. .Sh ROUTES All route configuration messages share the common header: .Bd -literal struct rtmsg { unsigned char rtm_family; /* address family */ unsigned char rtm_dst_len; /* Prefix length */ unsigned char rtm_src_len; /* Deprecated, set to 0 */ unsigned char rtm_tos; /* Type of service (not used) */ unsigned char rtm_table; /* deprecated, set to 0 */ unsigned char rtm_protocol; /* Routing protocol id (RTPROT_) */ unsigned char rtm_scope; /* Route distance (RT_SCOPE_) */ unsigned char rtm_type; /* Route type (RTN_) */ unsigned rtm_flags; /* Route flags (not supported) */ }; .Ed .Pp The .Va rtm_family specifies the route family to be operated on. Currently, .Dv AF_INET6 and .Dv AF_INET are the only supported families. The route prefix length is stored in .Va rtm_dst_len . The caller should set the originator identity (one of the .Dv RTPROT_ values) in .Va rtm_protocol . It is useful for users and for the application itself, allowing for easy identification of self-originated routes. The route scope has to be set via .Va rtm_scope field. The supported values are: .Bd -literal -offset indent -compact RT_SCOPE_UNIVERSE Global scope RT_SCOPE_LINK Link scope .Ed .Pp Route type needs to be set. The defined values are: .Bd -literal -offset indent -compact RTN_UNICAST Unicast route RTN_MULTICAST Multicast route RTN_BLACKHOLE Drops traffic towards destination RTN_PROHIBIT Drops traffic and sends reject .Ed .Pp The following messages are supported: .Ss RTM_NEWROUTE Adds a new route. All NL flags are supported. Extending a multipath route requires NLM_F_APPEND flag. .Ss RTM_DELROUTE Tries to delete a route. The route is specified using a combination of .Dv RTA_DST TLV and .Va rtm_dst_len . .Ss RTM_GETROUTE Fetches a single route or all routes in the current VNET, depending on the .Dv NLM_F_DUMP flag. Each route is reported as .Dv RTM_NEWROUTE message. The following filters are recognised by the kernel: .Pp .Bd -literal -offset indent -compact rtm_family required family or AF_UNSPEC RTA_TABLE fib number or RT_TABLE_UNSPEC to return all fibs .Ed .Ss TLVs .Bl -tag -width indent .It Dv RTA_DST (binary) IPv4/IPv6 address, depending on the .Va rtm_family . .It Dv RTA_OIF (uint32_t) transmit interface index. .It Dv RTA_GATEWAY (binary) IPv4/IPv6 gateway address, depending on the .Va rtm_family . .It Dv RTA_METRICS (nested) Container attribute, listing route properties. The only supported sub-attribute is .Dv RTAX_MTU , which stores path MTU as uint32_t. .It Dv RTA_MULTIPATH This attribute contains multipath route nexthops with their weights. These nexthops are represented as a sequence of .Va rtnexthop structures, each followed by .Dv RTA_GATEWAY or .Dv RTA_VIA attributes. .Bd -literal struct rtnexthop { unsigned short rtnh_len; unsigned char rtnh_flags; unsigned char rtnh_hops; /* nexthop weight */ int rtnh_ifindex; }; .Ed .Pp The .Va rtnh_len field specifies the total nexthop info length, including both .Va struct rtnexthop and the following TLVs. The .Va rtnh_hops field stores relative nexthop weight, used for load balancing between group members. The .Va rtnh_ifindex field contains the index of the transmit interface. .Pp The following TLVs can follow the structure: .Bd -literal -offset indent -compact RTA_GATEWAY IPv4/IPv6 nexthop address of the gateway RTA_VIA IPv6 nexthop address for IPv4 route RTA_KNH_ID Kernel-specific index of the nexthop .Ed .It Dv RTA_KNH_ID (uint32_t) (FreeBSD-specific) Auto-allocated kernel index of the nexthop. .It Dv RTA_RTFLAGS (uint32_t) (FreeBSD-specific) rtsock route flags. .It Dv RTA_TABLE (uint32_t) Fib number of the route. Default route table is .Dv RT_TABLE_MAIN . To explicitely specify "all tables" one needs to set the value to .Dv RT_TABLE_UNSPEC . .It Dv RTA_EXPIRES (uint32_t) seconds till path expiration. .It Dv RTA_NH_ID (uint32_t) useland nexthop or nexthop group index. .El .Ss Groups The following groups are defined: .Bd -literal -offset indent -compact RTNLGRP_IPV4_ROUTE Notifies on IPv4 route arrival/removal/change RTNLGRP_IPV6_ROUTE Notifies on IPv6 route arrival/removal/change .Ed .Sh NEXTHOPS All nexthop/nexthop group configuration messages share the common header: .Bd -literal struct nhmsg { unsigned char nh_family; /* transport family */ unsigned char nh_scope; /* ignored on RX, filled by kernel */ unsigned char nh_protocol; /* Routing protocol that installed nh */ unsigned char resvd; unsigned int nh_flags; /* RTNH_F_* flags from route.h */ }; .Ed The .Va nh_family specificies the gateway address family. It can be different from route address family for IPv4 routes with IPv6 nexthops. The .Va nh_protocol is similar to .Va rtm_protocol field, which designates originator application identity. .Pp The following messages are supported: .Ss RTM_NEWNEXTHOP Creates a new nexthop or nexthop group. .Ss RTM_DELNEXTHOP Deletes nexthop or nexthhop group. The required object is specified by the .Dv RTA_NH_ID attribute. .Ss RTM_GETNEXTHOP Fetches a single nexthop or all nexthops/nexthop groups, depending on the .Dv NLM_F_DUMP flag. The following filters are recognised by the kernel: .Pp .Bd -literal -offset indent -compact RTA_NH_ID nexthop or nexthtop group id NHA_GROUPS match only nexthtop groups .Ed .Ss TLVs .Bl -tag -width indent .It Dv RTA_NH_ID (uint32_t) Nexthhop index used to identify particular nexthop or nexthop group. Should be provided by userland at the nexthtop creation time. .It Dv NHA_GROUP This attribute designates the nexthtop group and contains all of its nexthtops and their relative weights. The attribute constists of a list of .Va nexthop_grp structures: .Bd -literal struct nexthop_grp { uint32_t id; /* nexhop userland index */ uint8_t weight; /* weight of this nexthop */ uint8_t resvd1; uint16_t resvd2; }; .Ed .It Dv NHA_GROUP_TYPE (uint16_t) Nexthtop group type, set to one of the following types: .Bd -literal -offset indent -compact NEXTHOP_GRP_TYPE_MPATH default multipath group .Ed .It Dv NHA_BLACKHOLE (flag) Marks the nexthtop as blackhole. .It Dv NHA_OIF (uint32_t) Transmit interface index of the nexthtop. .It Dv NHA_GATEWAY (binary) IPv4/IPv6 gateway address .It Dv NHA_GROUPS (flag) Matches nexthtop groups during dump. .El .Ss Groups The following groups are defined: .Bd -literal -offset indent -compact RTNLGRP_NEXTHOP Notifies on nexthop/groups arrival/removal/change .Ed .Sh INTERFACES All interface configuration messages share the common header: .Bd -literal struct ifinfomsg { unsigned char ifi_family; /* not used, set to 0 */ unsigned char __ifi_pad; unsigned short ifi_type; /* ARPHRD_* */ int ifi_index; /* Inteface index */ unsigned ifi_flags; /* IFF_* flags */ unsigned ifi_change; /* IFF_* change mask */ }; .Ed .Ss RTM_NEWLINK Creates a new interface. The only mandatory TLV is .Dv IFLA_IFNAME . .Ss RTM_DELLINK Deletes the interface specified by .Dv IFLA_IFNAME . .Ss RTM_GETLINK Fetches a single interface or all interfaces in the current VNET, depending on the .Dv NLM_F_DUMP flag. Each interface is reported as a .Dv RTM_NEWLINK message. The following filters are recognised by the kernel: .Pp .Bd -literal -offset indent -compact ifi_index interface index IFLA_IFNAME interface name IFLA_ALT_IFNAME interface name .Ed .Ss TLVs .Bl -tag -width indent .It Dv IFLA_ADDRESS (binary) Llink-level interface address (MAC). .It Dv IFLA_BROADCAST (binary) (readonly) Link-level broadcast address. .It Dv IFLA_IFNAME (string) New interface name. .It Dv IFLA_IFALIAS (string) Interface description. .It Dv IFLA_LINK (uint32_t) (readonly) Interface index. .It Dv IFLA_MASTER (uint32_t) Parent interface index. .It Dv IFLA_LINKINFO (nested) Interface type-specific attributes: .Bd -literal -offset indent -compact IFLA_INFO_KIND (string) interface type ("vlan") IFLA_INFO_DATA (nested) custom attributes .Ed The following types and attributes are supported: .Bl -tag -width indent .It Dv vlan .Bd -literal -offset indent -compact IFLA_VLAN_ID (uint16_t) 802.1Q vlan id IFLA_VLAN_PROTOCOL (uint16_t) Protocol: ETHERTYPE_VLAN or ETHERTYPE_QINQ .Ed .El .It Dv IFLA_OPERSTATE (uint8_t) Interface operational state per RFC 2863. Can be one of the following: .Bd -literal -offset indent -compact IF_OPER_UNKNOWN status can not be determined IF_OPER_NOTPRESENT some (hardware) component not present IF_OPER_DOWN down IF_OPER_LOWERLAYERDOWN some lower-level interface is down IF_OPER_TESTING in some test mode IF_OPER_DORMANT "up" but waiting for some condition (802.1X) IF_OPER_UP ready to pass packets .Ed .It Dv IFLA_STATS64 (readonly) Consists of the following 64-bit counters structure: .Bd -literal struct rtnl_link_stats64 { uint64_t rx_packets; /* total RX packets (IFCOUNTER_IPACKETS) */ uint64_t tx_packets; /* total TX packets (IFCOUNTER_OPACKETS) */ uint64_t rx_bytes; /* total RX bytes (IFCOUNTER_IBYTES) */ uint64_t tx_bytes; /* total TX bytes (IFCOUNTER_OBYTES) */ uint64_t rx_errors; /* RX errors (IFCOUNTER_IERRORS) */ uint64_t tx_errors; /* RX errors (IFCOUNTER_OERRORS) */ uint64_t rx_dropped; /* RX drop (no space in ring/no bufs) (IFCOUNTER_IQDROPS) */ uint64_t tx_dropped; /* TX drop (IFCOUNTER_OQDROPS) */ uint64_t multicast; /* RX multicast packets (IFCOUNTER_IMCASTS) */ uint64_t collisions; /* not supported */ uint64_t rx_length_errors; /* not supported */ uint64_t rx_over_errors; /* not supported */ uint64_t rx_crc_errors; /* not supported */ uint64_t rx_frame_errors; /* not supported */ uint64_t rx_fifo_errors; /* not supported */ uint64_t rx_missed_errors; /* not supported */ uint64_t tx_aborted_errors; /* not supported */ uint64_t tx_carrier_errors; /* not supported */ uint64_t tx_fifo_errors; /* not supported */ uint64_t tx_heartbeat_errors; /* not supported */ uint64_t tx_window_errors; /* not supported */ uint64_t rx_compressed; /* not supported */ uint64_t tx_compressed; /* not supported */ uint64_t rx_nohandler; /* dropped due to no proto handler (IFCOUNTER_NOPROTO) */ }; .Ed .El .Ss Groups The following groups are defined: .Bd -literal -offset indent -compact RTNLGRP_LINK Notifies on interface arrival/removal/change .Ed .Sh INTERFACE ADDRESSES All interface address configuration messages share the common header: .Bd -literal struct ifaddrmsg { uint8_t ifa_family; /* Address family */ uint8_t ifa_prefixlen; /* Prefix length */ uint8_t ifa_flags; /* Address-specific flags */ uint8_t ifa_scope; /* Address scope */ uint32_t ifa_index; /* Link ifindex */ }; .Ed .Pp The .Va ifa_family specifies the address family of the interface address. The .Va ifa_prefixlen specifies the prefix length if applicable for the address family. The .Va ifa_index specifies the interface index of the target interface. .Ss RTM_NEWADDR Not supported .Ss RTM_DELADDR Not supported .Ss RTM_GETADDR +Fetches interface addresses in the current VNET matching conditions. +Each address is reported as a +.Dv RTM_NEWADDR +message. +The following filters are recognised by the kernel: +.Pp +.Bd -literal -offset indent -compact +ifa_family required family or AF_UNSPEC +ifa_index matching interface index or 0 +.Ed .Ss TLVs .Bl -tag -width indent .It Dv IFA_ADDRESS (binary) masked interface address or destination address for p2p interfaces. .It Dv IFA_LOCAL -(binary) local interface address +(binary) local interface address. +Set for IPv4 and p2p addresses. +.It Dv IFA_LABEL +(string) interface name. .It Dv IFA_BROADCAST -(binary) broacast interface address +(binary) broacast interface address. .El .Ss Groups The following groups are defined: .Bd -literal -offset indent -compact RTNLGRP_IPV4_IFADDR Notifies on IPv4 ifaddr arrival/removal/change RTNLGRP_IPV6_IFADDR Notifies on IPv6 ifaddr arrival/removal/change .Ed .Sh NEIGHBORS All neighbor configuration messages share the common header: .Bd -literal struct ndmsg { uint8_t ndm_family; uint8_t ndm_pad1; uint16_t ndm_pad2; int32_t ndm_ifindex; uint16_t ndm_state; uint8_t ndm_flags; uint8_t ndm_type; }; .Ed .Pp The .Va ndm_family field specifies the address family (IPv4 or IPv6) of the neighbor. The .Va ndm_ifindex specifies the interface to operate on. The .Va ndm_state represents the entry state according to the neighbor model. The state can be one of the following: .Bd -literal -offset indent -compact NUD_INCOMPLETE No lladdr, address resolution in progress NUD_REACHABLE reachable & recently resolved NUD_STALE has lladdr but it's stale NUD_DELAY has lladdr, is stale, probes delayed NUD_PROBE has lladdr, is stale, probes sent NUD_FAILED unused .Ed .Pp The .Va ndm_flags field stores the options specific to this entry. Available flags: .Bd -literal -offset indent -compact NTF_SELF local station (LLE_IFADDR) NTF_PROXY proxy entry (LLE_PUB) NTF_STICKY permament entry (LLE_STATIC) NTF_ROUTER dst indicated itself as a router .Ed .Ss RTM_NEWNEIGH Creates new neighbor entry. The mandatory options are .Dv NDA_DST , .Dv NDA_LLADDR and .Dv NDA_IFINDEX . .Ss RTM_DELNEIGH Deletes the neighbor entry. The entry is specified by the combination of .Dv NDA_DST and .Dv NDA_IFINDEX . .Ss RTM_GETNEIGH Fetches a single neighbor or all neighbors in the current VNET, depending on the .Dv NLM_F_DUMP flag. Each entry is reported as .Dv RTM_NEWNEIGH message. The following filters are recognised by the kernel: .Pp .Bd -literal -offset indent -compact ndm_family required family or AF_UNSPEC ndm_ifindex target ifindex NDA_IFINDEX target ifindex .Ed .Ss TLVs .Bl -tag -width indent .It Dv NDA_DST (binary) neighbor IPv4/IPv6 address. .It Dv NDA_LLADDR (binary) neighbor link-level address. .It Dv NDA_IFINDEX (uint32_t) interface index. .It Dv NDA_FLAGS_EXT (uint32_t) extended version of .Va ndm_flags . .El .Ss Groups The following groups are defined: .Bd -literal -offset indent -compact RTNLGRP_NEIGH Notifies on ARP/NDP neighbor arrival/removal/change .Ed .Sh SEE ALSO .Xr netlink 4 , .Xr route 4 .Sh HISTORY The .Dv NETLINK_ROUTE protocol family appeared in .Fx 14.0 . .Sh AUTHORS The netlink was implementated by .An -nosplit .An Alexander Chernikov Aq Mt melifaro@FreeBSD.org . It was derived from the Google Summer of Code 2021 project by .An Ng Peng Nam Sean . diff --git a/sys/netlink/route/iface.c b/sys/netlink/route/iface.c index 81ae5bc8090f..f4936bb2c35b 100644 --- a/sys/netlink/route/iface.c +++ b/sys/netlink/route/iface.c @@ -1,1052 +1,1103 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2022 Alexander V. Chernikov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* scope deembedding */ #define DEBUG_MOD_NAME nl_iface #define DEBUG_MAX_LEVEL LOG_DEBUG3 #include _DECLARE_DEBUG(LOG_DEBUG); struct netlink_walkargs { struct nl_writer *nw; struct nlmsghdr hdr; struct nlpcb *so; uint32_t fibnum; int family; int error; int count; int dumped; }; static eventhandler_tag ifdetach_event, ifattach_event, iflink_event, ifaddr_event; static SLIST_HEAD(, nl_cloner) nl_cloners = SLIST_HEAD_INITIALIZER(nl_cloners); static struct sx rtnl_cloner_lock; SX_SYSINIT(rtnl_cloner_lock, &rtnl_cloner_lock, "rtnl cloner lock"); static struct nl_cloner *rtnl_iface_find_cloner_locked(const char *name); /* * RTM_GETLINK request * sendto(3, {{len=32, type=RTM_GETLINK, flags=NLM_F_REQUEST|NLM_F_DUMP, seq=1641940952, pid=0}, * {ifi_family=AF_INET, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}}, 32, 0, NULL, 0) = 32 * * Reply: * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_ETHER, ifi_index=if_nametoindex("enp0s31f6"), ifi_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_MULTICAST|IFF_LOWER_UP, ifi_change=0}, {{nla_len=10, nla_type=IFLA_ADDRESS}, "\xfe\x54\x00\x52\x3e\x90"} [ {{nla_len=14, nla_type=IFLA_IFNAME}, "enp0s31f6"}, {{nla_len=8, nla_type=IFLA_TXQLEN}, 1000}, {{nla_len=5, nla_type=IFLA_OPERSTATE}, 6}, {{nla_len=5, nla_type=IFLA_LINKMODE}, 0}, {{nla_len=8, nla_type=IFLA_MTU}, 1500}, {{nla_len=8, nla_type=IFLA_MIN_MTU}, 68}, {{nla_len=8, nla_type=IFLA_MAX_MTU}, 9000}, {{nla_len=8, nla_type=IFLA_GROUP}, 0}, {{nla_len=8, nla_type=IFLA_PROMISCUITY}, 0}, {{nla_len=8, nla_type=IFLA_NUM_TX_QUEUES}, 1}, {{nla_len=8, nla_type=IFLA_GSO_MAX_SEGS}, 65535}, {{nla_len=8, nla_type=IFLA_GSO_MAX_SIZE}, 65536}, {{nla_len=8, nla_type=IFLA_NUM_RX_QUEUES}, 1}, {{nla_len=5, nla_type=IFLA_CARRIER}, 1}, {{nla_len=13, nla_type=IFLA_QDISC}, "fq_codel"}, {{nla_len=8, nla_type=IFLA_CARRIER_CHANGES}, 2}, {{nla_len=5, nla_type=IFLA_PROTO_DOWN}, 0}, {{nla_len=8, nla_type=IFLA_CARRIER_UP_COUNT}, 1}, {{nla_len=8, nla_type=IFLA_CARRIER_DOWN_COUNT}, 1}, */ struct if_state { uint8_t ifla_operstate; uint8_t ifla_carrier; }; static void get_operstate_ether(struct ifnet *ifp, struct if_state *pstate) { struct ifmediareq ifmr = {}; int error; error = (*ifp->if_ioctl)(ifp, SIOCGIFMEDIA, (void *)&ifmr); if (error != 0) { NL_LOG(LOG_DEBUG, "error calling SIOCGIFMEDIA on %s: %d", if_name(ifp), error); return; } switch (IFM_TYPE(ifmr.ifm_active)) { case IFM_ETHER: if (ifmr.ifm_status & IFM_ACTIVE) { pstate->ifla_carrier = 1; if (ifp->if_flags & IFF_MONITOR) pstate->ifla_operstate = IF_OPER_DORMANT; else pstate->ifla_operstate = IF_OPER_UP; } else pstate->ifla_operstate = IF_OPER_DOWN; } } static bool get_stats(struct nl_writer *nw, struct ifnet *ifp) { struct rtnl_link_stats64 *stats; int nla_len = sizeof(struct nlattr) + sizeof(*stats); struct nlattr *nla = nlmsg_reserve_data(nw, nla_len, struct nlattr); if (nla == NULL) return (false); nla->nla_type = IFLA_STATS64; nla->nla_len = nla_len; stats = (struct rtnl_link_stats64 *)(nla + 1); stats->rx_packets = ifp->if_get_counter(ifp, IFCOUNTER_IPACKETS); stats->tx_packets = ifp->if_get_counter(ifp, IFCOUNTER_OPACKETS); stats->rx_bytes = ifp->if_get_counter(ifp, IFCOUNTER_IBYTES); stats->tx_bytes = ifp->if_get_counter(ifp, IFCOUNTER_OBYTES); stats->rx_errors = ifp->if_get_counter(ifp, IFCOUNTER_IERRORS); stats->tx_errors = ifp->if_get_counter(ifp, IFCOUNTER_OERRORS); stats->rx_dropped = ifp->if_get_counter(ifp, IFCOUNTER_IQDROPS); stats->tx_dropped = ifp->if_get_counter(ifp, IFCOUNTER_OQDROPS); stats->multicast = ifp->if_get_counter(ifp, IFCOUNTER_IMCASTS); stats->rx_nohandler = ifp->if_get_counter(ifp, IFCOUNTER_NOPROTO); return (true); } static void get_operstate(struct ifnet *ifp, struct if_state *pstate) { pstate->ifla_operstate = IF_OPER_UNKNOWN; pstate->ifla_carrier = 0; /* no carrier */ switch (ifp->if_type) { case IFT_ETHER: get_operstate_ether(ifp, pstate); break; case IFT_LOOP: if (ifp->if_flags & IFF_UP) { pstate->ifla_operstate = IF_OPER_UP; pstate->ifla_carrier = 1; } else pstate->ifla_operstate = IF_OPER_DOWN; break; } } static unsigned ifp_flags_to_netlink(const struct ifnet *ifp) { return (ifp->if_flags | ifp->if_drv_flags); } #define LLADDR_CONST(s) ((const void *)((s)->sdl_data + (s)->sdl_nlen)) static bool dump_sa(struct nl_writer *nw, int attr, const struct sockaddr *sa) { uint32_t addr_len = 0; const void *addr_data = NULL; #ifdef INET6 struct in6_addr addr6; #endif if (sa == NULL) return (true); switch (sa->sa_family) { #ifdef INET case AF_INET: addr_len = sizeof(struct in_addr); addr_data = &((const struct sockaddr_in *)sa)->sin_addr; break; #endif #ifdef INET6 case AF_INET6: in6_splitscope(&((const struct sockaddr_in6 *)sa)->sin6_addr, &addr6, &addr_len); addr_len = sizeof(struct in6_addr); addr_data = &addr6; break; #endif case AF_LINK: addr_len = ((const struct sockaddr_dl *)sa)->sdl_alen; addr_data = LLADDR_CONST((const struct sockaddr_dl *)sa); break; default: NL_LOG(LOG_DEBUG, "unsupported family: %d, skipping", sa->sa_family); return (true); } return (nlattr_add(nw, attr, addr_len, addr_data)); } /* * Dumps interface state, properties and metrics. * @nw: message writer * @ifp: target interface * @hdr: template header * @if_flags_mask: changed if_[drv]_flags bitmask * * This function is called without epoch and MAY sleep. */ static bool dump_iface(struct nl_writer *nw, struct ifnet *ifp, const struct nlmsghdr *hdr, int if_flags_mask) { struct ifinfomsg *ifinfo; NL_LOG(LOG_DEBUG3, "dumping interface %s data", if_name(ifp)); if (!nlmsg_reply(nw, hdr, sizeof(struct ifinfomsg))) goto enomem; ifinfo = nlmsg_reserve_object(nw, struct ifinfomsg); ifinfo->ifi_family = AF_UNSPEC; ifinfo->__ifi_pad = 0; ifinfo->ifi_type = ifp->if_type; ifinfo->ifi_index = ifp->if_index; ifinfo->ifi_flags = ifp_flags_to_netlink(ifp); ifinfo->ifi_change = if_flags_mask; struct if_state ifs = {}; get_operstate(ifp, &ifs); if (ifs.ifla_operstate == IF_OPER_UP) ifinfo->ifi_flags |= IFF_LOWER_UP; nlattr_add_string(nw, IFLA_IFNAME, if_name(ifp)); nlattr_add_u8(nw, IFLA_OPERSTATE, ifs.ifla_operstate); nlattr_add_u8(nw, IFLA_CARRIER, ifs.ifla_carrier); /* nlattr_add_u8(nw, IFLA_PROTO_DOWN, val); nlattr_add_u8(nw, IFLA_LINKMODE, val); */ if ((ifp->if_addr != NULL)) { dump_sa(nw, IFLA_ADDRESS, ifp->if_addr->ifa_addr); } if ((ifp->if_broadcastaddr != NULL)) { nlattr_add(nw, IFLA_BROADCAST, ifp->if_addrlen, ifp->if_broadcastaddr); } nlattr_add_u32(nw, IFLA_MTU, ifp->if_mtu); /* nlattr_add_u32(nw, IFLA_MIN_MTU, 60); nlattr_add_u32(nw, IFLA_MAX_MTU, 9000); nlattr_add_u32(nw, IFLA_GROUP, 0); */ if (ifp->if_description != NULL) nlattr_add_string(nw, IFLA_IFALIAS, ifp->if_description); get_stats(nw, ifp); uint32_t val = (ifp->if_flags & IFF_PROMISC) != 0; nlattr_add_u32(nw, IFLA_PROMISCUITY, val); sx_slock(&rtnl_cloner_lock); struct nl_cloner *cloner = rtnl_iface_find_cloner_locked(ifp->if_dname); if (cloner != NULL && cloner->dump_f != NULL) { /* Ignore any dump error */ cloner->dump_f(ifp, nw); } sx_sunlock(&rtnl_cloner_lock); if (nlmsg_end(nw)) return (true); enomem: NL_LOG(LOG_DEBUG, "unable to dump interface %s state (ENOMEM)", if_name(ifp)); nlmsg_abort(nw); return (false); } static bool check_ifmsg(void *hdr, struct nl_pstate *npt) { struct ifinfomsg *ifm = hdr; if (ifm->__ifi_pad != 0 || ifm->ifi_type != 0 || ifm->ifi_flags != 0 || ifm->ifi_change != 0) { nlmsg_report_err_msg(npt, "strict checking: non-zero values in ifinfomsg header"); return (false); } return (true); } #define _IN(_field) offsetof(struct ifinfomsg, _field) #define _OUT(_field) offsetof(struct nl_parsed_link, _field) static const struct nlfield_parser nlf_p_if[] = { { .off_in = _IN(ifi_type), .off_out = _OUT(ifi_type), .cb = nlf_get_u16 }, { .off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = nlf_get_u32 }, { .off_in = _IN(ifi_flags), .off_out = _OUT(ifi_flags), .cb = nlf_get_u32 }, { .off_in = _IN(ifi_change), .off_out = _OUT(ifi_change), .cb = nlf_get_u32 }, }; static const struct nlattr_parser nla_p_linfo[] = { { .type = IFLA_INFO_KIND, .off = _OUT(ifla_cloner), .cb = nlattr_get_stringn }, { .type = IFLA_INFO_DATA, .off = _OUT(ifla_idata), .cb = nlattr_get_nla }, }; NL_DECLARE_ATTR_PARSER(linfo_parser, nla_p_linfo); static const struct nlattr_parser nla_p_if[] = { { .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = nlattr_get_string }, { .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = nlattr_get_uint32 }, { .type = IFLA_LINK, .off = _OUT(ifi_index), .cb = nlattr_get_uint32 }, { .type = IFLA_LINKINFO, .arg = &linfo_parser, .cb = nlattr_get_nested }, { .type = IFLA_IFALIAS, .off = _OUT(ifla_ifalias), .cb = nlattr_get_string }, { .type = IFLA_GROUP, .off = _OUT(ifla_group), .cb = nlattr_get_string }, { .type = IFLA_ALT_IFNAME, .off = _OUT(ifla_ifname), .cb = nlattr_get_string }, }; #undef _IN #undef _OUT NL_DECLARE_STRICT_PARSER(ifmsg_parser, struct ifinfomsg, check_ifmsg, nlf_p_if, nla_p_if); static bool match_iface(struct nl_parsed_link *attrs, struct ifnet *ifp) { if (attrs->ifi_index != 0 && attrs->ifi_index != ifp->if_index) return (false); if (attrs->ifi_type != 0 && attrs->ifi_index != ifp->if_type) return (false); if (attrs->ifla_ifname != NULL && strcmp(attrs->ifla_ifname, if_name(ifp))) return (false); /* TODO: add group match */ return (true); } /* * {nlmsg_len=52, nlmsg_type=RTM_GETLINK, nlmsg_flags=NLM_F_REQUEST, nlmsg_seq=1662842818, nlmsg_pid=0}, * {ifi_family=AF_PACKET, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}, * [ * [{nla_len=10, nla_type=IFLA_IFNAME}, "vnet9"], * [{nla_len=8, nla_type=IFLA_EXT_MASK}, RTEXT_FILTER_VF] * ] */ static int rtnl_handle_getlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt) { struct epoch_tracker et; struct ifnet *ifp; int error = 0; struct nl_parsed_link attrs = {}; error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs); if (error != 0) return (error); struct netlink_walkargs wa = { .so = nlp, .nw = npt->nw, .hdr.nlmsg_pid = hdr->nlmsg_pid, .hdr.nlmsg_seq = hdr->nlmsg_seq, .hdr.nlmsg_flags = hdr->nlmsg_flags, .hdr.nlmsg_type = NL_RTM_NEWLINK, }; /* Fast track for an interface w/ explicit name or index match */ if ((attrs.ifi_index != 0) || (attrs.ifla_ifname != NULL)) { if (attrs.ifi_index != 0) { NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching index %u", attrs.ifi_index); NET_EPOCH_ENTER(et); ifp = ifnet_byindex_ref(attrs.ifi_index); NET_EPOCH_EXIT(et); } else { NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching name %s", attrs.ifla_ifname); ifp = ifunit_ref(attrs.ifla_ifname); } if (ifp != NULL) { if (match_iface(&attrs, ifp)) { if (!dump_iface(wa.nw, ifp, &wa.hdr, 0)) error = ENOMEM; } else error = ENODEV; if_rele(ifp); } else error = ENODEV; return (error); } /* Always treat non-direct-match as a multipart message */ wa.hdr.nlmsg_flags |= NLM_F_MULTI; /* * Fetching some link properties require performing ioctl's that may be blocking. * Address it by saving referenced pointers of the matching links, * exiting from epoch and going through the list one-by-one. */ NL_LOG(LOG_DEBUG2, "Start dump"); struct ifnet **match_array; int offset = 0, base_count = 16; /* start with 128 bytes */ match_array = malloc(base_count * sizeof(void *), M_TEMP, M_NOWAIT); NLP_LOG(LOG_DEBUG3, nlp, "MATCHING: index=%u type=%d name=%s", attrs.ifi_index, attrs.ifi_type, attrs.ifla_ifname); NET_EPOCH_ENTER(et); CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { wa.count++; if (match_iface(&attrs, ifp)) { if (offset < base_count) { if (!if_try_ref(ifp)) continue; match_array[offset++] = ifp; continue; } /* Too many matches, need to reallocate */ struct ifnet **new_array; int sz = base_count * sizeof(void *); base_count *= 2; new_array = malloc(sz * 2, M_TEMP, M_NOWAIT); if (new_array == NULL) { error = ENOMEM; break; } memcpy(new_array, match_array, sz); free(match_array, M_TEMP); match_array = new_array; } } NET_EPOCH_EXIT(et); NL_LOG(LOG_DEBUG2, "Matched %d interface(s), dumping", offset); for (int i = 0; error == 0 && i < offset; i++) { if (!dump_iface(wa.nw, match_array[i], &wa.hdr, 0)) error = ENOMEM; } for (int i = 0; i < offset; i++) if_rele(match_array[i]); free(match_array, M_TEMP); NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped); if (!nlmsg_end_dump(wa.nw, error, &wa.hdr)) { NL_LOG(LOG_DEBUG, "Unable to finalize the dump"); return (ENOMEM); } return (error); } /* * sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[ * {nlmsg_len=60, nlmsg_type=RTM_NEWLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, nlmsg_seq=1662715618, nlmsg_pid=0}, * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}, * {nla_len=11, nla_type=IFLA_IFNAME}, "dummy0"], * [ * {nla_len=16, nla_type=IFLA_LINKINFO}, * [ * {nla_len=9, nla_type=IFLA_INFO_KIND}, "dummy"... * ] * ] */ static int rtnl_handle_dellink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt) { struct epoch_tracker et; struct ifnet *ifp; int error; struct nl_parsed_link attrs = {}; error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs); if (error != 0) return (error); NET_EPOCH_ENTER(et); ifp = ifnet_byindex_ref(attrs.ifi_index); NET_EPOCH_EXIT(et); if (ifp == NULL) { NLP_LOG(LOG_DEBUG, nlp, "unable to find interface %u", attrs.ifi_index); return (ENOENT); } NLP_LOG(LOG_DEBUG3, nlp, "mapped ifindex %u to %s", attrs.ifi_index, if_name(ifp)); sx_xlock(&ifnet_detach_sxlock); error = if_clone_destroy(if_name(ifp)); sx_xunlock(&ifnet_detach_sxlock); NLP_LOG(LOG_DEBUG2, nlp, "deleting interface %s returned %d", if_name(ifp), error); if_rele(ifp); return (error); } /* * New link: * type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1668185590, pid=0}, * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0} * [ * {{nla_len=8, nla_type=IFLA_MTU}, 123}, * {{nla_len=10, nla_type=IFLA_IFNAME}, "vlan1"}, * {{nla_len=24, nla_type=IFLA_LINKINFO}, * [ * {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...}, * {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x7b\x00\x00\x00"}]}]} * * Update link: * type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1668185923, pid=0}, * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=if_nametoindex("lo"), ifi_flags=0, ifi_change=0}, * {{nla_len=8, nla_type=IFLA_MTU}, 123}} * * * Check command availability: * type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK, seq=0, pid=0}, * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0} */ static int create_link(struct nlmsghdr *hdr, struct nl_parsed_link *lattrs, struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt) { if (lattrs->ifla_ifname == NULL || strlen(lattrs->ifla_ifname) == 0) { NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_IFNAME attribute"); return (EINVAL); } if (lattrs->ifla_cloner == NULL || strlen(lattrs->ifla_cloner) == 0) { NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_INFO_KIND attribute"); return (EINVAL); } bool found = false; int error = 0; sx_slock(&rtnl_cloner_lock); struct nl_cloner *cloner = rtnl_iface_find_cloner_locked(lattrs->ifla_cloner); if (cloner != NULL) { found = true; error = cloner->create_f(lattrs, bm, nlp, npt); } sx_sunlock(&rtnl_cloner_lock); if (!found) error = generic_cloner.create_f(lattrs, bm, nlp, npt); return (error); } static int modify_link(struct nlmsghdr *hdr, struct nl_parsed_link *lattrs, struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt) { struct ifnet *ifp = NULL; struct epoch_tracker et; if (lattrs->ifi_index == 0 && lattrs->ifla_ifname == NULL) { /* * Applications like ip(8) verify RTM_NEWLINK command * existence by calling it with empty arguments. Always * return "innocent" error in that case. */ NLMSG_REPORT_ERR_MSG(npt, "empty ifi_index field"); return (EPERM); } if (lattrs->ifi_index != 0) { NET_EPOCH_ENTER(et); ifp = ifnet_byindex_ref(lattrs->ifi_index); NET_EPOCH_EXIT(et); if (ifp == NULL) { NLMSG_REPORT_ERR_MSG(npt, "unable to find interface #%u", lattrs->ifi_index); return (ENOENT); } } if (ifp == NULL && lattrs->ifla_ifname != NULL) { ifp = ifunit_ref(lattrs->ifla_ifname); if (ifp == NULL) { NLMSG_REPORT_ERR_MSG(npt, "unable to find interface %s", lattrs->ifla_ifname); return (ENOENT); } } MPASS(ifp != NULL); /* * There can be multiple kinds of interfaces: * 1) cloned, with additional options * 2) cloned, but w/o additional options * 3) non-cloned (e.g. "physical). * * Thus, try to find cloner-specific callback and fallback to the * "default" handler if not found. */ bool found = false; int error = 0; sx_slock(&rtnl_cloner_lock); struct nl_cloner *cloner = rtnl_iface_find_cloner_locked(ifp->if_dname); if (cloner != NULL) { found = true; error = cloner->modify_f(ifp, lattrs, bm, nlp, npt); } sx_sunlock(&rtnl_cloner_lock); if (!found) error = generic_cloner.modify_f(ifp, lattrs, bm, nlp, npt); if_rele(ifp); return (error); } static int rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt) { struct nlattr_bmask bm; int error; struct nl_parsed_link attrs = {}; error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs); if (error != 0) return (error); nl_get_attrs_bmask_nlmsg(hdr, &ifmsg_parser, &bm); if (hdr->nlmsg_flags & NLM_F_CREATE) return (create_link(hdr, &attrs, &bm, nlp, npt)); else return (modify_link(hdr, &attrs, &bm, nlp, npt)); } +struct nl_parsed_ifa { + uint8_t ifa_family; + uint8_t ifa_prefixlen; + uint8_t ifa_scope; + uint32_t ifa_index; + uint32_t ifa_flags; + struct sockaddr *ifa_address; + struct sockaddr *ifa_local; +}; + +#define _IN(_field) offsetof(struct ifaddrmsg, _field) +#define _OUT(_field) offsetof(struct nl_parsed_ifa, _field) +static const struct nlfield_parser nlf_p_ifa[] = { + { .off_in = _IN(ifa_family), .off_out = _OUT(ifa_family), .cb = nlf_get_u8 }, + { .off_in = _IN(ifa_prefixlen), .off_out = _OUT(ifa_prefixlen), .cb = nlf_get_u8 }, + { .off_in = _IN(ifa_scope), .off_out = _OUT(ifa_scope), .cb = nlf_get_u8 }, + { .off_in = _IN(ifa_flags), .off_out = _OUT(ifa_flags), .cb = nlf_get_u8_u32 }, + { .off_in = _IN(ifa_index), .off_out = _OUT(ifa_index), .cb = nlf_get_u32 }, +}; + +static const struct nlattr_parser nla_p_ifa[] = { + { .type = IFA_ADDRESS, .off = _OUT(ifa_address), .cb = nlattr_get_ip }, + { .type = IFA_LOCAL, .off = _OUT(ifa_local), .cb = nlattr_get_ip }, + { .type = IFA_FLAGS, .off = _OUT(ifa_flags), .cb = nlattr_get_uint32 }, +}; +#undef _IN +#undef _OUT +NL_DECLARE_PARSER(ifaddrmsg_parser, struct ifaddrmsg, nlf_p_ifa, nla_p_ifa); + + /* {ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")}, [ {{nla_len=8, nla_type=IFA_ADDRESS}, inet_addr("127.0.0.1")}, {{nla_len=8, nla_type=IFA_LOCAL}, inet_addr("127.0.0.1")}, {{nla_len=7, nla_type=IFA_LABEL}, "lo"}, {{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT}, {{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=3619, tstamp=3619}}]}, --- {{len=72, type=RTM_NEWADDR, flags=NLM_F_MULTI, seq=1642191126, pid=566735}, {ifa_family=AF_INET6, ifa_prefixlen=96, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_UNIVERSE, ifa_index=if_nametoindex("virbr0")}, [ {{nla_len=20, nla_type=IFA_ADDRESS}, inet_pton(AF_INET6, "2a01:4f8:13a:70c:ffff::1")}, {{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=4283, tstamp=4283}}, {{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT}]}, */ static uint8_t ifa_get_scope(const struct ifaddr *ifa) { const struct sockaddr *sa; uint8_t addr_scope = RT_SCOPE_UNIVERSE; sa = ifa->ifa_addr; switch (sa->sa_family) { #ifdef INET case AF_INET: { struct in_addr addr; addr = ((const struct sockaddr_in *)sa)->sin_addr; if (IN_LOOPBACK(addr.s_addr)) addr_scope = RT_SCOPE_HOST; else if (IN_LINKLOCAL(addr.s_addr)) addr_scope = RT_SCOPE_LINK; break; } #endif #ifdef INET6 case AF_INET6: { const struct in6_addr *addr; addr = &((const struct sockaddr_in6 *)sa)->sin6_addr; if (IN6_IS_ADDR_LOOPBACK(addr)) addr_scope = RT_SCOPE_HOST; else if (IN6_IS_ADDR_LINKLOCAL(addr)) addr_scope = RT_SCOPE_LINK; break; } #endif } return (addr_scope); } static uint8_t inet6_get_plen(const struct in6_addr *addr) { return (bitcount32(addr->s6_addr32[0]) + bitcount32(addr->s6_addr32[1]) + bitcount32(addr->s6_addr32[2]) + bitcount32(addr->s6_addr32[3])); } static uint8_t get_sa_plen(const struct sockaddr *sa) { #ifdef INET const struct in_addr *paddr; #endif #ifdef INET6 const struct in6_addr *paddr6; #endif switch (sa->sa_family) { #ifdef INET case AF_INET: if (sa == NULL) return (32); paddr = &(((const struct sockaddr_in *)sa)->sin_addr); return bitcount32(paddr->s_addr);; #endif #ifdef INET6 case AF_INET6: if (sa == NULL) return (128); paddr6 = &(((const struct sockaddr_in6 *)sa)->sin6_addr); return inet6_get_plen(paddr6); #endif } return (0); } /* * {'attrs': [('IFA_ADDRESS', '12.0.0.1'), ('IFA_LOCAL', '12.0.0.1'), ('IFA_LABEL', 'eth10'), ('IFA_FLAGS', 128), ('IFA_CACHEINFO', {'ifa_preferred': 4294967295, 'ifa_valid': 4294967295, 'cstamp': 63745746, 'tstamp': 63745746})], */ static bool dump_iface_addr(struct nl_writer *nw, struct ifnet *ifp, struct ifaddr *ifa, const struct nlmsghdr *hdr) { struct ifaddrmsg *ifamsg; struct sockaddr *sa = ifa->ifa_addr; NL_LOG(LOG_DEBUG3, "dumping ifa %p type %s(%d) for interface %s", ifa, rib_print_family(sa->sa_family), sa->sa_family, if_name(ifp)); if (!nlmsg_reply(nw, hdr, sizeof(struct ifaddrmsg))) goto enomem; ifamsg = nlmsg_reserve_object(nw, struct ifaddrmsg); ifamsg->ifa_family = sa->sa_family; ifamsg->ifa_prefixlen = get_sa_plen(ifa->ifa_netmask); ifamsg->ifa_flags = 0; // ifa_flags is useless ifamsg->ifa_scope = ifa_get_scope(ifa); ifamsg->ifa_index = ifp->if_index; if (ifp->if_flags & IFF_POINTOPOINT) { dump_sa(nw, IFA_ADDRESS, ifa->ifa_dstaddr); dump_sa(nw, IFA_LOCAL, sa); } else { dump_sa(nw, IFA_ADDRESS, sa); #ifdef INET /* * In most cases, IFA_ADDRESS == IFA_LOCAL * Skip IFA_LOCAL for anything except INET */ if (sa->sa_family == AF_INET) dump_sa(nw, IFA_LOCAL, sa); #endif } if (ifp->if_flags & IFF_BROADCAST) dump_sa(nw, IFA_BROADCAST, ifa->ifa_broadaddr); nlattr_add_string(nw, IFA_LABEL, if_name(ifp)); uint32_t val = 0; // ifa->ifa_flags; nlattr_add_u32(nw, IFA_FLAGS, val); if (nlmsg_end(nw)) return (true); enomem: NL_LOG(LOG_DEBUG, "Failed to dump ifa type %s(%d) for interface %s", rib_print_family(sa->sa_family), sa->sa_family, if_name(ifp)); nlmsg_abort(nw); return (false); } static int -rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt) +dump_iface_addrs(struct netlink_walkargs *wa, struct ifnet *ifp) { struct ifaddr *ifa; + + CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + if (wa->family != 0 && wa->family != ifa->ifa_addr->sa_family) + continue; + if (ifa->ifa_addr->sa_family == AF_LINK) + continue; + wa->count++; + if (!dump_iface_addr(wa->nw, ifp, ifa, &wa->hdr)) + return (ENOMEM); + wa->dumped++; + } + + return (0); +} + +static int +rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt) +{ struct ifnet *ifp; int error = 0; + struct nl_parsed_ifa attrs = {}; + error = nl_parse_nlmsg(hdr, &ifaddrmsg_parser, npt, &attrs); + if (error != 0) + return (error); + struct netlink_walkargs wa = { .so = nlp, .nw = npt->nw, + .family = attrs.ifa_family, .hdr.nlmsg_pid = hdr->nlmsg_pid, .hdr.nlmsg_seq = hdr->nlmsg_seq, .hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI, .hdr.nlmsg_type = NL_RTM_NEWADDR, }; NL_LOG(LOG_DEBUG2, "Start dump"); - CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { - CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { - if (wa.family != 0 && wa.family != ifa->ifa_addr->sa_family) - continue; - if (ifa->ifa_addr->sa_family == AF_LINK) - continue; - wa.count++; - if (!dump_iface_addr(wa.nw, ifp, ifa, &wa.hdr)) { - error = ENOMEM; - break; - } - wa.dumped++; - } - if (error != 0) - break; - } + if (attrs.ifa_index != 0) { + ifp = ifnet_byindex(attrs.ifa_index); + if (ifp == NULL) + error = ENOENT; + else + error = dump_iface_addrs(&wa, ifp); + } else { + CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { + error = dump_iface_addrs(&wa, ifp); + if (error != 0) + break; + } + } NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped); if (!nlmsg_end_dump(wa.nw, error, &wa.hdr)) { NL_LOG(LOG_DEBUG, "Unable to finalize the dump"); return (ENOMEM); } return (error); } static void rtnl_handle_ifaddr(void *arg __unused, struct ifaddr *ifa, int cmd) { struct nlmsghdr hdr = {}; struct nl_writer nw = {}; uint32_t group = 0; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: group = RTNLGRP_IPV4_IFADDR; break; #endif #ifdef INET6 case AF_INET6: group = RTNLGRP_IPV6_IFADDR; break; #endif default: NL_LOG(LOG_DEBUG2, "ifa notification for unknown AF: %d", ifa->ifa_addr->sa_family); return; } if (!nl_has_listeners(NETLINK_ROUTE, group)) return; if (!nlmsg_get_group_writer(&nw, NLMSG_LARGE, NETLINK_ROUTE, group)) { NL_LOG(LOG_DEBUG, "error allocating group writer"); return; } hdr.nlmsg_type = (cmd == RTM_DELETE) ? NL_RTM_DELADDR : NL_RTM_NEWADDR; dump_iface_addr(&nw, ifa->ifa_ifp, ifa, &hdr); nlmsg_flush(&nw); } static void rtnl_handle_ifevent(struct ifnet *ifp, int nlmsg_type, int if_flags_mask) { struct nlmsghdr hdr = { .nlmsg_type = nlmsg_type }; struct nl_writer nw = {}; if (!nl_has_listeners(NETLINK_ROUTE, RTNLGRP_LINK)) return; if (!nlmsg_get_group_writer(&nw, NLMSG_LARGE, NETLINK_ROUTE, RTNLGRP_LINK)) { NL_LOG(LOG_DEBUG, "error allocating mbuf"); return; } dump_iface(&nw, ifp, &hdr, if_flags_mask); nlmsg_flush(&nw); } static void rtnl_handle_ifattach(void *arg, struct ifnet *ifp) { NL_LOG(LOG_DEBUG2, "ifnet %s", if_name(ifp)); rtnl_handle_ifevent(ifp, NL_RTM_NEWLINK, 0); } static void rtnl_handle_ifdetach(void *arg, struct ifnet *ifp) { NL_LOG(LOG_DEBUG2, "ifnet %s", if_name(ifp)); rtnl_handle_ifevent(ifp, NL_RTM_DELLINK, 0); } static void rtnl_handle_iflink(void *arg, struct ifnet *ifp) { NL_LOG(LOG_DEBUG2, "ifnet %s", if_name(ifp)); rtnl_handle_ifevent(ifp, NL_RTM_NEWLINK, 0); } void rtnl_handle_ifnet_event(struct ifnet *ifp, int if_flags_mask) { NL_LOG(LOG_DEBUG2, "ifnet %s", if_name(ifp)); rtnl_handle_ifevent(ifp, NL_RTM_NEWLINK, if_flags_mask); } static const struct rtnl_cmd_handler cmd_handlers[] = { { .cmd = NL_RTM_GETLINK, .name = "RTM_GETLINK", .cb = &rtnl_handle_getlink, .flags = RTNL_F_NOEPOCH, }, { .cmd = NL_RTM_DELLINK, .name = "RTM_DELLINK", .cb = &rtnl_handle_dellink, .priv = PRIV_NET_IFDESTROY, .flags = RTNL_F_NOEPOCH, }, { .cmd = NL_RTM_NEWLINK, .name = "RTM_NEWLINK", .cb = &rtnl_handle_newlink, .priv = PRIV_NET_IFCREATE, .flags = RTNL_F_NOEPOCH, }, { .cmd = NL_RTM_GETADDR, .name = "RTM_GETADDR", .cb = &rtnl_handle_getaddr, }, { .cmd = NL_RTM_NEWADDR, .name = "RTM_NEWADDR", .cb = &rtnl_handle_getaddr, }, { .cmd = NL_RTM_DELADDR, .name = "RTM_DELADDR", .cb = &rtnl_handle_getaddr, }, }; -static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser }; +static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser, &ifaddrmsg_parser }; void rtnl_iface_add_cloner(struct nl_cloner *cloner) { sx_xlock(&rtnl_cloner_lock); SLIST_INSERT_HEAD(&nl_cloners, cloner, next); sx_xunlock(&rtnl_cloner_lock); } void rtnl_iface_del_cloner(struct nl_cloner *cloner) { sx_xlock(&rtnl_cloner_lock); SLIST_REMOVE(&nl_cloners, cloner, nl_cloner, next); sx_xunlock(&rtnl_cloner_lock); } static struct nl_cloner * rtnl_iface_find_cloner_locked(const char *name) { struct nl_cloner *cloner; SLIST_FOREACH(cloner, &nl_cloners, next) { if (!strcmp(name, cloner->name)) return (cloner); } return (NULL); } void rtnl_ifaces_init(void) { ifattach_event = EVENTHANDLER_REGISTER( ifnet_arrival_event, rtnl_handle_ifattach, NULL, EVENTHANDLER_PRI_ANY); ifdetach_event = EVENTHANDLER_REGISTER( ifnet_departure_event, rtnl_handle_ifdetach, NULL, EVENTHANDLER_PRI_ANY); ifaddr_event = EVENTHANDLER_REGISTER( rt_addrmsg, rtnl_handle_ifaddr, NULL, EVENTHANDLER_PRI_ANY); iflink_event = EVENTHANDLER_REGISTER( ifnet_link_event, rtnl_handle_iflink, NULL, EVENTHANDLER_PRI_ANY); NL_VERIFY_PARSERS(all_parsers); rtnl_iface_drivers_register(); rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers)); } void rtnl_ifaces_destroy(void) { EVENTHANDLER_DEREGISTER(ifnet_arrival_event, ifattach_event); EVENTHANDLER_DEREGISTER(ifnet_departure_event, ifdetach_event); EVENTHANDLER_DEREGISTER(rt_addrmsg, ifaddr_event); EVENTHANDLER_DEREGISTER(ifnet_link_event, iflink_event); } diff --git a/sys/netlink/route/ifaddrs.h b/sys/netlink/route/ifaddrs.h index e2013cb266d7..7ada8f22bf7b 100644 --- a/sys/netlink/route/ifaddrs.h +++ b/sys/netlink/route/ifaddrs.h @@ -1,90 +1,90 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2022 Alexander V. Chernikov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Interface address-related (RTM_ADDR) message header and attributes. */ #ifndef _NETLINK_ROUTE_IFADDRS_H_ #define _NETLINK_ROUTE_IFADDRS_H_ /* Base header for all of the relevant messages */ struct ifaddrmsg { uint8_t ifa_family; /* Address family */ uint8_t ifa_prefixlen; /* Prefix length */ uint8_t ifa_flags; /* Address-specific flags */ uint8_t ifa_scope; /* Address scope */ uint32_t ifa_index; /* Link ifindex */ }; #ifndef _KERNEL #define _NL_IFA_HDRLEN ((int)sizeof(struct ifaddrmsg)) #define IFA_RTA(_ifa) ((struct rtattr *)(NL_ITEM_DATA(_ifa, _NL_IFA_HDRLEN))) #define IFA_PAYLOAD(_hdr) NLMSG_PAYLOAD(_hdr, _NL_IFA_HDRLEN) #endif /* Defined attributes */ enum { IFA_UNSPEC, IFA_ADDRESS = 1, /* binary, prefix address (destination for p2p) */ IFA_LOCAL = 2, /* binary, interface address */ - IFA_LABEL = 3, /* not supported */ + IFA_LABEL = 3, /* string, interface name */ IFA_BROADCAST = 4, /* binary, broadcast ifa */ IFA_ANYCAST = 5, /* not supported */ IFA_CACHEINFO = 6, /* not supported */ IFA_MULTICAST = 7, /* not supported */ IFA_FLAGS = 8, /* not supported */ IFA_RT_PRIORITY = 9, /* not supported */ IFA_TARGET_NETNSID = 10, /* not supported */ __IFA_MAX, }; #define IFA_MAX (__IFA_MAX - 1) /* IFA_FLAGS attribute flags */ #define IFA_F_SECONDARY 0x0001 #define IFA_F_TEMPORARY IFA_F_SECONDARY #define IFA_F_NODAD 0x0002 #define IFA_F_OPTIMISTIC 0x0004 #define IFA_F_DADFAILED 0x0008 #define IFA_F_HOMEADDRESS 0x0010 #define IFA_F_DEPRECATED 0x0020 #define IFA_F_TENTATIVE 0x0040 #define IFA_F_PERMANENT 0x0080 #define IFA_F_MANAGETEMPADDR 0x0100 #define IFA_F_NOPREFIXROUTE 0x0200 #define IFA_F_MCAUTOJOIN 0x0400 #define IFA_F_STABLE_PRIVACY 0x0800 /* IFA_CACHEINFO value */ struct ifa_cacheinfo { uint32_t ifa_prefered; uint32_t ifa_valid; uint32_t cstamp; uint32_t tstamp; }; #endif diff --git a/tests/atf_python/sys/net/netlink.py b/tests/atf_python/sys/net/netlink.py index 046519ce0343..57c8582627cf 100644 --- a/tests/atf_python/sys/net/netlink.py +++ b/tests/atf_python/sys/net/netlink.py @@ -1,1495 +1,1567 @@ #!/usr/local/bin/python3 import os import socket import struct import sys import unittest from ctypes import c_int from ctypes import c_ubyte from ctypes import c_uint from ctypes import c_ushort from ctypes import sizeof from ctypes import Structure from enum import auto from enum import Enum from typing import Any from typing import Dict from typing import List from typing import NamedTuple def roundup2(val: int, num: int) -> int: if val % num: return (val | (num - 1)) + 1 else: return val def align4(val: int) -> int: return roundup2(val, 4) class SockaddrNl(Structure): _fields_ = [ ("nl_len", c_ubyte), ("nl_family", c_ubyte), ("nl_pad", c_ushort), ("nl_pid", c_uint), ("nl_groups", c_uint), ] class Nlmsghdr(Structure): _fields_ = [ ("nlmsg_len", c_uint), ("nlmsg_type", c_ushort), ("nlmsg_flags", c_ushort), ("nlmsg_seq", c_uint), ("nlmsg_pid", c_uint), ] +class Nlmsgdone(Structure): + _fields_ = [ + ("error", c_int), + ] + + class Nlmsgerr(Structure): _fields_ = [ ("error", c_int), ("msg", Nlmsghdr), ] class NlErrattrType(Enum): NLMSGERR_ATTR_UNUSED = 0 NLMSGERR_ATTR_MSG = auto() NLMSGERR_ATTR_OFFS = auto() NLMSGERR_ATTR_COOKIE = auto() NLMSGERR_ATTR_POLICY = auto() class RtattrType(Enum): RTA_UNSPEC = 0 RTA_DST = auto() RTA_SRC = auto() RTA_IIF = auto() RTA_OIF = auto() RTA_GATEWAY = auto() RTA_PRIORITY = auto() RTA_PREFSRC = auto() RTA_METRICS = auto() RTA_MULTIPATH = auto() RTA_PROTOINFO = auto() RTA_FLOW = auto() RTA_CACHEINFO = auto() RTA_SESSION = auto() RTA_MP_ALGO = auto() RTA_TABLE = auto() RTA_MARK = auto() RTA_MFC_STATS = auto() RTA_VIA = auto() RTA_NEWDST = auto() RTA_PREF = auto() RTA_ENCAP_TYPE = auto() RTA_ENCAP = auto() RTA_EXPIRES = auto() RTA_PAD = auto() RTA_UID = auto() RTA_TTL_PROPAGATE = auto() RTA_IP_PROTO = auto() RTA_SPORT = auto() RTA_DPORT = auto() RTA_NH_ID = auto() class NlMsgType(Enum): NLMSG_NOOP = 1 NLMSG_ERROR = 2 NLMSG_DONE = 3 NLMSG_OVERRUN = 4 class NlRtMsgType(Enum): RTM_NEWLINK = 16 RTM_DELLINK = 17 RTM_GETLINK = 18 RTM_SETLINK = 19 RTM_NEWADDR = 20 RTM_DELADDR = 21 RTM_GETADDR = 22 RTM_NEWROUTE = 24 RTM_DELROUTE = 25 RTM_GETROUTE = 26 RTM_NEWNEIGH = 28 RTM_DELNEIGH = 27 RTM_GETNEIGH = 28 RTM_NEWRULE = 32 RTM_DELRULE = 33 RTM_GETRULE = 34 RTM_NEWQDISC = 36 RTM_DELQDISC = 37 RTM_GETQDISC = 38 RTM_NEWTCLASS = 40 RTM_DELTCLASS = 41 RTM_GETTCLASS = 42 RTM_NEWTFILTER = 44 RTM_DELTFILTER = 45 RTM_GETTFILTER = 46 RTM_NEWACTION = 48 RTM_DELACTION = 49 RTM_GETACTION = 50 RTM_NEWPREFIX = 52 RTM_GETMULTICAST = 58 RTM_GETANYCAST = 62 RTM_NEWNEIGHTBL = 64 RTM_GETNEIGHTBL = 66 RTM_SETNEIGHTBL = 67 RTM_NEWNDUSEROPT = 68 RTM_NEWADDRLABEL = 72 RTM_DELADDRLABEL = 73 RTM_GETADDRLABEL = 74 RTM_GETDCB = 78 RTM_SETDCB = 79 RTM_NEWNETCONF = 80 RTM_GETNETCONF = 82 RTM_NEWMDB = 84 RTM_DELMDB = 85 RTM_GETMDB = 86 RTM_NEWNSID = 88 RTM_DELNSID = 89 RTM_GETNSID = 90 RTM_NEWSTATS = 92 RTM_GETSTATS = 94 class RtAttr(Structure): _fields_ = [ ("rta_len", c_ushort), ("rta_type", c_ushort), ] class RtMsgHdr(Structure): _fields_ = [ ("rtm_family", c_ubyte), ("rtm_dst_len", c_ubyte), ("rtm_src_len", c_ubyte), ("rtm_tos", c_ubyte), ("rtm_table", c_ubyte), ("rtm_protocol", c_ubyte), ("rtm_scope", c_ubyte), ("rtm_type", c_ubyte), ("rtm_flags", c_uint), ] class RtMsgFlags(Enum): RTM_F_NOTIFY = 0x100 RTM_F_CLONED = 0x200 RTM_F_EQUALIZE = 0x400 RTM_F_PREFIX = 0x800 RTM_F_LOOKUP_TABLE = 0x1000 RTM_F_FIB_MATCH = 0x2000 RTM_F_OFFLOAD = 0x4000 RTM_F_TRAP = 0x8000 RTM_F_OFFLOAD_FAILED = 0x20000000 class AddressFamilyLinux(Enum): AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 AF_NETLINK = 16 class AddressFamilyBsd(Enum): AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 AF_NETLINK = 38 class NlmBaseFlags(Enum): NLM_F_REQUEST = 0x01 NLM_F_MULTI = 0x02 NLM_F_ACK = 0x04 NLM_F_ECHO = 0x08 NLM_F_DUMP_INTR = 0x10 NLM_F_DUMP_FILTERED = 0x20 # XXX: in python3.8 it is possible to # class NlmGetFlags(Enum, NlmBaseFlags): class NlmGetFlags(Enum): NLM_F_ROOT = 0x100 NLM_F_MATCH = 0x200 NLM_F_ATOMIC = 0x400 class NlmNewFlags(Enum): NLM_F_REPLACE = 0x100 NLM_F_EXCL = 0x200 NLM_F_CREATE = 0x400 NLM_F_APPEND = 0x800 class NlmDeleteFlags(Enum): NLM_F_NONREC = 0x100 class NlmAckFlags(Enum): NLM_F_CAPPED = 0x100 NLM_F_ACK_TLVS = 0x200 class RtScope(Enum): RT_SCOPE_UNIVERSE = 0 RT_SCOPE_SITE = 200 RT_SCOPE_LINK = 253 RT_SCOPE_HOST = 254 RT_SCOPE_NOWHERE = 255 class RtType(Enum): RTN_UNSPEC = 0 RTN_UNICAST = auto() RTN_LOCAL = auto() RTN_BROADCAST = auto() RTN_ANYCAST = auto() RTN_MULTICAST = auto() RTN_BLACKHOLE = auto() RTN_UNREACHABLE = auto() RTN_PROHIBIT = auto() RTN_THROW = auto() RTN_NAT = auto() RTN_XRESOLVE = auto() class RtProto(Enum): RTPROT_UNSPEC = 0 RTPROT_REDIRECT = 1 RTPROT_KERNEL = 2 RTPROT_BOOT = 3 RTPROT_STATIC = 4 RTPROT_GATED = 8 RTPROT_RA = 9 RTPROT_MRT = 10 RTPROT_ZEBRA = 11 RTPROT_BIRD = 12 RTPROT_DNROUTED = 13 RTPROT_XORP = 14 RTPROT_NTK = 15 RTPROT_DHCP = 16 RTPROT_MROUTED = 17 RTPROT_KEEPALIVED = 18 RTPROT_BABEL = 42 RTPROT_OPENR = 99 RTPROT_BGP = 186 RTPROT_ISIS = 187 RTPROT_OSPF = 188 RTPROT_RIP = 189 RTPROT_EIGRP = 192 class NlRtaxType(Enum): RTAX_UNSPEC = 0 RTAX_LOCK = auto() RTAX_MTU = auto() RTAX_WINDOW = auto() RTAX_RTT = auto() RTAX_RTTVAR = auto() RTAX_SSTHRESH = auto() RTAX_CWND = auto() RTAX_ADVMSS = auto() RTAX_REORDERING = auto() RTAX_HOPLIMIT = auto() RTAX_INITCWND = auto() RTAX_FEATURES = auto() RTAX_RTO_MIN = auto() RTAX_INITRWND = auto() RTAX_QUICKACK = auto() RTAX_CC_ALGO = auto() RTAX_FASTOPEN_NO_COOKIE = auto() class NlRtGroup(Enum): RTNLGRP_NONE = 0 RTNLGRP_LINK = auto() RTNLGRP_NOTIFY = auto() RTNLGRP_NEIGH = auto() RTNLGRP_TC = auto() RTNLGRP_IPV4_IFADDR = auto() RTNLGRP_IPV4_MROUTE = auto() RTNLGRP_IPV4_ROUTE = auto() RTNLGRP_IPV4_RULE = auto() RTNLGRP_IPV6_IFADDR = auto() RTNLGRP_IPV6_MROUTE = auto() RTNLGRP_IPV6_ROUTE = auto() RTNLGRP_IPV6_IFINFO = auto() RTNLGRP_DECnet_IFADDR = auto() RTNLGRP_NOP2 = auto() RTNLGRP_DECnet_ROUTE = auto() RTNLGRP_DECnet_RULE = auto() RTNLGRP_NOP4 = auto() RTNLGRP_IPV6_PREFIX = auto() RTNLGRP_IPV6_RULE = auto() RTNLGRP_ND_USEROPT = auto() RTNLGRP_PHONET_IFADDR = auto() RTNLGRP_PHONET_ROUTE = auto() RTNLGRP_DCB = auto() RTNLGRP_IPV4_NETCONF = auto() RTNLGRP_IPV6_NETCONF = auto() RTNLGRP_MDB = auto() RTNLGRP_MPLS_ROUTE = auto() RTNLGRP_NSID = auto() RTNLGRP_MPLS_NETCONF = auto() RTNLGRP_IPV4_MROUTE_R = auto() RTNLGRP_IPV6_MROUTE_R = auto() RTNLGRP_NEXTHOP = auto() RTNLGRP_BRVLAN = auto() class IfinfoMsg(Structure): _fields_ = [ ("ifi_family", c_ubyte), ("__ifi_pad", c_ubyte), ("ifi_type", c_ushort), ("ifi_index", c_int), ("ifi_flags", c_uint), ("ifi_change", c_uint), ] class IflattrType(Enum): IFLA_UNSPEC = 0 IFLA_ADDRESS = auto() IFLA_BROADCAST = auto() IFLA_IFNAME = auto() IFLA_MTU = auto() IFLA_LINK = auto() IFLA_QDISC = auto() IFLA_STATS = auto() IFLA_COST = auto() IFLA_PRIORITY = auto() IFLA_MASTER = auto() IFLA_WIRELESS = auto() IFLA_PROTINFO = auto() IFLA_TXQLEN = auto() IFLA_MAP = auto() IFLA_WEIGHT = auto() IFLA_OPERSTATE = auto() IFLA_LINKMODE = auto() IFLA_LINKINFO = auto() IFLA_NET_NS_PID = auto() IFLA_IFALIAS = auto() IFLA_NUM_VF = auto() IFLA_VFINFO_LIST = auto() IFLA_STATS64 = auto() IFLA_VF_PORTS = auto() IFLA_PORT_SELF = auto() IFLA_AF_SPEC = auto() IFLA_GROUP = auto() IFLA_NET_NS_FD = auto() IFLA_EXT_MASK = auto() IFLA_PROMISCUITY = auto() IFLA_NUM_TX_QUEUES = auto() IFLA_NUM_RX_QUEUES = auto() IFLA_CARRIER = auto() IFLA_PHYS_PORT_ID = auto() IFLA_CARRIER_CHANGES = auto() IFLA_PHYS_SWITCH_ID = auto() IFLA_LINK_NETNSID = auto() IFLA_PHYS_PORT_NAME = auto() IFLA_PROTO_DOWN = auto() IFLA_GSO_MAX_SEGS = auto() IFLA_GSO_MAX_SIZE = auto() IFLA_PAD = auto() IFLA_XDP = auto() IFLA_EVENT = auto() IFLA_NEW_NETNSID = auto() IFLA_IF_NETNSID = auto() IFLA_CARRIER_UP_COUNT = auto() IFLA_CARRIER_DOWN_COUNT = auto() IFLA_NEW_IFINDEX = auto() IFLA_MIN_MTU = auto() IFLA_MAX_MTU = auto() IFLA_PROP_LIST = auto() IFLA_ALT_IFNAME = auto() IFLA_PERM_ADDRESS = auto() IFLA_PROTO_DOWN_REASON = auto() class IflinkInfo(Enum): IFLA_INFO_UNSPEC = 0 IFLA_INFO_KIND = auto() IFLA_INFO_DATA = auto() IFLA_INFO_XSTATS = auto() IFLA_INFO_SLAVE_KIND = auto() IFLA_INFO_SLAVE_DATA = auto() class IfLinkInfoDataVlan(Enum): IFLA_VLAN_UNSPEC = 0 IFLA_VLAN_ID = auto() IFLA_VLAN_FLAGS = auto() IFLA_VLAN_EGRESS_QOS = auto() IFLA_VLAN_INGRESS_QOS = auto() IFLA_VLAN_PROTOCOL = auto() class IfaddrMsg(Structure): _fields_ = [ ("ifa_family", c_ubyte), ("ifa_prefixlen", c_ubyte), ("ifa_flags", c_ubyte), ("ifa_scope", c_ubyte), ("ifa_index", c_uint), ] class IfattrType(Enum): IFA_UNSPEC = 0 IFA_ADDRESS = auto() IFA_LOCAL = auto() IFA_LABEL = auto() IFA_BROADCAST = auto() IFA_ANYCAST = auto() IFA_CACHEINFO = auto() IFA_MULTICAST = auto() IFA_FLAGS = auto() IFA_RT_PRIORITY = auto() IFA_TARGET_NETNSID = auto() class GenlMsgHdr(Structure): _fields_ = [ ("cmd", c_ubyte), ("version", c_ubyte), ("reserved", c_ushort), ] class NlConst: AF_NETLINK = 38 NETLINK_ROUTE = 0 NETLINK_GENERIC = 16 class NlHelper: def __init__(self): self._pmap = {} self._af_cls = self.get_af_cls() self._seq_counter = 1 self.pid = os.getpid() def get_seq(self): ret = self._seq_counter self._seq_counter += 1 return ret def get_af_cls(self): if sys.platform.startswith("freebsd"): cls = AddressFamilyBsd else: cls = AddressFamilyLinux return cls def get_propmap(self, cls): if cls not in self._pmap: ret = {} for prop in dir(cls): if not prop.startswith("_"): ret[getattr(cls, prop).value] = prop self._pmap[cls] = ret return self._pmap[cls] def get_name_propmap(self, cls): ret = {} for prop in dir(cls): if not prop.startswith("_"): ret[prop] = getattr(cls, prop).value return ret def get_attr_byval(self, cls, attr_val): propmap = self.get_propmap(cls) return propmap.get(attr_val) def get_nlmsg_name(self, val): for cls in [NlRtMsgType, NlMsgType]: v = self.get_attr_byval(cls, val) if v is not None: return v return "msg#{}".format(val) def get_af_name(self, family): v = self.get_attr_byval(self._af_cls, family) if v is not None: return v return "af#{}".format(family) def get_af_value(self, family_str: str) -> int: propmap = self.get_name_propmap(self._af_cls) return propmap.get(family_str) def get_rta_name(self, val): return self.get_attr_byval(RtattrType, val) def get_bitmask_map(self, cls, val): propmap = self.get_propmap(cls) v = 1 ret = {} while val: if v & val: if v in propmap: ret[v] = propmap[v] else: ret[v] = hex(v) val -= v v *= 2 return ret def get_bitmask_str(self, cls, val): bmap = self.get_bitmask_map(cls, val) return ",".join([v for k, v in bmap.items()]) def get_nlm_flags_str(self, msg_str: str, reply: bool, val): if reply: return self.get_bitmask_str(NlmAckFlags, val) if msg_str.startswith("RTM_GET"): return self.get_bitmask_str(NlmGetFlags, val) elif msg_str.startswith("RTM_DEL"): return self.get_bitmask_str(NlmDeleteFlags, val) elif msg_str.startswith("RTM_NEW"): return self.get_bitmask_str(NlmNewFlags, val) else: return self.get_bitmask_str(NlmBaseFlags, val) class NlAttr(object): def __init__(self, nla_type, data): if isinstance(nla_type, Enum): self._nla_type = nla_type.value self._enum = nla_type else: self._nla_type = nla_type self._enum = None self.nla_list = [] self._data = data @property def nla_type(self): return self._nla_type & 0x3F @property def nla_len(self): return len(self._data) + 4 def add_nla(self, nla): self.nla_list.append(nla) def print_attr(self, prepend=""): if self._enum is not None: type_str = self._enum.name else: type_str = "nla#{}".format(self.nla_type) print( "{}len={} type={}({}){}".format( prepend, self.nla_len, type_str, self.nla_type, self._print_attr_value() ) ) @staticmethod def _validate(data): if len(data) < 4: raise ValueError("attribute too short") nla_len, nla_type = struct.unpack("@HH", data[:4]) if nla_len > len(data): raise ValueError("attribute length too big") if nla_len < 4: raise ValueError("attribute length too short") @classmethod def _parse(cls, data): nla_len, nla_type = struct.unpack("@HH", data[:4]) return cls(nla_type, data[4:]) @classmethod def from_bytes(cls, data, attr_type_enum=None): cls._validate(data) attr = cls._parse(data) attr._enum = attr_type_enum return attr def _to_bytes(self, data: bytes): ret = data if align4(len(ret)) != len(ret): ret = data + bytes(align4(len(ret)) - len(ret)) return struct.pack("@HH", len(data) + 4, self._nla_type) + ret def __bytes__(self): return self._to_bytes(self._data) def _print_attr_value(self): return " " + " ".join(["x{:02X}".format(b) for b in self._data]) class NlAttrNested(NlAttr): def __init__(self, nla_type, val): super().__init__(nla_type, b"") self.nla_list = val @property def nla_len(self): return align4(len(b"".join([bytes(nla) for nla in self.nla_list]))) + 4 def print_attr(self, prepend=""): if self._enum is not None: type_str = self._enum.name else: type_str = "nla#{}".format(self.nla_type) print( "{}len={} type={}({}) {{".format( prepend, self.nla_len, type_str, self.nla_type ) ) for nla in self.nla_list: nla.print_attr(prepend + " ") print("{}}}".format(prepend)) def __bytes__(self): return self._to_bytes(b"".join([bytes(nla) for nla in self.nla_list])) class NlAttrU32(NlAttr): def __init__(self, nla_type, val): self.u32 = val super().__init__(nla_type, b"") @property def nla_len(self): return 8 def _print_attr_value(self): return " val={}".format(self.u32) @staticmethod def _validate(data): assert len(data) == 8 nla_len, nla_type = struct.unpack("@HH", data[:4]) assert nla_len == 8 @classmethod def _parse(cls, data): nla_len, nla_type, val = struct.unpack("@HHI", data) return cls(nla_type, val) def __bytes__(self): return self._to_bytes(struct.pack("@I", self.u32)) class NlAttrU16(NlAttr): def __init__(self, nla_type, val): self.u16 = val super().__init__(nla_type, b"") @property def nla_len(self): return 6 def _print_attr_value(self): return " val={}".format(self.u16) @staticmethod def _validate(data): assert len(data) == 6 nla_len, nla_type = struct.unpack("@HH", data[:4]) assert nla_len == 6 @classmethod def _parse(cls, data): nla_len, nla_type, val = struct.unpack("@HHH", data) return cls(nla_type, val) def __bytes__(self): return self._to_bytes(struct.pack("@H", self.u16)) class NlAttrU8(NlAttr): def __init__(self, nla_type, val): self.u8 = val super().__init__(nla_type, b"") @property def nla_len(self): return 5 def _print_attr_value(self): return " val={}".format(self.u8) @staticmethod def _validate(data): assert len(data) == 5 nla_len, nla_type = struct.unpack("@HH", data[:4]) assert nla_len == 5 @classmethod def _parse(cls, data): nla_len, nla_type, val = struct.unpack("@HHB", data) return cls(nla_type, val) def __bytes__(self): return self._to_bytes(struct.pack("@B", self.u8)) class NlAttrIp(NlAttr): def __init__(self, nla_type, addr: str): super().__init__(nla_type, b"") self.addr = addr if ":" in self.addr: self.family = socket.AF_INET6 else: self.family = socket.AF_INET @staticmethod def _validate(data): nla_len, nla_type = struct.unpack("@HH", data[:4]) data_len = nla_len - 4 if data_len != 4 and data_len != 16: raise ValueError( "Error validating attr {}: nla_len is not valid".format( # noqa: E501 nla_type ) ) @property def nla_len(self): if self.family == socket.AF_INET6: return 20 else: return 8 return align4(len(self._data)) + 4 @classmethod def _parse(cls, data): nla_len, nla_type = struct.unpack("@HH", data[:4]) data_len = len(data) - 4 if data_len == 4: addr = socket.inet_ntop(socket.AF_INET, data[4:8]) else: addr = socket.inet_ntop(socket.AF_INET6, data[4:20]) return cls(nla_type, addr) def __bytes__(self): return self._to_bytes(socket.inet_pton(self.family, self.addr)) def _print_attr_value(self): return " addr={}".format(self.addr) class NlAttrIfindex(NlAttrU32): def _print_attr_value(self): try: ifname = socket.if_indextoname(self.u32) return " iface={}(#{})".format(ifname, self.u32) except OSError: pass return " iface=if#{}".format(self.u32) class NlAttrTable(NlAttrU32): def _print_attr_value(self): return " rtable={}".format(self.u32) class NlAttrNhId(NlAttrU32): def _print_attr_value(self): return " nh_id={}".format(self.u32) class NlAttrMac(NlAttr): def _print_attr_value(self): return ' mac="' + ":".join(["{:02X}".format(b) for b in self._data]) + '"' class NlAttrIfStats(NlAttr): def _print_attr_value(self): return " stats={...}" class NlAttrVia(NlAttr): def __init__(self, nla_type, family, addr: str): super().__init__(nla_type, b"") self.addr = addr self.family = family @staticmethod def _validate(data): nla_len, nla_type = struct.unpack("@HH", data[:4]) data_len = nla_len - 4 if data_len == 0: raise ValueError( "Error validating attr {}: empty data".format(nla_type) ) # noqa: E501 family = int(data_len[0]) if family not in (socket.AF_INET, socket.AF_INET6): raise ValueError( "Error validating attr {}: unsupported AF {}".format( # noqa: E501 nla_type, family ) ) if family == socket.AF_INET: expected_len = 1 + 4 else: expected_len = 1 + 16 if data_len != expected_len: raise ValueError( "Error validating attr {}: expected len {} got {}".format( # noqa: E501 nla_type, expected_len, data_len ) ) @property def nla_len(self): if self.family == socket.AF_INET6: return 21 else: return 9 @classmethod def _parse(cls, data): nla_len, nla_type, family = struct.unpack("@HHB", data[:5]) off = 5 if family == socket.AF_INET: addr = socket.inet_ntop(family, data[off : off + 4]) else: addr = socket.inet_ntop(family, data[off : off + 16]) return cls(nla_type, family, addr) def __bytes__(self): addr = socket.inet_pton(self.family, self.addr) return self._to_bytes(struct.pack("@B", self.family) + addr) def _print_attr_value(self): return " via={}".format(self.addr) class NlAttrStr(NlAttr): def __init__(self, nla_type, text): super().__init__(nla_type, b"") self.text = text @staticmethod def _validate(data): NlAttr._validate(data) try: data[4:].decode("utf-8") except Exception as e: raise ValueError("wrong utf-8 string: {}".format(e)) @property def nla_len(self): return len(self.text) + 5 @classmethod def _parse(cls, data): text = data[4:-1].decode("utf-8") nla_len, nla_type = struct.unpack("@HH", data[:4]) return cls(nla_type, text) def __bytes__(self): return self._to_bytes(bytes(self.text, encoding="utf-8") + bytes(1)) def _print_attr_value(self): return ' val="{}"'.format(self.text) class NlAttrStrn(NlAttr): def __init__(self, nla_type, text): super().__init__(nla_type, b"") self.text = text @staticmethod def _validate(data): NlAttr._validate(data) try: data[4:].decode("utf-8") except Exception as e: raise ValueError("wrong utf-8 string: {}".format(e)) @property def nla_len(self): return len(self.text) + 4 @classmethod def _parse(cls, data): text = data[4:].decode("utf-8") nla_len, nla_type = struct.unpack("@HH", data[:4]) return cls(nla_type, text) def __bytes__(self): return self._to_bytes(bytes(self.text, encoding="utf-8")) def _print_attr_value(self): return ' val="{}"'.format(self.text) class AttrDescr(NamedTuple): val: Enum cls: NlAttr child_map: Any = None def prepare_attrs_map(attrs: List[AttrDescr]) -> Dict[str, Dict]: ret = {} for ad in attrs: ret[ad.val.value] = {"ad": ad} if ad.child_map: ret[ad.val.value]["child"] = prepare_attrs_map(ad.child_map) return ret rtnl_route_attrs = [ AttrDescr(RtattrType.RTA_DST, NlAttrIp), AttrDescr(RtattrType.RTA_SRC, NlAttrIp), AttrDescr(RtattrType.RTA_IIF, NlAttrIfindex), AttrDescr(RtattrType.RTA_OIF, NlAttrIfindex), AttrDescr(RtattrType.RTA_GATEWAY, NlAttrTable), AttrDescr(RtattrType.RTA_VIA, NlAttrVia), AttrDescr(RtattrType.RTA_NH_ID, NlAttrNhId), AttrDescr( RtattrType.RTA_METRICS, NlAttrNested, [ AttrDescr(NlRtaxType.RTAX_MTU, NlAttrU32), ], ), ] +nldone_attrs = [] + nlerr_attrs = [ AttrDescr(NlErrattrType.NLMSGERR_ATTR_MSG, NlAttrStr), AttrDescr(NlErrattrType.NLMSGERR_ATTR_OFFS, NlAttrU32), ] rtnl_ifla_attrs = [ AttrDescr(IflattrType.IFLA_ADDRESS, NlAttrMac), AttrDescr(IflattrType.IFLA_BROADCAST, NlAttrMac), AttrDescr(IflattrType.IFLA_IFNAME, NlAttrStr), AttrDescr(IflattrType.IFLA_MTU, NlAttrU32), AttrDescr(IflattrType.IFLA_PROMISCUITY, NlAttrU32), AttrDescr(IflattrType.IFLA_OPERSTATE, NlAttrU8), AttrDescr(IflattrType.IFLA_CARRIER, NlAttrU8), AttrDescr(IflattrType.IFLA_IFALIAS, NlAttrStr), AttrDescr(IflattrType.IFLA_STATS64, NlAttrIfStats), AttrDescr( IflattrType.IFLA_LINKINFO, NlAttrNested, [ AttrDescr(IflinkInfo.IFLA_INFO_KIND, NlAttrStr), AttrDescr(IflinkInfo.IFLA_INFO_DATA, NlAttr), ], ), ] rtnl_ifa_attrs = [ AttrDescr(IfattrType.IFA_ADDRESS, NlAttrIp), AttrDescr(IfattrType.IFA_LOCAL, NlAttrIp), + AttrDescr(IfattrType.IFA_LABEL, NlAttrStr), AttrDescr(IfattrType.IFA_BROADCAST, NlAttrIp), AttrDescr(IfattrType.IFA_ANYCAST, NlAttrIp), AttrDescr(IfattrType.IFA_FLAGS, NlAttrU32), ] class BaseNetlinkMessage(object): def __init__(self, helper, nlmsg_type): self.nlmsg_type = nlmsg_type self.ut = unittest.TestCase() self.nla_list = [] self._orig_data = None self.helper = helper self.nl_hdr = Nlmsghdr( nlmsg_type=nlmsg_type, nlmsg_seq=helper.get_seq(), nlmsg_pid=helper.pid ) self.base_hdr = None def add_nla(self, nla): self.nla_list.append(nla) def _get_nla(self, nla_list, nla_type): if isinstance(nla_type, Enum): nla_type_raw = nla_type.value else: nla_type_raw = nla_type for nla in nla_list: if nla.nla_type == nla_type_raw: return nla return None def get_nla(self, nla_type): return self._get_nla(self.nla_list, nla_type) @staticmethod def parse_nl_header(data: bytes): if len(data) < sizeof(Nlmsghdr): raise ValueError("length less than netlink message header") return Nlmsghdr.from_buffer_copy(data), sizeof(Nlmsghdr) def is_type(self, nlmsg_type): if isinstance(nlmsg_type, Enum): nlmsg_type_raw = nlmsg_type.value else: nlmsg_type_raw = nlmsg_type return nlmsg_type_raw == self.nl_hdr.nlmsg_type def is_reply(self, hdr): return hdr.nlmsg_type == NlMsgType.NLMSG_ERROR.value def print_nl_header(self, hdr, prepend=""): # len=44, type=RTM_DELROUTE, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1641163704, pid=0 # noqa: E501 is_reply = self.is_reply(hdr) msg_name = self.helper.get_nlmsg_name(hdr.nlmsg_type) print( "{}len={}, type={}, flags={}(0x{:X}), seq={}, pid={}".format( prepend, hdr.nlmsg_len, msg_name, self.helper.get_nlm_flags_str( msg_name, is_reply, hdr.nlmsg_flags ), # noqa: E501 hdr.nlmsg_flags, hdr.nlmsg_seq, hdr.nlmsg_pid, ) ) @classmethod def from_bytes(cls, helper, data): try: hdr, hdrlen = BaseNetlinkMessage.parse_nl_header(data) self = cls(helper, hdr.nlmsg_type) self._orig_data = data self.nl_hdr = hdr except ValueError as e: print("Failed to parse nl header: {}".format(e)) cls.print_as_bytes(data) raise return self def print_message(self): self.print_nl_header(self.nl_hdr) @staticmethod def print_as_bytes(data: bytes, descr: str): print("===vv {} (len:{:3d}) vv===".format(descr, len(data))) off = 0 step = 16 while off < len(data): for i in range(step): if off + i < len(data): print(" {:02X}".format(data[off + i]), end="") print("") off += step print("--------------------") class StdNetlinkMessage(BaseNetlinkMessage): nl_attrs_map = {} @classmethod def from_bytes(cls, helper, data): try: hdr, hdrlen = BaseNetlinkMessage.parse_nl_header(data) self = cls(helper, hdr.nlmsg_type) self._orig_data = data self.nl_hdr = hdr except ValueError as e: print("Failed to parse nl header: {}".format(e)) cls.print_as_bytes(data) raise offset = align4(hdrlen) try: base_hdr, hdrlen = self.parse_base_header(data[offset:]) self.base_hdr = base_hdr offset += align4(hdrlen) # XXX: CAP_ACK except ValueError as e: print("Failed to parse nl rt header: {}".format(e)) cls.print_as_bytes(data) raise orig_offset = offset try: nla_list, nla_len = self.parse_nla_list(data[offset:]) offset += nla_len if offset != len(data): raise ValueError( "{} bytes left at the end of the packet".format(len(data) - offset) ) # noqa: E501 self.nla_list = nla_list except ValueError as e: print( "Failed to parse nla attributes at offset {}: {}".format(orig_offset, e) ) # noqa: E501 cls.print_as_bytes(data, "msg dump") cls.print_as_bytes(data[orig_offset:], "failed block") raise return self def _parse_attrs(self, data: bytes, attr_map): ret = [] off = 0 while len(data) - off >= 4: nla_len, raw_nla_type = struct.unpack("@HH", data[off : off + 4]) if nla_len + off > len(data): raise ValueError( "attr length {} > than the remaining length {}".format( nla_len, len(data) - off ) ) nla_type = raw_nla_type & 0x3F if nla_type in attr_map: v = attr_map[nla_type] val = v["ad"].cls.from_bytes(data[off : off + nla_len], v["ad"].val) if "child" in v: # nested attrs, _ = self._parse_attrs(data[off : off + nla_len], v["child"]) val = NlAttrNested(raw_nla_type, attrs) else: # unknown attribute val = NlAttr(raw_nla_type, data[off + 4 : off + nla_len]) ret.append(val) off += align4(nla_len) return ret, off def parse_nla_list(self, data: bytes) -> List[NlAttr]: return self._parse_attrs(data, self.nl_attrs_map) def print_message(self): self.print_nl_header(self.nl_hdr) self.print_base_header(self.base_hdr, " ") for nla in self.nla_list: nla.print_attr(" ") +class NetlinkDoneMessage(StdNetlinkMessage): + messages = [NlMsgType.NLMSG_DONE.value] + nl_attrs_map = prepare_attrs_map(nldone_attrs) + + @property + def error_code(self): + return self.base_hdr.error + + def parse_base_header(self, data): + if len(data) < sizeof(Nlmsgdone): + raise ValueError("length less than nlmsgdone header") + done_hdr = Nlmsgdone.from_buffer_copy(data) + sz = sizeof(Nlmsgdone) + return (done_hdr, sz) + + def print_base_header(self, hdr, prepend=""): + print("{}error={}".format(prepend, hdr.error)) + + class NetlinkErrorMessage(StdNetlinkMessage): messages = [NlMsgType.NLMSG_ERROR.value] nl_attrs_map = prepare_attrs_map(nlerr_attrs) @property def error_code(self): return self.base_hdr.error @property def error_str(self): nla = self.get_nla(NlErrattrType.NLMSGERR_ATTR_MSG) if nla: return nla.text return None @property def error_offset(self): nla = self.get_nla(NlErrattrType.NLMSGERR_ATTR_OFFS) if nla: return nla.u32 return None def parse_base_header(self, data): if len(data) < sizeof(Nlmsgerr): raise ValueError("length less than nlmsgerr header") err_hdr = Nlmsgerr.from_buffer_copy(data) sz = sizeof(Nlmsgerr) if (self.nl_hdr.nlmsg_flags & 0x100) == 0: sz += align4(err_hdr.msg.nlmsg_len - sizeof(Nlmsghdr)) return (err_hdr, sz) def print_base_header(self, errhdr, prepend=""): print("{}error={}, ".format(prepend, errhdr.error), end="") self.print_nl_header(errhdr.msg, prepend) class BaseNetlinkRtMessage(StdNetlinkMessage): def __init__(self, helper, nlm_type): super().__init__(helper, nlm_type) self.base_hdr = None def __bytes__(self): ret = bytes() for nla in self.nla_list: ret += bytes(nla) ret = bytes(self.base_hdr) + ret self.nl_hdr.nlmsg_len = len(ret) + sizeof(Nlmsghdr) return bytes(self.nl_hdr) + ret def print_message(self): self.print_nl_header(self.nl_hdr) self.print_base_header(self.base_hdr, " ") for nla in self.nla_list: nla.print_attr(" ") class NetlinkRtMessage(BaseNetlinkRtMessage): messages = [ NlRtMsgType.RTM_NEWROUTE.value, NlRtMsgType.RTM_DELROUTE.value, NlRtMsgType.RTM_GETROUTE.value, ] nl_attrs_map = prepare_attrs_map(rtnl_route_attrs) def __init__(self, helper, nlm_type): super().__init__(helper, nlm_type) self.base_hdr = RtMsgHdr() def parse_base_header(self, data): if len(data) < sizeof(RtMsgHdr): raise ValueError("length less than rtmsg header") rtm_hdr = RtMsgHdr.from_buffer_copy(data) return (rtm_hdr, sizeof(RtMsgHdr)) def print_base_header(self, hdr, prepend=""): family = self.helper.get_af_name(hdr.rtm_family) print( "{}family={}, dst_len={}, src_len={}, tos={}, table={}, protocol={}({}), scope={}({}), type={}({}), flags={}({})".format( # noqa: E501 prepend, family, hdr.rtm_dst_len, hdr.rtm_src_len, hdr.rtm_tos, hdr.rtm_table, self.helper.get_attr_byval(RtProto, hdr.rtm_protocol), hdr.rtm_protocol, self.helper.get_attr_byval(RtScope, hdr.rtm_scope), hdr.rtm_scope, self.helper.get_attr_byval(RtType, hdr.rtm_type), hdr.rtm_type, self.helper.get_bitmask_str(RtMsgFlags, hdr.rtm_flags), hdr.rtm_flags, ) ) class NetlinkIflaMessage(BaseNetlinkRtMessage): messages = [ NlRtMsgType.RTM_NEWLINK.value, NlRtMsgType.RTM_DELLINK.value, NlRtMsgType.RTM_GETLINK.value, ] nl_attrs_map = prepare_attrs_map(rtnl_ifla_attrs) def __init__(self, helper, nlm_type): super().__init__(helper, nlm_type) self.base_hdr = IfinfoMsg() def parse_base_header(self, data): if len(data) < sizeof(IfinfoMsg): raise ValueError("length less than IfinfoMsg header") rtm_hdr = IfinfoMsg.from_buffer_copy(data) return (rtm_hdr, sizeof(IfinfoMsg)) def print_base_header(self, hdr, prepend=""): family = self.helper.get_af_name(hdr.ifi_family) print( "{}family={}, ifi_type={}, ifi_index={}, ifi_flags={}, ifi_change={}".format( # noqa: E501 prepend, family, hdr.ifi_type, hdr.ifi_index, hdr.ifi_flags, hdr.ifi_change, ) ) class NetlinkIfaMessage(BaseNetlinkRtMessage): messages = [ NlRtMsgType.RTM_NEWADDR.value, NlRtMsgType.RTM_DELADDR.value, NlRtMsgType.RTM_GETADDR.value, ] nl_attrs_map = prepare_attrs_map(rtnl_ifa_attrs) def __init__(self, helper, nlm_type): super().__init__(helper, nlm_type) self.base_hdr = IfaddrMsg() def parse_base_header(self, data): if len(data) < sizeof(IfaddrMsg): raise ValueError("length less than IfaddrMsg header") rtm_hdr = IfaddrMsg.from_buffer_copy(data) return (rtm_hdr, sizeof(IfaddrMsg)) def print_base_header(self, hdr, prepend=""): family = self.helper.get_af_name(hdr.ifa_family) print( "{}family={}, ifa_prefixlen={}, ifa_flags={}, ifa_scope={}, ifa_index={}".format( # noqa: E501 prepend, family, hdr.ifa_prefixlen, hdr.ifa_flags, hdr.ifa_scope, hdr.ifa_index, ) ) class Nlsock: def __init__(self, family, helper): self.helper = helper self.sock_fd = self._setup_netlink(family) self._data = bytes() self.msgmap = self.build_msgmap() # self.set_groups(NlRtGroup.RTNLGRP_IPV4_ROUTE.value | NlRtGroup.RTNLGRP_IPV6_ROUTE.value) # noqa: E501 def build_msgmap(self): classes = [ NetlinkRtMessage, NetlinkIflaMessage, NetlinkIfaMessage, + NetlinkDoneMessage, NetlinkErrorMessage, ] xmap = {} for cls in classes: for message in cls.messages: xmap[message] = cls return xmap def _setup_netlink(self, netlink_family) -> int: family = self.helper.get_af_value("AF_NETLINK") s = socket.socket(family, socket.SOCK_RAW, netlink_family) s.setsockopt(270, 10, 1) # NETLINK_CAP_ACK s.setsockopt(270, 11, 1) # NETLINK_EXT_ACK return s def set_groups(self, mask: int): self.sock_fd.setsockopt(socket.SOL_SOCKET, 1, mask) # snl = SockaddrNl(nl_len = sizeof(SockaddrNl), nl_family=38, # nl_pid=self.pid, nl_groups=mask) # xbuffer = create_string_buffer(sizeof(SockaddrNl)) # memmove(xbuffer, addressof(snl), sizeof(SockaddrNl)) # k = struct.pack("@BBHII", 12, 38, 0, self.pid, mask) # self.sock_fd.bind(k) def write_message(self, msg): print("vvvvvvvv OUT vvvvvvvv") msg.print_message() msg_bytes = bytes(msg) try: ret = os.write(self.sock_fd.fileno(), msg_bytes) assert ret == len(msg_bytes) except Exception as e: print("write({}) -> {}".format(len(msg_bytes), e)) def parse_message(self, data: bytes): if len(data) < sizeof(Nlmsghdr): raise Exception("Short read from nl: {} bytes".format(len(data))) hdr = Nlmsghdr.from_buffer_copy(data) nlmsg_type = hdr.nlmsg_type cls = self.msgmap.get(nlmsg_type) if not cls: cls = BaseNetlinkMessage return cls.from_bytes(self.helper, data) def write_data(self, data: bytes): self.sock_fd.send(data) def read_data(self): while True: data = self.sock_fd.recv(65535) self._data += data if len(self._data) >= sizeof(Nlmsghdr): break def read_message(self) -> bytes: if len(self._data) < sizeof(Nlmsghdr): self.read_data() hdr = Nlmsghdr.from_buffer_copy(self._data) while hdr.nlmsg_len > len(self._data): self.read_data() raw_msg = self._data[: hdr.nlmsg_len] self._data = self._data[hdr.nlmsg_len :] return self.parse_message(raw_msg) def request_ifaces(self): msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value) flags = NlmGetFlags.NLM_F_ROOT.value | NlmGetFlags.NLM_F_MATCH.value msg.nl_hdr.nlmsg_flags = flags | NlmBaseFlags.NLM_F_REQUEST.value msg_bytes = bytes(msg) x = self.parse_message(msg_bytes) x.print_message() print(msg_bytes) # Skip family for now self.write_data(msg_bytes) def request_ifaddrs(self, family): msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value) flags = NlmGetFlags.NLM_F_ROOT.value | NlmGetFlags.NLM_F_MATCH.value msg.base_hdr.ifa_family = family msg.nl_hdr.nlmsg_flags = flags | NlmBaseFlags.NLM_F_REQUEST.value msg_bytes = bytes(msg) x = self.parse_message(msg_bytes) x.print_message() print(msg_bytes) # Skip family for now self.write_data(msg_bytes) def request_routes(self, family): msg = NetlinkRtMessage(self.helper, NlRtMsgType.RTM_GETROUTE.value) flags = NlmGetFlags.NLM_F_ROOT.value | NlmGetFlags.NLM_F_MATCH.value msg.base_hdr.rtm_family = family msg.nl_hdr.nlmsg_flags = flags | NlmBaseFlags.NLM_F_REQUEST.value msg_bytes = bytes(msg) x = self.parse_message(msg_bytes) x.print_message() print(msg_bytes) # Skip family for now self.write_data(msg_bytes) def request_raw(self): flags = NlmGetFlags.NLM_F_ROOT.value | NlmGetFlags.NLM_F_MATCH.value hdr = Nlmsghdr( nlmsg_type=NlRtMsgType.RTM_GETROUTE.value, nlmsg_flags=flags | NlmBaseFlags.NLM_F_REQUEST.value, nlmsg_len=sizeof(Nlmsghdr) + sizeof(RtMsgHdr) + 4, ) rthdr = RtMsgHdr() rta = RtAttr(rta_len=3, rta_type=RtattrType.RTA_OIF.value) msg_bytes = bytes(hdr) + bytes(rthdr) + bytes(rta) # x = self.parse_message(msg_bytes) # x.print_message() print(msg_bytes) self.write_data(msg_bytes) def request_families(self): hdr = Nlmsghdr( nlmsg_type=16, nlmsg_flags=NlmBaseFlags.NLM_F_REQUEST.value, nlmsg_len=sizeof(Nlmsghdr) + sizeof(GenlMsgHdr) + 4, ) ghdr = GenlMsgHdr(cmd=3) rta = RtAttr(rta_len=3, rta_type=RtattrType.RTA_OIF) msg_bytes = bytes(hdr) + bytes(ghdr) + bytes(rta) x = self.parse_message(msg_bytes) x.print_message() print(msg_bytes) self.write_data(msg_bytes) -def main(): - helper = NlHelper() - if False: - nl = Nlsock(NlConst.NETLINK_GENERIC, helper) - nl.request_families() - else: - nl = Nlsock(NlConst.NETLINK_ROUTE, helper) - # nl.request_ifaddrs(socket.AF_INET) - # nl.request_raw() - nl.request_routes(0) - # nl.request_ifaces() - while True: - msg = nl.read_message() +class NetlinkMultipartIterator(object): + def __init__(self, obj, seq_number: int, msg_type): + self._obj = obj + self._seq = seq_number + self._msg_type = msg_type + + def __iter__(self): + return self + + def __next__(self): + msg = self._obj.read_message() + if self._seq != msg.nl_hdr.nlmsg_seq: + raise ValueError("bad sequence number") + if msg.is_type(NlMsgType.NLMSG_ERROR): + raise ValueError( + "error while handling multipart msg: {}".format(msg.error_code) + ) + elif msg.is_type(NlMsgType.NLMSG_DONE): + if msg.error_code == 0: + raise StopIteration + raise ValueError( + "error listing some parts of the multipart msg: {}".format( + msg.error_code + ) + ) + elif not msg.is_type(self._msg_type): + raise ValueError("bad message type: {}".format(msg)) + return msg + + +class NetlinkTestTemplate(object): + REQUIRED_MODULES = ["netlink"] + + def setup_netlink(self, netlink_family: NlConst): + self.helper = NlHelper() + self.nlsock = Nlsock(netlink_family, self.helper) + + def write_message(self, msg): print("") + print("============= >> TX MESSAGE =============") msg.print_message() - msg.print_as_bytes(msg._orig_data, "-- DATA --") - pass + self.nlsock.write_data(bytes(msg)) + msg.print_as_bytes(bytes(msg), "-- DATA --") + + def read_message(self): + msg = self.nlsock.read_message() + print("") + print("============= << RX MESSAGE =============") + msg.print_message() + return msg + + def get_reply(self, tx_msg): + self.write_message(tx_msg) + while True: + rx_msg = self.read_message() + if tx_msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq: + return rx_msg + + def read_msg_list(self, seq, msg_type): + return list(NetlinkMultipartIterator(self, seq, msg_type)) diff --git a/tests/sys/netlink/test_rtnl_iface.py b/tests/sys/netlink/test_rtnl_iface.py index 3340eaa4d16d..8660051be8e2 100644 --- a/tests/sys/netlink/test_rtnl_iface.py +++ b/tests/sys/netlink/test_rtnl_iface.py @@ -1,283 +1,256 @@ import errno import socket import pytest from atf_python.sys.net.netlink import IflattrType from atf_python.sys.net.netlink import IflinkInfo from atf_python.sys.net.netlink import IfLinkInfoDataVlan from atf_python.sys.net.netlink import NetlinkIflaMessage +from atf_python.sys.net.netlink import NetlinkTestTemplate from atf_python.sys.net.netlink import NlAttrNested from atf_python.sys.net.netlink import NlAttrStr from atf_python.sys.net.netlink import NlAttrStrn from atf_python.sys.net.netlink import NlAttrU16 from atf_python.sys.net.netlink import NlAttrU32 from atf_python.sys.net.netlink import NlConst -from atf_python.sys.net.netlink import NlHelper from atf_python.sys.net.netlink import NlmBaseFlags from atf_python.sys.net.netlink import NlmNewFlags from atf_python.sys.net.netlink import NlMsgType from atf_python.sys.net.netlink import NlRtMsgType -from atf_python.sys.net.netlink import Nlsock from atf_python.sys.net.vnet import SingleVnetTestTemplate -class TestRtNlIface(SingleVnetTestTemplate): - REQUIRED_MODULES = ["netlink"] - +class TestRtNlIface(SingleVnetTestTemplate, NetlinkTestTemplate): def setup_method(self, method): super().setup_method(method) - self.helper = NlHelper() - self.nlsock = Nlsock(NlConst.NETLINK_ROUTE, self.helper) - - def write_message(self, msg): - print("") - print("============= >> TX MESSAGE =============") - msg.print_message() - self.nlsock.write_data(bytes(msg)) - msg.print_as_bytes(bytes(msg), "-- DATA --") - - def read_message(self): - msg = self.nlsock.read_message() - print("") - print("============= << RX MESSAGE =============") - msg.print_message() - return msg - - def get_reply(self, tx_msg): - self.write_message(tx_msg) - while True: - rx_msg = self.read_message() - if tx_msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq: - return rx_msg + self.setup_netlink(NlConst.NETLINK_ROUTE) def get_interface_byname(self, ifname): msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value) msg.nl_hdr.nlmsg_flags = ( NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, ifname)) self.write_message(msg) while True: rx_msg = self.read_message() if msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq: if rx_msg.is_type(NlMsgType.NLMSG_ERROR): if rx_msg.error_code != 0: raise ValueError("unable to get interface {}".format(ifname)) elif rx_msg.is_type(NlRtMsgType.RTM_NEWLINK): return rx_msg else: raise ValueError("bad message") def test_get_iface_byname_error(self): """Tests error on fetching non-existing interface name""" msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value) msg.nl_hdr.nlmsg_flags = ( NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == errno.ENODEV def test_get_iface_byindex_error(self): """Tests error on fetching non-existing interface index""" msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value) msg.nl_hdr.nlmsg_flags = ( NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.base_hdr.ifi_index = 2147483647 rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == errno.ENODEV @pytest.mark.require_user("root") def test_create_iface_plain(self): """Tests loopback creation w/o any parameters""" flags = NlmNewFlags.NLM_F_EXCL.value | NlmNewFlags.NLM_F_CREATE.value msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_NEWLINK.value) msg.nl_hdr.nlmsg_flags = ( flags | NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) msg.add_nla( NlAttrNested( IflattrType.IFLA_LINKINFO, [ NlAttrStrn(IflinkInfo.IFLA_INFO_KIND, "lo"), ], ) ) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 self.get_interface_byname("lo10") @pytest.mark.require_user("root") def test_create_iface_attrs(self): """Tests interface creation with additional properties""" flags = NlmNewFlags.NLM_F_EXCL.value | NlmNewFlags.NLM_F_CREATE.value msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_NEWLINK.value) msg.nl_hdr.nlmsg_flags = ( flags | NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) msg.add_nla( NlAttrNested( IflattrType.IFLA_LINKINFO, [ NlAttrStrn(IflinkInfo.IFLA_INFO_KIND, "lo"), ], ) ) # Custom attributes msg.add_nla(NlAttrStr(IflattrType.IFLA_IFALIAS, "test description")) msg.add_nla(NlAttrU32(IflattrType.IFLA_MTU, 1024)) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 iface_msg = self.get_interface_byname("lo10") assert iface_msg.get_nla(IflattrType.IFLA_IFALIAS).text == "test description" assert iface_msg.get_nla(IflattrType.IFLA_MTU).u32 == 1024 @pytest.mark.require_user("root") def test_modify_iface_attrs(self): """Tests interface modifications""" flags = NlmNewFlags.NLM_F_EXCL.value | NlmNewFlags.NLM_F_CREATE.value msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_NEWLINK.value) msg.nl_hdr.nlmsg_flags = ( flags | NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) msg.add_nla( NlAttrNested( IflattrType.IFLA_LINKINFO, [ NlAttrStrn(IflinkInfo.IFLA_INFO_KIND, "lo"), ], ) ) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_NEWLINK.value) msg.nl_hdr.nlmsg_flags = ( NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) # Custom attributes msg.add_nla(NlAttrStr(IflattrType.IFLA_IFALIAS, "test description")) msg.add_nla(NlAttrU32(IflattrType.IFLA_MTU, 1024)) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 iface_msg = self.get_interface_byname("lo10") assert iface_msg.get_nla(IflattrType.IFLA_IFALIAS).text == "test description" assert iface_msg.get_nla(IflattrType.IFLA_MTU).u32 == 1024 @pytest.mark.require_user("root") def test_delete_iface(self): """Tests interface modifications""" flags = NlmNewFlags.NLM_F_EXCL.value | NlmNewFlags.NLM_F_CREATE.value msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_NEWLINK.value) msg.nl_hdr.nlmsg_flags = ( flags | NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) msg.add_nla( NlAttrNested( IflattrType.IFLA_LINKINFO, [ NlAttrStrn(IflinkInfo.IFLA_INFO_KIND, "lo"), ], ) ) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 iface_msg = self.get_interface_byname("lo10") iface_idx = iface_msg.base_hdr.ifi_index msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_DELLINK.value) msg.nl_hdr.nlmsg_flags = ( NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.base_hdr.ifi_index = iface_idx # msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "lo10")) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value) msg.nl_hdr.nlmsg_flags = ( NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.base_hdr.ifi_index = 2147483647 rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == errno.ENODEV # # * # * {len=76, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1662892737, pid=0}, # * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}, - # * [ # * {{nla_len=8, nla_type=IFLA_LINK}, 2}, # * {{nla_len=12, nla_type=IFLA_IFNAME}, "xvlan22"}, # * {{nla_len=24, nla_type=IFLA_LINKINFO}, - # * [ # * {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...}, - # * {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"}]}]}, iov_len=76}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 76 + # * {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"} # */ @pytest.mark.skip(reason="vlan support needs more work") @pytest.mark.require_user("root") def test_create_vlan_plain(self): """Creates 802.1Q VLAN interface in vlanXX and ifX fashion""" os_ifname = self.vnet.iface_alias_map["if1"].name ifindex = socket.if_nametoindex(os_ifname) flags = NlmNewFlags.NLM_F_EXCL.value | NlmNewFlags.NLM_F_CREATE.value msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_NEWLINK.value) msg.nl_hdr.nlmsg_flags = ( flags | NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value ) msg.add_nla(NlAttrU32(IflattrType.IFLA_LINK, ifindex)) msg.add_nla(NlAttrStr(IflattrType.IFLA_IFNAME, "vlan22")) msg.add_nla( NlAttrNested( IflattrType.IFLA_LINKINFO, [ NlAttrStrn(IflinkInfo.IFLA_INFO_KIND, "vlan"), NlAttrNested( IflinkInfo.IFLA_INFO_DATA, [ NlAttrU16(IfLinkInfoDataVlan.IFLA_VLAN_ID, 22), ], ), ], ) ) rx_msg = self.get_reply(msg) assert rx_msg.is_type(NlMsgType.NLMSG_ERROR) assert rx_msg.error_code == 0 self.get_interface_byname("vlan22") # ToolsHelper.print_net_debug() diff --git a/tests/sys/netlink/test_rtnl_ifaddr.py b/tests/sys/netlink/test_rtnl_ifaddr.py new file mode 100644 index 000000000000..5574644f7b2f --- /dev/null +++ b/tests/sys/netlink/test_rtnl_ifaddr.py @@ -0,0 +1,144 @@ +import ipaddress +import socket +import struct + +from atf_python.sys.net.netlink import IfattrType +from atf_python.sys.net.netlink import NetlinkIfaMessage +from atf_python.sys.net.netlink import NetlinkTestTemplate +from atf_python.sys.net.netlink import NlConst +from atf_python.sys.net.netlink import NlHelper +from atf_python.sys.net.netlink import NlmBaseFlags +from atf_python.sys.net.netlink import NlMsgType +from atf_python.sys.net.netlink import NlRtMsgType +from atf_python.sys.net.netlink import Nlsock +from atf_python.sys.net.netlink import RtScope +from atf_python.sys.net.vnet import SingleVnetTestTemplate + + +class TestRtNlIfaddr(SingleVnetTestTemplate, NetlinkTestTemplate): + def setup_method(self, method): + method_name = method.__name__ + if "4" in method_name: + self.IPV4_PREFIXES = ["192.0.2.1/24"] + if "6" in method_name: + self.IPV6_PREFIXES = ["2001:db8::1/64"] + super().setup_method(method) + self.setup_netlink(NlConst.NETLINK_ROUTE) + + def test_46_nofilter(self): + """Tests that listing outputs both IPv4/IPv6 and interfaces""" + msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value) + msg.nl_hdr.nlmsg_flags = ( + NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value + ) + self.write_message(msg) + + ret = [] + for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR): + ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index) + family = rx_msg.base_hdr.ifa_family + ret.append((ifname, family, rx_msg)) + + ifname = "lo0" + assert len([r for r in ret if r[0] == ifname]) > 0 + + ifname = self.vnet.iface_alias_map["if1"].name + assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET]) == 1 + assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2 + + def test_46_filter_iface(self): + """Tests that listing outputs both IPv4/IPv6 for the specific interface""" + epair_ifname = self.vnet.iface_alias_map["if1"].name + + msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value) + msg.nl_hdr.nlmsg_flags = ( + NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value + ) + msg.base_hdr.ifa_index = socket.if_nametoindex(epair_ifname) + self.write_message(msg) + + ret = [] + for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR): + ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index) + family = rx_msg.base_hdr.ifa_family + ret.append((ifname, family, rx_msg)) + + ifname = epair_ifname + assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET]) == 1 + assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2 + assert len(ret) == 3 + + def filter_iface_family(self, family, num_items): + """Tests that listing outputs IPv4 for the specific interface""" + epair_ifname = self.vnet.iface_alias_map["if1"].name + + msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value) + msg.nl_hdr.nlmsg_flags = ( + NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value + ) + msg.base_hdr.ifa_family = family + msg.base_hdr.ifa_index = socket.if_nametoindex(epair_ifname) + self.write_message(msg) + + ret = [] + for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR): + assert family == rx_msg.base_hdr.ifa_family + assert epair_ifname == socket.if_indextoname(rx_msg.base_hdr.ifa_index) + ret.append(rx_msg) + assert len(ret) == num_items + return ret + + def test_4_broadcast(self): + """Tests header/attr output for listing IPv4 ifas on broadcast iface""" + ret = self.filter_iface_family(socket.AF_INET, 1) + # Should be 192.0.2.1/24 + msg = ret[0] + # Family and ifindex has been checked already + assert msg.base_hdr.ifa_prefixlen == 24 + # Ignore IFA_FLAGS for now + assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value + + assert msg.get_nla(IfattrType.IFA_ADDRESS).addr == "192.0.2.1" + assert msg.get_nla(IfattrType.IFA_LOCAL).addr == "192.0.2.1" + assert msg.get_nla(IfattrType.IFA_BROADCAST).addr == "192.0.2.255" + + epair_ifname = self.vnet.iface_alias_map["if1"].name + assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname + + def test_6_broadcast(self): + """Tests header/attr output for listing IPv6 ifas on broadcast iface""" + ret = self.filter_iface_family(socket.AF_INET6, 2) + # Should be 192.0.2.1/24 + if ret[0].base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value: + (gmsg, lmsg) = ret + else: + (lmsg, gmsg) = ret + # Start with global ( 2001:db8::1/64 ) + msg = gmsg + # Family and ifindex has been checked already + assert msg.base_hdr.ifa_prefixlen == 64 + # Ignore IFA_FLAGS for now + assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value + + assert msg.get_nla(IfattrType.IFA_ADDRESS).addr == "2001:db8::1" + assert msg.get_nla(IfattrType.IFA_LOCAL) is None + assert msg.get_nla(IfattrType.IFA_BROADCAST) is None + + epair_ifname = self.vnet.iface_alias_map["if1"].name + assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname + + # Local: fe80::/64 + msg = lmsg + assert msg.base_hdr.ifa_prefixlen == 64 + # Ignore IFA_FLAGS for now + assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_LINK.value + + addr = ipaddress.ip_address(msg.get_nla(IfattrType.IFA_ADDRESS).addr) + assert addr.is_link_local + # Verify that ifindex is not emmbedded + assert struct.unpack("!H", addr.packed[2:4])[0] == 0 + assert msg.get_nla(IfattrType.IFA_LOCAL) is None + assert msg.get_nla(IfattrType.IFA_BROADCAST) is None + + epair_ifname = self.vnet.iface_alias_map["if1"].name + assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname