diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index 5fbcd3c31669..7724138d3298 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -1,1158 +1,1160 @@ # $FreeBSD$ # # Please see the file src/etc/mtree/README before making changes to this file. # /set type=dir uname=root gname=wheel mode=0755 tags=package=tests . bin cat .. chflags .. chmod .. cp .. date .. dd .. echo .. expr .. ln .. ls .. mkdir .. mv .. pax .. pkill .. pwait .. rm .. rmdir .. sh builtins .. errors .. execution .. expansion .. invocation .. parameters .. parser .. set-e .. .. sleep .. test .. .. cddl lib .. sbin .. usr.bin ctfconvert .. ztest .. .. usr.sbin dtrace common aggs .. arithmetic .. arrays .. assocs .. begin .. bitfields .. buffering .. builtinvar .. cg .. clauses .. cpc .. decls .. drops .. dtraceUtil .. end .. env .. enum .. error .. exit .. fbtprovider .. funcs .. grammar .. include .. inline .. io .. ip .. java_api .. json .. lexer .. llquantize .. mdb .. mib .. misc .. multiaggs .. offsetof .. operators .. pid .. plockstat .. pointers .. pragma .. predicates .. preprocessor .. print .. printa .. printf .. privs .. probes .. proc .. profile-n .. providers .. raise .. rates .. safety .. scalars .. sched .. scripting .. sdt .. sizeof .. speculation .. stability .. stack .. stackdepth .. stop .. strlen .. strtoll .. struct .. sugar .. syscall .. sysevent .. tick-n .. trace .. tracemem .. translators .. typedef .. types .. uctf .. union .. usdt .. ustack .. vars .. version .. .. i386 arrays .. funcs .. pid .. ustack .. .. amd64 arrays .. kinst .. .. .. zfsd .. .. .. etc rc.d .. .. games .. gnu lib .. usr.bin diff .. .. .. lib atf libatf-c detail .. .. libatf-c++ detail .. .. test-programs .. .. csu dynamic .. dynamiclib .. static .. .. googletest gmock .. gmock_main .. gtest .. gtest_main .. .. libarchive .. libbe .. libc c063 .. db .. gen execve .. posix_spawn .. .. hash data .. .. iconv .. inet .. locale .. net getaddrinfo data .. .. .. nss .. regex data .. .. resolv .. rpc .. ssp .. setjmp .. stdio .. stdlib .. string .. sys .. time .. tls dso .. .. termios .. ttyio .. .. libcam .. libcasper services cap_dns .. cap_grp .. cap_pwd .. cap_sysctl .. .. .. libcrypt .. libdevdctl .. libexecinfo .. libkvm .. libmp .. libnv .. libproc .. libregex data .. .. librt .. libsbuf .. libsysdecode .. libthr dlopen .. .. libutil .. libxo .. msun .. .. libexec atf atf-check .. atf-pytest-wrapper .. atf-sh .. .. rc .. rtld-elf .. tftpd .. .. sbin bectl .. dhclient .. devd .. growfs .. ifconfig .. md5 .. mdconfig .. newfs_msdos .. nvmecontrol .. pfctl files .. .. ping .. route .. .. secure lib .. libexec .. usr.bin .. usr.sbin .. .. share examples tests atf .. googletest .. plain .. tap .. .. .. zoneinfo .. .. sys acl .. aio .. audit .. auditpipe .. capsicum .. cddl zfs bin .. include .. tests acl cifs .. nontrivial .. trivial .. .. atime .. bootfs .. cache .. cachefile .. clean_mirror .. cli_root zfs_upgrade .. zfs_promote .. zfs_clone .. zfs_property .. zfs_destroy .. zpool_create .. zpool_history .. zpool_expand .. zpool_remove .. zfs_mount .. zfs_unshare .. zdb .. zpool_online .. zpool_get .. zpool_export .. zfs_copies .. zfs_get .. zfs .. zpool_clear .. zpool_import blockfiles .. .. zpool .. zpool_offline .. zpool_replace .. zfs_rollback .. zpool_set .. zfs_send .. zfs_set .. zpool_detach .. zfs_diff .. zpool_scrub .. zfs_inherit .. zfs_snapshot .. zfs_share .. zpool_destroy .. zpool_status .. zfs_unmount .. zfs_receive .. zfs_create .. zpool_upgrade blockfiles .. .. zpool_add .. zfs_rename .. zpool_attach .. zfs_reservation .. .. cli_user misc .. zfs_list .. zpool_iostat .. zpool_list .. .. compression .. ctime .. delegate .. devices .. exec .. grow_pool .. grow_replicas .. history .. hotplug .. hotspare .. inheritance .. interop .. inuse .. iscsi .. large_files .. largest_pool .. link_count .. migration .. mmap .. mount .. mv_files .. nestedfs .. no_space .. online_offline .. pool_names .. poolversion .. quota .. redundancy .. refquota .. refreserv .. rename_dirs .. replacement .. reservation .. rootpool .. rsend .. scrub_mirror .. slog .. snapshot .. snapused .. sparse .. threadsappend .. truncate .. txg_integrity .. userquota .. utils_test .. write_dirs .. xattr .. zfsd .. zil .. zinject .. zones .. zvol zvol_ENOSPC .. zvol_cli .. zvol_misc .. zvol_swap .. .. zvol_thrash .. .. .. .. devrandom .. dtrace .. fifo .. file .. fs fusefs .. tmpfs .. .. geom class concat .. eli .. gate .. gpt .. mirror .. multipath .. nop .. part .. raid3 .. shsec .. stripe .. uzip etalon .. .. .. .. kern acct .. execve .. pipe .. .. kqueue libkqueue .. .. mac bsdextended .. portacl .. .. mqueue .. net if_ovpn .. routing .. .. netgraph .. netinet .. netinet6 frag6 .. .. netipsec tunnel .. .. + netlink + .. netmap .. netpfil common .. pf ioctl .. .. .. opencrypto .. pjdfstest chflags .. chmod .. chown .. ftruncate .. granular .. link .. mkdir .. mkfifo .. mknod .. open .. rename .. rmdir .. symlink .. truncate .. unlink .. utimensat .. .. posixshm .. sys .. vfs .. vm .. vmm .. .. usr.bin apply .. awk bugs-fixed .. netbsd .. .. basename .. bintrans .. bmake archives fmt_44bsd .. fmt_44bsd_mod .. fmt_oldbsd .. .. basic t0 .. t1 .. t2 .. t3 .. .. execution ellipsis .. empty .. joberr .. plus .. .. shell builtin .. meta .. path .. path_select .. replace .. select .. .. suffixes basic .. src_wild1 .. src_wild2 .. .. syntax directive-t0 .. enl .. funny-targets .. semi .. .. sysmk t0 2 1 .. .. mk .. .. t1 2 1 .. .. mk .. .. t2 2 1 .. .. mk .. .. .. variables modifier_M .. modifier_t .. opt_V .. t0 .. .. .. bsdcat .. calendar .. cmp .. compress .. cpio .. col .. comm .. csplit .. cut .. dc .. diff .. diff3 .. dirname .. du .. file2c .. file .. find .. fold .. getconf .. gh-bc .. grep .. gzip .. head .. hexdump .. ident .. indent .. join .. jot .. lastcomm .. limits .. locale .. m4 .. mkimg .. mktemp .. ncal .. opensm .. patch .. pr .. printf .. procstat .. renice .. rs .. sdiff .. sed regress.multitest.out .. .. seq .. soelim .. sort .. split .. stat .. tail .. tar .. timeout .. tr .. truncate .. units .. unifdef .. uniq .. vmstat .. xargs .. xinstall .. xo .. yacc yacc .. .. .. usr.sbin chown .. daemon .. etcupdate .. extattr .. fstyp .. jail .. makefs .. mixer .. newsyslog .. nmtree .. praudit .. pw .. rpcbind .. sa .. .. .. # vim: set expandtab ts=4 sw=4: diff --git a/share/man/man4/rtnetlink.4 b/share/man/man4/rtnetlink.4 index 9f20671719f0..a06807809691 100644 --- a/share/man/man4/rtnetlink.4 +++ b/share/man/man4/rtnetlink.4 @@ -1,519 +1,521 @@ .\" .\" 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 .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 .It Dv IFA_BROADCAST (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/netlink_message_parser.c b/sys/netlink/netlink_message_parser.c index d33eddb800e4..451d9d497491 100644 --- a/sys/netlink/netlink_message_parser.c +++ b/sys/netlink/netlink_message_parser.c @@ -1,472 +1,478 @@ /*- * 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 #define DEBUG_MOD_NAME nl_parser #define DEBUG_MAX_LEVEL LOG_DEBUG3 #include _DECLARE_DEBUG(LOG_DEBUG); bool nlmsg_report_err_msg(struct nl_pstate *npt, const char *fmt, ...) { va_list ap; if (npt->err_msg != NULL) return (false); char *buf = npt_alloc(npt, NL_MAX_ERROR_BUF); if (buf == NULL) return (false); va_start(ap, fmt); vsnprintf(buf, NL_MAX_ERROR_BUF, fmt, ap); va_end(ap); npt->err_msg = buf; return (true); } bool nlmsg_report_err_offset(struct nl_pstate *npt, uint32_t off) { if (npt->err_off != 0) return (false); npt->err_off = off; return (true); } static const struct nlattr_parser * search_states(const struct nlattr_parser *ps, int pslen, int key) { int left_i = 0, right_i = pslen - 1; if (key < ps[0].type || key > ps[pslen - 1].type) return (NULL); while (left_i + 1 < right_i) { int mid_i = (left_i + right_i) / 2; if (key < ps[mid_i].type) right_i = mid_i; else if (key > ps[mid_i].type) left_i = mid_i + 1; else return (&ps[mid_i]); } if (ps[left_i].type == key) return (&ps[left_i]); else if (ps[right_i].type == key) return (&ps[right_i]); return (NULL); } int nl_parse_attrs_raw(struct nlattr *nla_head, int len, const struct nlattr_parser *ps, int pslen, struct nl_pstate *npt, void *target) { struct nlattr *nla = NULL; int error = 0; NL_LOG(LOG_DEBUG3, "parse %p remaining_len %d", nla_head, len); int orig_len = len; NLA_FOREACH(nla, nla_head, len) { NL_LOG(LOG_DEBUG3, ">> parsing %p attr_type %d len %d (rem %d)", nla, nla->nla_type, nla->nla_len, len); if (nla->nla_len < sizeof(struct nlattr)) { NLMSG_REPORT_ERR_MSG(npt, "Invalid attr %p type %d len: %d", nla, nla->nla_type, nla->nla_len); uint32_t off = (char *)nla - (char *)npt->hdr; nlmsg_report_err_offset(npt, off); return (EINVAL); } int nla_type = nla->nla_type & NLA_TYPE_MASK; const struct nlattr_parser *s = search_states(ps, pslen, nla_type); if (s != NULL) { void *ptr = (void *)((char *)target + s->off); error = s->cb(nla, npt, s->arg, ptr); if (error != 0) { uint32_t off = (char *)nla - (char *)npt->hdr; nlmsg_report_err_offset(npt, off); NL_LOG(LOG_DEBUG3, "parse failed att offset %u", off); return (error); } } else { /* Ignore non-specified attributes */ NL_LOG(LOG_DEBUG3, "ignoring attr %d", nla->nla_type); } } if (len >= sizeof(struct nlattr)) { nla = (struct nlattr *)((char *)nla_head + (orig_len - len)); NL_LOG(LOG_DEBUG3, " >>> end %p attr_type %d len %d", nla, nla->nla_type, nla->nla_len); } NL_LOG(LOG_DEBUG3, "end parse: %p remaining_len %d", nla, len); return (0); } -int -nl_parse_attrs(struct nlmsghdr *hdr, int hdrlen, struct nlattr_parser *ps, int pslen, - struct nl_pstate *npt, void *target) +void +nl_get_attrs_bmask_raw(struct nlattr *nla_head, int len, struct nlattr_bmask *bm) { - int off = NLMSG_HDRLEN + NETLINK_ALIGN(hdrlen); - int len = hdr->nlmsg_len - off; - struct nlattr *nla_head = (struct nlattr *)((char *)hdr + off); + struct nlattr *nla = NULL; + + bzero(bm->mask, sizeof(bm->mask)); - return (nl_parse_attrs_raw(nla_head, len, ps, pslen, npt, target)); + NLA_FOREACH(nla, nla_head, len) { + if (nla->nla_len < sizeof(struct nlattr)) + return; + int nla_type = nla->nla_type & NLA_TYPE_MASK; + if (nla_type <= sizeof(bm->mask) * 8) + bm->mask[nla_type / 8] |= 1 << (nla_type % 8); + } } + int nlattr_get_flag(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { if (__predict_false(NLA_DATA_LEN(nla) != 0)) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not a flag", nla->nla_type, NLA_DATA_LEN(nla)); return (EINVAL); } *((uint8_t *)target) = 1; return (0); } static struct sockaddr * parse_rta_ip4(void *rta_data, struct nl_pstate *npt, int *perror) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)npt_alloc_sockaddr(npt, sizeof(struct sockaddr_in)); if (__predict_false(sin == NULL)) { *perror = ENOBUFS; return (NULL); } sin->sin_len = sizeof(struct sockaddr_in); sin->sin_family = AF_INET; memcpy(&sin->sin_addr, rta_data, sizeof(struct in_addr)); return ((struct sockaddr *)sin); } static struct sockaddr * parse_rta_ip6(void *rta_data, struct nl_pstate *npt, int *perror) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)npt_alloc_sockaddr(npt, sizeof(struct sockaddr_in6)); if (__predict_false(sin6 == NULL)) { *perror = ENOBUFS; return (NULL); } sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; memcpy(&sin6->sin6_addr, rta_data, sizeof(struct in6_addr)); return ((struct sockaddr *)sin6); } static struct sockaddr * parse_rta_ip(struct rtattr *rta, struct nl_pstate *npt, int *perror) { void *rta_data = NL_RTA_DATA(rta); int rta_len = NL_RTA_DATA_LEN(rta); if (rta_len == sizeof(struct in_addr)) { return (parse_rta_ip4(rta_data, npt, perror)); } else if (rta_len == sizeof(struct in6_addr)) { return (parse_rta_ip6(rta_data, npt, perror)); } else { NLMSG_REPORT_ERR_MSG(npt, "unknown IP len: %d for rta type %d", rta_len, rta->rta_type); *perror = ENOTSUP; return (NULL); } return (NULL); } int nlattr_get_ip(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { int error = 0; struct sockaddr *sa = parse_rta_ip((struct rtattr *)nla, npt, &error); *((struct sockaddr **)target) = sa; return (error); } static struct sockaddr * parse_rta_via(struct rtattr *rta, struct nl_pstate *npt, int *perror) { struct rtvia *via = NL_RTA_DATA(rta); int data_len = NL_RTA_DATA_LEN(rta); if (__predict_false(data_len) < sizeof(struct rtvia)) { NLMSG_REPORT_ERR_MSG(npt, "undersized RTA_VIA(%d) attr: len %d", rta->rta_type, data_len); *perror = EINVAL; return (NULL); } data_len -= offsetof(struct rtvia, rtvia_addr); switch (via->rtvia_family) { case AF_INET: if (__predict_false(data_len < sizeof(struct in_addr))) { *perror = EINVAL; return (NULL); } return (parse_rta_ip4(via->rtvia_addr, npt, perror)); case AF_INET6: if (__predict_false(data_len < sizeof(struct in6_addr))) { *perror = EINVAL; return (NULL); } return (parse_rta_ip6(via->rtvia_addr, npt, perror)); default: *perror = ENOTSUP; return (NULL); } } int nlattr_get_ipvia(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { int error = 0; struct sockaddr *sa = parse_rta_via((struct rtattr *)nla, npt, &error); *((struct sockaddr **)target) = sa; return (error); } int nlattr_get_uint16(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { if (__predict_false(NLA_DATA_LEN(nla) != sizeof(uint16_t))) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not uint32", nla->nla_type, NLA_DATA_LEN(nla)); return (EINVAL); } *((uint16_t *)target) = *((const uint16_t *)NL_RTA_DATA_CONST(nla)); return (0); } int nlattr_get_uint32(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { if (__predict_false(NLA_DATA_LEN(nla) != sizeof(uint32_t))) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not uint32", nla->nla_type, NLA_DATA_LEN(nla)); return (EINVAL); } *((uint32_t *)target) = *((const uint32_t *)NL_RTA_DATA_CONST(nla)); return (0); } int nlattr_get_uint64(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { if (__predict_false(NLA_DATA_LEN(nla) != sizeof(uint64_t))) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not uint64", nla->nla_type, NLA_DATA_LEN(nla)); return (EINVAL); } memcpy(target, NL_RTA_DATA_CONST(nla), sizeof(uint64_t)); return (0); } static int nlattr_get_ifp_internal(struct nlattr *nla, struct nl_pstate *npt, void *target, bool zero_ok) { if (__predict_false(NLA_DATA_LEN(nla) != sizeof(uint32_t))) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not uint32", nla->nla_type, NLA_DATA_LEN(nla)); return (EINVAL); } uint32_t ifindex = *((const uint32_t *)NLA_DATA_CONST(nla)); if (ifindex == 0 && zero_ok) { *((struct ifnet **)target) = NULL; return (0); } NET_EPOCH_ASSERT(); struct ifnet *ifp = ifnet_byindex(ifindex); if (__predict_false(ifp == NULL)) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d: ifindex %u invalid", nla->nla_type, ifindex); return (ENOENT); } *((struct ifnet **)target) = ifp; NL_LOG(LOG_DEBUG3, "nla type %d: ifindex %u -> %s", nla->nla_type, ifindex, if_name(ifp)); return (0); } int nlattr_get_ifp(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { return (nlattr_get_ifp_internal(nla, npt, target, false)); } int nlattr_get_ifpz(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { return (nlattr_get_ifp_internal(nla, npt, target, true)); } int nlattr_get_string(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { int maxlen = NLA_DATA_LEN(nla); if (__predict_false(strnlen((char *)NLA_DATA(nla), maxlen) >= maxlen)) { NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not NULL-terminated", nla->nla_type, maxlen); return (EINVAL); } *((char **)target) = (char *)NLA_DATA(nla); return (0); } int nlattr_get_stringn(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { int maxlen = NLA_DATA_LEN(nla); char *buf = npt_alloc(npt, maxlen + 1); if (buf == NULL) return (ENOMEM); buf[maxlen] = '\0'; memcpy(buf, NLA_DATA(nla), maxlen); *((char **)target) = buf; return (0); } int nlattr_get_nla(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { NL_LOG(LOG_DEBUG3, "STORING %p len %d", nla, nla->nla_len); *((struct nlattr **)target) = nla; return (0); } int nlattr_get_nested(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { const struct nlhdr_parser *p = (const struct nlhdr_parser *)arg; int error; /* Assumes target points to the beginning of the structure */ error = nl_parse_header(NLA_DATA(nla), NLA_DATA_LEN(nla), p, npt, target); return (error); } int nlf_get_ifp(void *src, struct nl_pstate *npt, void *target) { int ifindex = *((const int *)src); NET_EPOCH_ASSERT(); struct ifnet *ifp = ifnet_byindex(ifindex); if (ifp == NULL) { NL_LOG(LOG_DEBUG, "ifindex %u invalid", ifindex); return (ENOENT); } *((struct ifnet **)target) = ifp; return (0); } int nlf_get_ifpz(void *src, struct nl_pstate *npt, void *target) { int ifindex = *((const int *)src); NET_EPOCH_ASSERT(); struct ifnet *ifp = ifnet_byindex(ifindex); if (ifindex != 0 && ifp == NULL) { NL_LOG(LOG_DEBUG, "ifindex %u invalid", ifindex); return (ENOENT); } *((struct ifnet **)target) = ifp; return (0); } int nlf_get_u8(void *src, struct nl_pstate *npt, void *target) { uint8_t val = *((const uint8_t *)src); *((uint8_t *)target) = val; return (0); } int nlf_get_u8_u32(void *src, struct nl_pstate *npt, void *target) { *((uint32_t *)target) = *((const uint8_t *)src); return (0); } int nlf_get_u16(void *src, struct nl_pstate *npt, void *target) { *((uint16_t *)target) = *((const uint16_t *)src); return (0); } int nlf_get_u32(void *src, struct nl_pstate *npt, void *target) { *((uint32_t *)target) = *((const uint32_t *)src); return (0); } diff --git a/sys/netlink/netlink_message_parser.h b/sys/netlink/netlink_message_parser.h index b23b223ef80e..96fd1c7337b7 100644 --- a/sys/netlink/netlink_message_parser.h +++ b/sys/netlink/netlink_message_parser.h @@ -1,274 +1,292 @@ /*- * 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. */ #ifndef _NETLINK_NETLINK_MESSAGE_PARSER_H_ #define _NETLINK_NETLINK_MESSAGE_PARSER_H_ #ifdef _KERNEL /* * It is not meant to be included directly */ /* Parsing state */ struct linear_buffer { char *base; /* Base allocated memory pointer */ uint32_t offset; /* Currently used offset */ uint32_t size; /* Total buffer size */ }; static inline void * lb_alloc(struct linear_buffer *lb, int len) { len = roundup2(len, sizeof(uint64_t)); if (lb->offset + len > lb->size) return (NULL); void *data = (void *)(lb->base + lb->offset); lb->offset += len; return (data); } static inline void lb_clear(struct linear_buffer *lb) { memset(lb->base, 0, lb->size); lb->offset = 0; } #define NL_MAX_ERROR_BUF 128 #define SCRATCH_BUFFER_SIZE (1024 + NL_MAX_ERROR_BUF) struct nl_pstate { struct linear_buffer lb; /* Per-message scratch buffer */ struct nlpcb *nlp; /* Originator socket */ struct nl_writer *nw; /* Message writer to use */ struct nlmsghdr *hdr; /* Current parsed message header */ uint32_t err_off; /* error offset from hdr start */ int error; /* last operation error */ char *err_msg; /* Description of last error */ bool strict; /* Strict parsing required */ }; static inline void * npt_alloc(struct nl_pstate *npt, int len) { return (lb_alloc(&npt->lb, len)); } #define npt_alloc_sockaddr(_npt, _len) ((struct sockaddr *)(npt_alloc(_npt, _len))) typedef int parse_field_f(void *hdr, struct nl_pstate *npt, void *target); struct nlfield_parser { uint16_t off_in; uint16_t off_out; parse_field_f *cb; }; static const struct nlfield_parser nlf_p_empty[] = {}; int nlf_get_ifp(void *src, struct nl_pstate *npt, void *target); int nlf_get_ifpz(void *src, struct nl_pstate *npt, void *target); int nlf_get_u8(void *src, struct nl_pstate *npt, void *target); int nlf_get_u16(void *src, struct nl_pstate *npt, void *target); int nlf_get_u32(void *src, struct nl_pstate *npt, void *target); int nlf_get_u8_u32(void *src, struct nl_pstate *npt, void *target); struct nlattr_parser; typedef int parse_attr_f(struct nlattr *attr, struct nl_pstate *npt, const void *arg, void *target); struct nlattr_parser { uint16_t type; /* Attribute type */ uint16_t off; /* field offset in the target structure */ parse_attr_f *cb; /* parser function to call */ const void *arg; }; typedef bool strict_parser_f(void *hdr, struct nl_pstate *npt); struct nlhdr_parser { int nl_hdr_off; /* aligned netlink header size */ int out_hdr_off; /* target header size */ int fp_size; int np_size; const struct nlfield_parser *fp; /* array of header field parsers */ const struct nlattr_parser *np; /* array of attribute parsers */ strict_parser_f *sp; /* Parser function */ }; #define NL_DECLARE_PARSER(_name, _t, _fp, _np) \ static const struct nlhdr_parser _name = { \ .nl_hdr_off = sizeof(_t), \ .fp = &((_fp)[0]), \ .np = &((_np)[0]), \ .fp_size = NL_ARRAY_LEN(_fp), \ .np_size = NL_ARRAY_LEN(_np), \ } #define NL_DECLARE_STRICT_PARSER(_name, _t, _sp, _fp, _np)\ static const struct nlhdr_parser _name = { \ .nl_hdr_off = sizeof(_t), \ .fp = &((_fp)[0]), \ .np = &((_np)[0]), \ .fp_size = NL_ARRAY_LEN(_fp), \ .np_size = NL_ARRAY_LEN(_np), \ .sp = _sp, \ } #define NL_DECLARE_ARR_PARSER(_name, _t, _o, _fp, _np) \ static const struct nlhdr_parser _name = { \ .nl_hdr_off = sizeof(_t), \ .out_hdr_off = sizeof(_o), \ .fp = &((_fp)[0]), \ .np = &((_np)[0]), \ .fp_size = NL_ARRAY_LEN(_fp), \ .np_size = NL_ARRAY_LEN(_np), \ } #define NL_DECLARE_ATTR_PARSER(_name, _np) \ static const struct nlhdr_parser _name = { \ .np = &((_np)[0]), \ .np_size = NL_ARRAY_LEN(_np), \ } -struct nlarr_hdr { - int num_items; - int max_items; +struct nlattr_bmask { + uint64_t mask[2]; }; +static inline bool +nl_has_attr(const struct nlattr_bmask *bm, unsigned int attr_type) +{ + MPASS(attr_type < sizeof(bm->mask) * 8); + + return ((bm->mask[attr_type / 8] & (1 << (attr_type % 8)))); +} +void nl_get_attrs_bmask_raw(struct nlattr *nla_head, int len, struct nlattr_bmask *bm); + int nl_parse_attrs_raw(struct nlattr *nla_head, int len, const struct nlattr_parser *ps, int pslen, struct nl_pstate *npt, void *target); -int nl_parse_attrs(struct nlmsghdr *hdr, int hdrlen, struct nlattr_parser *ps, - int pslen, struct nl_pstate *npt, void *target); int nlattr_get_flag(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_ip(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_uint16(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_uint32(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_uint64(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_ifp(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_ifpz(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_ipvia(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_string(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_stringn(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_nla(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); int nlattr_get_nested(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target); bool nlmsg_report_err_msg(struct nl_pstate *npt, const char *fmt, ...); #define NLMSG_REPORT_ERR_MSG(_npt, _fmt, ...) { \ nlmsg_report_err_msg(_npt, _fmt, ## __VA_ARGS__); \ NLP_LOG(LOG_DEBUG, (_npt)->nlp, _fmt, ## __VA_ARGS__); \ } bool nlmsg_report_err_offset(struct nl_pstate *npt, uint32_t off); /* * Have it inline so compiler can optimize field accesses into * the list of direct function calls without iteration. */ static inline int nl_parse_header(void *hdr, int len, const struct nlhdr_parser *parser, struct nl_pstate *npt, void *target) { int error; if (__predict_false(len < parser->nl_hdr_off)) { nlmsg_report_err_msg(npt, "header too short: expected %d, got %d", parser->nl_hdr_off, len); return (EINVAL); } if (npt->strict && parser->sp != NULL && !parser->sp(hdr, npt)) return (EINVAL); /* Extract fields first */ for (int i = 0; i < parser->fp_size; i++) { const struct nlfield_parser *fp = &parser->fp[i]; void *src = (char *)hdr + fp->off_in; void *dst = (char *)target + fp->off_out; error = fp->cb(src, npt, dst); if (error != 0) return (error); } struct nlattr *nla_head = (struct nlattr *)((char *)hdr + parser->nl_hdr_off); error = nl_parse_attrs_raw(nla_head, len - parser->nl_hdr_off, parser->np, parser->np_size, npt, target); return (error); } static inline int nl_parse_nested(struct nlattr *nla, const struct nlhdr_parser *parser, struct nl_pstate *npt, void *target) { struct nlattr *nla_head = (struct nlattr *)NLA_DATA(nla); return (nl_parse_attrs_raw(nla_head, NLA_DATA_LEN(nla), parser->np, parser->np_size, npt, target)); } /* * Checks that attributes are sorted by attribute type. */ static inline void nl_verify_parsers(const struct nlhdr_parser **parser, int count) { #ifdef INVARIANTS for (int i = 0; i < count; i++) { const struct nlhdr_parser *p = parser[i]; int attr_type = 0; for (int j = 0; j < p->np_size; j++) { MPASS(p->np[j].type > attr_type); attr_type = p->np[j].type; } } #endif } void nl_verify_parsers(const struct nlhdr_parser **parser, int count); #define NL_VERIFY_PARSERS(_p) nl_verify_parsers((_p), NL_ARRAY_LEN(_p)) static inline int nl_parse_nlmsg(struct nlmsghdr *hdr, const struct nlhdr_parser *parser, struct nl_pstate *npt, void *target) { return (nl_parse_header(hdr + 1, hdr->nlmsg_len - sizeof(*hdr), parser, npt, target)); } +static inline void +nl_get_attrs_bmask_nlmsg(struct nlmsghdr *hdr, const struct nlhdr_parser *parser, + struct nlattr_bmask *bm) +{ + struct nlattr *nla_head; + + nla_head = (struct nlattr *)((char *)(hdr + 1) + parser->nl_hdr_off); + int len = hdr->nlmsg_len - sizeof(*hdr) - parser->nl_hdr_off; + + nl_get_attrs_bmask_raw(nla_head, len, bm); +} + #endif #endif diff --git a/sys/netlink/route/iface.c b/sys/netlink/route/iface.c index 579869e9662c..b033ba71009d 100644 --- a/sys/netlink/route/iface.c +++ b/sys/netlink/route/iface.c @@ -1,900 +1,1040 @@ /*- * 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 | NLM_F_MULTI, + .hdr.nlmsg_flags = hdr->nlmsg_flags, .hdr.nlmsg_type = NL_RTM_NEWLINK, }; - /* Fast track for an interface w/ explicit index match */ - if (attrs.ifi_index != 0) { - NET_EPOCH_ENTER(et); - ifp = ifnet_byindex_ref(attrs.ifi_index); - NET_EPOCH_EXIT(et); - NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching index %u", attrs.ifi_index); + /* 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 = ESRCH; + error = ENODEV; if_rele(ifp); } else - error = ESRCH; + 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 -rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt) +create_link(struct nlmsghdr *hdr, struct nl_parsed_link *lattrs, + struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt) { - struct nl_cloner *cloner; - int error; + 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); + } - struct nl_parsed_link attrs = {}; - error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs); - if (error != 0) - return (error); + 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 (attrs.ifla_ifname == NULL || strlen(attrs.ifla_ifname) == 0) { - /* Applications like ip(8) verify RTM_NEWLINK existance - * by calling it with empty arguments. Always return "innocent" - * error. + 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 IFLA_IFNAME attribute"); + NLMSG_REPORT_ERR_MSG(npt, "empty ifi_index field"); return (EPERM); } - if (attrs.ifla_cloner == NULL || strlen(attrs.ifla_cloner) == 0) { - NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_INFO_KIND attribute"); - return (EINVAL); + 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); + } } - sx_slock(&rtnl_cloner_lock); - SLIST_FOREACH(cloner, &nl_cloners, next) { - if (!strcmp(attrs.ifla_cloner, cloner->name)) { - error = cloner->create_f(&attrs, nlp, npt); - sx_sunlock(&rtnl_cloner_lock); - return (error); + 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); - /* TODO: load cloner module if not exists & privilege permits */ - NLMSG_REPORT_ERR_MSG(npt, "interface type %s not supported", attrs.ifla_cloner); - return (ENOTSUP); + 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)); +} + /* {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; struct sockaddr *dst_sa = ifa->ifa_dstaddr; if ((dst_sa == NULL) || (dst_sa->sa_family != sa->sa_family)) dst_sa = sa; dump_sa(nw, IFA_ADDRESS, dst_sa); dump_sa(nw, IFA_LOCAL, sa); 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) { struct ifaddr *ifa; struct ifnet *ifp; int error = 0; 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 | 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; } 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 }; 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) +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/iface_drivers.c b/sys/netlink/route/iface_drivers.c index ccc8f2184fa3..7f098b808743 100644 --- a/sys/netlink/route/iface_drivers.c +++ b/sys/netlink/route/iface_drivers.c @@ -1,165 +1,263 @@ /*- * 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 #include /* scope deembedding */ #define DEBUG_MOD_NAME nl_iface_drivers #define DEBUG_MAX_LEVEL LOG_DEBUG3 #include _DECLARE_DEBUG(LOG_DEBUG); +/* + * Generic modification interface handler. + * Responsible for changing network stack interface attributes + * such as state, mtu or description. + */ +static int +modify_generic(struct ifnet *ifp, struct nl_parsed_link *lattrs, + const struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt) +{ + int error; + + if (lattrs->ifla_ifalias != NULL) { + if (nlp_has_priv(nlp, PRIV_NET_SETIFDESCR)) { + int len = strlen(lattrs->ifla_ifalias) + 1; + char *buf = if_allocdescr(len, true); + + memcpy(buf, lattrs->ifla_ifalias, len); + if_setdescr(ifp, buf); + getmicrotime(&ifp->if_lastchange); + } else { + nlmsg_report_err_msg(npt, "Not enough privileges to set descr"); + return (EPERM); + } + } + + if ((lattrs->ifi_change & IFF_UP) && (lattrs->ifi_flags & IFF_UP) == 0) { + /* Request to down the interface */ + if_down(ifp); + } + + if (lattrs->ifla_mtu > 0) { + if (nlp_has_priv(nlp, PRIV_NET_SETIFMTU)) { + struct ifreq ifr = { .ifr_mtu = lattrs->ifla_mtu }; + error = ifhwioctl(SIOCSIFMTU, ifp, (char *)&ifr, curthread); + } else { + nlmsg_report_err_msg(npt, "Not enough privileges to set mtu"); + return (EPERM); + } + } + + if (lattrs->ifi_change & IFF_PROMISC) { + error = ifpromisc(ifp, lattrs->ifi_flags & IFF_PROMISC); + if (error != 0) { + nlmsg_report_err_msg(npt, "unable to set promisc"); + return (error); + } + } + + return (0); +} + +/* + * Generic creation interface handler. + * Responsible for creating interfaces w/o parameters and setting + * misc attributes such as state, mtu or description. + */ +static int +create_generic(struct nl_parsed_link *lattrs, const struct nlattr_bmask *bm, + struct nlpcb *nlp, struct nl_pstate *npt) +{ + int error = 0; + + struct ifc_data ifd = {}; + struct ifnet *ifp = NULL; + error = ifc_create_ifp(lattrs->ifla_ifname, &ifd, &ifp); + + NLP_LOG(LOG_DEBUG2, nlp, "clone for %s returned %d", lattrs->ifla_ifname, error); + + if (error == 0) { + struct epoch_tracker et; + + NET_EPOCH_ENTER(et); + bool success = if_try_ref(ifp); + NET_EPOCH_EXIT(et); + if (!success) + return (EINVAL); + error = modify_generic(ifp, lattrs, bm, nlp, npt); + if_rele(ifp); + } + + return (error); +} + +struct nl_cloner generic_cloner = { + .name = "_default_", + .create_f = create_generic, + .modify_f = modify_generic, +}; + /* * * {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 */ struct nl_parsed_vlan { uint16_t vlan_id; uint16_t vlan_proto; struct ifla_vlan_flags vlan_flags; }; #define _OUT(_field) offsetof(struct nl_parsed_vlan, _field) static const struct nlattr_parser nla_p_vlan[] = { { .type = IFLA_VLAN_ID, .off = _OUT(vlan_id), .cb = nlattr_get_uint16 }, { .type = IFLA_VLAN_FLAGS, .off = _OUT(vlan_flags), .cb = nlattr_get_nla }, { .type = IFLA_VLAN_PROTOCOL, .off = _OUT(vlan_proto), .cb = nlattr_get_uint16 }, }; #undef _OUT NL_DECLARE_ATTR_PARSER(vlan_parser, nla_p_vlan); static int -create_vlan(struct nl_parsed_link *lattrs, struct nlpcb *nlp, struct nl_pstate *npt) +create_vlan(struct nl_parsed_link *lattrs, const struct nlattr_bmask *bm, + struct nlpcb *nlp, struct nl_pstate *npt) { struct epoch_tracker et; struct ifnet *ifp; int error; /* * lattrs.ifla_ifname is the new interface name * lattrs.ifi_index contains parent interface index * lattrs.ifla_idata contains un-parsed vlan data */ struct nl_parsed_vlan attrs = { .vlan_id = 0xFEFE, .vlan_proto = ETHERTYPE_VLAN }; NLP_LOG(LOG_DEBUG3, nlp, "nested: %p len %d", lattrs->ifla_idata, lattrs->ifla_idata->nla_len); if (lattrs->ifla_idata == NULL) { NLMSG_REPORT_ERR_MSG(npt, "vlan id is required, guessing not supported"); return (ENOTSUP); } error = nl_parse_nested(lattrs->ifla_idata, &vlan_parser, npt, &attrs); if (error != 0) return (error); if (attrs.vlan_id > 4095) { NLMSG_REPORT_ERR_MSG(npt, "Invalid VID: %d", attrs.vlan_id); return (EINVAL); } if (attrs.vlan_proto != ETHERTYPE_VLAN && attrs.vlan_proto != ETHERTYPE_QINQ) { NLMSG_REPORT_ERR_MSG(npt, "Unsupported ethertype: 0x%04X", attrs.vlan_proto); return (ENOTSUP); } NET_EPOCH_ENTER(et); ifp = ifnet_byindex_ref(lattrs->ifi_index); NET_EPOCH_EXIT(et); if (ifp == NULL) { NLP_LOG(LOG_DEBUG, nlp, "unable to find parent interface %u", lattrs->ifi_index); return (ENOENT); } /* Waiting till if_clone changes lands */ /* struct vlanreq params = { .vlr_tag = attrs.vlan_id, .vlr_proto = attrs.vlan_proto, }; */ int ifname_len = strlen(lattrs->ifla_ifname) + 1; error = if_clone_create(lattrs->ifla_ifname, ifname_len, (char *)NULL); NLP_LOG(LOG_DEBUG2, nlp, "clone for %s returned %d", lattrs->ifla_ifname, error); if_rele(ifp); return (error); } +static int +dump_vlan(struct ifnet *ifp, struct nl_writer *nw) +{ + return (0); +} + static struct nl_cloner vlan_cloner = { .name = "vlan", .create_f = create_vlan, + .modify_f = modify_generic, + .dump_f = dump_vlan, }; static const struct nlhdr_parser *all_parsers[] = { &vlan_parser }; void rtnl_iface_drivers_register(void) { rtnl_iface_add_cloner(&vlan_cloner); NL_VERIFY_PARSERS(all_parsers); } diff --git a/sys/netlink/route/interface.h b/sys/netlink/route/interface.h index 1b8f1cf7b53d..12a8aa718993 100644 --- a/sys/netlink/route/interface.h +++ b/sys/netlink/route/interface.h @@ -1,248 +1,248 @@ /*- * 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-related (RTM_LINK) message header and attributes. */ #ifndef _NETLINK_ROUTE_INTERFACE_H_ #define _NETLINK_ROUTE_INTERFACE_H_ /* Base header for all of the relevant messages */ struct ifinfomsg { unsigned char ifi_family; /* not used */ 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 */ }; /* Linux-specific link-level state flag */ #define IFF_LOWER_UP IFF_NETLINK_1 #ifndef _KERNEL /* Compatilbility helpers */ #define _IFINFO_HDRLEN ((int)sizeof(struct ifinfomsg)) #define IFLA_RTA(_ifi) ((struct rtattr *)NL_ITEM_DATA(_ifi, _IFINFO_HDRLEN)) #define IFLA_PAYLOAD(_ifi) NLMSG_PAYLOAD(_ifi, _IFINFO_HDRLEN) #endif enum { IFLA_UNSPEC = 0, IFLA_ADDRESS = 1, /* binary: Link-level address (MAC) */ #define IFLA_ADDRESS IFLA_ADDRESS IFLA_BROADCAST = 2, /* binary: link-level broadcast address */ #define IFLA_BROADCAST IFLA_BROADCAST IFLA_IFNAME = 3, /* string: Interface name */ #define IFLA_IFNAME IFLA_IFNAME IFLA_MTU = 4, /* u32: Current interface L3 mtu */ #define IFLA_MTU IFLA_MTU IFLA_LINK = 5, /* u32: interface index */ #define IFLA_LINK IFLA_LINK IFLA_QDISC = 6, /* string: Queing policy (not supported) */ #define IFLA_QDISC IFLA_QDISC IFLA_STATS = 7, /* Interface counters */ #define IFLA_STATS IFLA_STATS IFLA_COST = 8, /* not supported */ #define IFLA_COST IFLA_COST IFLA_PRIORITY = 9, /* not supported */ #define IFLA_PRIORITY IFLA_PRIORITY IFLA_MASTER = 10, /* u32: parent interface ifindex */ #define IFLA_MASTER IFLA_MASTER IFLA_WIRELESS = 11, /* not supported */ #define IFLA_WIRELESS IFLA_WIRELESS IFLA_PROTINFO = 12, /* protocol-specific data */ #define IFLA_PROTINFO IFLA_PROTINFO IFLA_TXQLEN = 13, /* u32: transmit queue length */ #define IFLA_TXQLEN IFLA_TXQLEN IFLA_MAP = 14, /* not supported */ #define IFLA_MAP IFLA_MAP IFLA_WEIGHT = 15, /* not supported */ #define IFLA_WEIGHT IFLA_WEIGHT IFLA_OPERSTATE = 16, /* u8: ifOperStatus per RFC 2863 */ #define IFLA_OPERSTATE IFLA_OPERSTATE IFLA_LINKMODE = 17, /* u8: ifmedia (not supported) */ #define IFLA_LINKMODE IFLA_LINKMODE IFLA_LINKINFO = 18, /* nested: IFLA_INFO_ */ #define IFLA_LINKINFO IFLA_LINKINFO IFLA_NET_NS_PID = 19, /* u32: vnet id (not supported) */ #define IFLA_NET_NS_PID IFLA_NET_NS_PID - IFLA_IFALIAS = 20, /* not supported */ + IFLA_IFALIAS = 20, /* string: interface description */ #define IFLA_IFALIAS IFLA_IFALIAS IFLA_NUM_VF = 21, /* not supported */ #define IFLA_NUM_VF IFLA_NUM_VF IFLA_VFINFO_LIST= 22, /* not supported */ #define IFLA_VFINFO_LIST IFLA_VFINFO_LIST IFLA_STATS64 = 23, /* rtnl_link_stats64: iface stats */ #define IFLA_STATS64 IFLA_STATS64 IFLA_VF_PORTS, IFLA_PORT_SELF, IFLA_AF_SPEC, IFLA_GROUP, /* Group the device belongs to */ IFLA_NET_NS_FD, IFLA_EXT_MASK, /* Extended info mask, VFs, etc */ IFLA_PROMISCUITY, /* Promiscuity count: > 0 means acts PROMISC */ #define IFLA_PROMISCUITY IFLA_PROMISCUITY IFLA_NUM_TX_QUEUES, IFLA_NUM_RX_QUEUES, IFLA_CARRIER, IFLA_PHYS_PORT_ID, IFLA_CARRIER_CHANGES, IFLA_PHYS_SWITCH_ID, IFLA_LINK_NETNSID, IFLA_PHYS_PORT_NAME, IFLA_PROTO_DOWN, IFLA_GSO_MAX_SEGS, IFLA_GSO_MAX_SIZE, IFLA_PAD, IFLA_XDP, IFLA_EVENT, IFLA_NEW_NETNSID, IFLA_IF_NETNSID, IFLA_TARGET_NETNSID = IFLA_IF_NETNSID, /* new alias */ IFLA_CARRIER_UP_COUNT, IFLA_CARRIER_DOWN_COUNT, IFLA_NEW_IFINDEX, IFLA_MIN_MTU, IFLA_MAX_MTU, IFLA_PROP_LIST, IFLA_ALT_IFNAME, /* Alternative ifname */ IFLA_PERM_ADDRESS, IFLA_PROTO_DOWN_REASON, __IFLA_MAX }; #define IFLA_MAX (__IFLA_MAX - 1) /* * Attributes that can be used as filters: * IFLA_IFNAME, IFLA_GROUP, IFLA_ALT_IFNAME * Headers that can be used as filters: * ifi_index, ifi_type */ /* * IFLA_OPERSTATE. * The values below represent the possible * states of ifOperStatus defined by RFC 2863 */ enum { IF_OPER_UNKNOWN = 0, /* status can not be determined */ IF_OPER_NOTPRESENT = 1, /* some (hardware) component not present */ IF_OPER_DOWN = 2, /* down */ IF_OPER_LOWERLAYERDOWN = 3, /* some lower-level interface is down */ IF_OPER_TESTING = 4, /* in some test mode */ IF_OPER_DORMANT = 5, /* "up" but waiting for some condition (802.1X) */ IF_OPER_UP = 6, /* ready to pass packets */ }; /* IFLA_STATS */ struct rtnl_link_stats { uint32_t rx_packets; /* total RX packets (IFCOUNTER_IPACKETS) */ uint32_t tx_packets; /* total TX packets (IFCOUNTER_OPACKETS) */ uint32_t rx_bytes; /* total RX bytes (IFCOUNTER_IBYTES) */ uint32_t tx_bytes; /* total TX bytes (IFCOUNTER_OBYTES) */ uint32_t rx_errors; /* RX errors (IFCOUNTER_IERRORS) */ uint32_t tx_errors; /* RX errors (IFCOUNTER_OERRORS) */ uint32_t rx_dropped; /* RX drop (no space in ring/no bufs) (IFCOUNTER_IQDROPS) */ uint32_t tx_dropped; /* TX drop (IFCOUNTER_OQDROPS) */ uint32_t multicast; /* RX multicast packets (IFCOUNTER_IMCASTS) */ uint32_t collisions; /* not supported */ uint32_t rx_length_errors; /* not supported */ uint32_t rx_over_errors; /* not supported */ uint32_t rx_crc_errors; /* not supported */ uint32_t rx_frame_errors; /* not supported */ uint32_t rx_fifo_errors; /* not supported */ uint32_t rx_missed_errors; /* not supported */ uint32_t tx_aborted_errors; /* not supported */ uint32_t tx_carrier_errors; /* not supported */ uint32_t tx_fifo_errors; /* not supported */ uint32_t tx_heartbeat_errors; /* not supported */ uint32_t tx_window_errors; /* not supported */ uint32_t rx_compressed; /* not supported */ uint32_t tx_compressed; /* not supported */ uint32_t rx_nohandler; /* dropped due to no proto handler (IFCOUNTER_NOPROTO) */ }; /* IFLA_STATS64 */ 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) */ }; /* IFLA_LINKINFO child nlattr types */ enum { IFLA_INFO_UNSPEC, IFLA_INFO_KIND = 1, /* string, link type ("vlan") */ IFLA_INFO_DATA = 2, /* Per-link-type custom data */ IFLA_INFO_XSTATS = 3, IFLA_INFO_SLAVE_KIND = 4, IFLA_INFO_SLAVE_DATA = 5, __IFLA_INFO_MAX, }; #define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1) /* IFLA_INFO_DATA vlan attributes */ enum { IFLA_VLAN_UNSPEC, IFLA_VLAN_ID, IFLA_VLAN_FLAGS, IFLA_VLAN_EGRESS_QOS, IFLA_VLAN_INGRESS_QOS, IFLA_VLAN_PROTOCOL, __IFLA_VLAN_MAX, }; #define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) struct ifla_vlan_flags { uint32_t flags; uint32_t mask; }; #endif diff --git a/sys/netlink/route/route_var.h b/sys/netlink/route/route_var.h index 0bcfcc962020..f1e522c7ae05 100644 --- a/sys/netlink/route/route_var.h +++ b/sys/netlink/route/route_var.h @@ -1,102 +1,109 @@ /*- * 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. */ /* * This file contains definitions shared among NETLINK_ROUTE family */ #ifndef _NETLINK_ROUTE_ROUTE_VAR_H_ #define _NETLINK_ROUTE_ROUTE_VAR_H_ #include /* values for priv_check */ struct nlmsghdr; struct nlpcb; struct nl_pstate; typedef int rtnl_msg_cb_f(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt); struct rtnl_cmd_handler { int cmd; const char *name; rtnl_msg_cb_f *cb; int priv; int flags; }; #define RTNL_F_NOEPOCH 0x01 bool rtnl_register_messages(const struct rtnl_cmd_handler *handlers, int count); /* route.c */ struct rib_cmd_info; void rtnl_handle_route_event(uint32_t fibnum, const struct rib_cmd_info *rc); void rtnl_routes_init(void); /* neigh.c */ void rtnl_neighs_init(void); void rtnl_neighs_destroy(void); /* iface.c */ struct nl_parsed_link { char *ifla_group; char *ifla_ifname; char *ifla_cloner; + char *ifla_ifalias; struct nlattr *ifla_idata; unsigned short ifi_type; int ifi_index; uint32_t ifla_mtu; + uint32_t ifi_flags; + uint32_t ifi_change; }; -typedef int rtnl_iface_create_f(struct nl_parsed_link *lattrs, struct nlpcb *nlp, - struct nl_pstate *npt); -typedef int rtnl_iface_modify_f(struct nl_parsed_link *lattrs, struct nlpcb *nlp, - struct nl_pstate *npt); +typedef int rtnl_iface_create_f(struct nl_parsed_link *lattrs, + const struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt); +typedef int rtnl_iface_modify_f(struct ifnet *ifp, struct nl_parsed_link *lattrs, + const struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt); +typedef int rtnl_iface_dump_f(struct ifnet *ifp, struct nl_writer *nw); struct nl_cloner { const char *name; rtnl_iface_create_f *create_f; rtnl_iface_modify_f *modify_f; + rtnl_iface_dump_f *dump_f; SLIST_ENTRY(nl_cloner) next; }; +extern struct nl_cloner generic_cloner; + void rtnl_ifaces_init(void); void rtnl_ifaces_destroy(void); void rtnl_iface_add_cloner(struct nl_cloner *cloner); void rtnl_iface_del_cloner(struct nl_cloner *cloner); void rtnl_handle_ifnet_event(struct ifnet *ifp, int if_change_mask); /* iface_drivers.c */ void rtnl_iface_drivers_register(void); /* nexthop.c */ void rtnl_nexthops_init(void); struct nhop_object *nl_find_nhop(uint32_t fibnum, int family, uint32_t uidx, int nh_flags, int *perror); #endif diff --git a/tests/atf_python/sys/net/Makefile b/tests/atf_python/sys/net/Makefile index 05b1d8afe863..63efefd27142 100644 --- a/tests/atf_python/sys/net/Makefile +++ b/tests/atf_python/sys/net/Makefile @@ -1,10 +1,10 @@ .include .PATH: ${.CURDIR} -FILES= __init__.py rtsock.py tools.py vnet.py +FILES= __init__.py netlink.py rtsock.py tools.py vnet.py .include FILESDIR= ${TESTSBASE}/atf_python/sys/net .include diff --git a/tests/atf_python/sys/net/netlink.py b/tests/atf_python/sys/net/netlink.py new file mode 100644 index 000000000000..046519ce0343 --- /dev/null +++ b/tests/atf_python/sys/net/netlink.py @@ -0,0 +1,1495 @@ +#!/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 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), + ], + ), +] + +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_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 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, + 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() + print("") + msg.print_message() + msg.print_as_bytes(msg._orig_data, "-- DATA --") + pass diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile new file mode 100644 index 000000000000..d05965761f62 --- /dev/null +++ b/tests/sys/netlink/Makefile @@ -0,0 +1,14 @@ +# $FreeBSD$ + +PACKAGE= tests +WARNS?= 1 + +TESTSDIR= ${TESTSBASE}/sys/netlink + +#ATF_TESTS_C += test_rtsock_l3 +#ATF_TESTS_C += test_rtsock_lladdr +ATF_TESTS_PYTEST += test_rtnl_iface.py + +CFLAGS+= -I${.CURDIR:H:H:H} + +.include diff --git a/tests/sys/netlink/test_rtnl_iface.py b/tests/sys/netlink/test_rtnl_iface.py new file mode 100644 index 000000000000..38a3075f09c9 --- /dev/null +++ b/tests/sys/netlink/test_rtnl_iface.py @@ -0,0 +1,281 @@ +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 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): + 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 + + 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 + # */ + @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()