git: c1871a3372e3 - main - netlink: improve RTM_GETADDR handling.

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Sun, 08 Jan 2023 15:06:43 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=c1871a3372e382bfcd46452d1d8d4f06561508cc

commit c1871a3372e382bfcd46452d1d8d4f06561508cc
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-01-07 16:18:39 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-01-08 15:06:34 +0000

    netlink: improve RTM_GETADDR handling.
    
    * Allow filtering by ifa_family & ifa_index.
    * Add common RTM_<NEW|DEL|GET>ADDR parser
    * Add tests verifying RTM_GETADDR filtering behaviour & output
    * Factor out common netlink socket test methods into NetlinkTestTemplate
    * Add NLMSG_DONE message handler
    
    Reviewed By: pauamma
    Differential Revision: https://reviews.freebsd.org/D37970
---
 share/man/man4/rtnetlink.4            |  17 +++-
 sys/netlink/route/iface.c             |  87 +++++++++++++++-----
 sys/netlink/route/ifaddrs.h           |   2 +-
 tests/atf_python/sys/net/netlink.py   | 102 ++++++++++++++++++++----
 tests/sys/netlink/Makefile            |   1 +
 tests/sys/netlink/test_rtnl_iface.py  |  35 +--------
 tests/sys/netlink/test_rtnl_ifaddr.py | 144 ++++++++++++++++++++++++++++++++++
 7 files changed, 321 insertions(+), 67 deletions(-)

diff --git a/share/man/man4/rtnetlink.4 b/share/man/man4/rtnetlink.4
index a06807809691..dc40b277d934 100644
--- a/share/man/man4/rtnetlink.4
+++ b/share/man/man4/rtnetlink.4
@@ -403,14 +403,27 @@ Not supported
 .Ss RTM_DELADDR
 Not supported
 .Ss RTM_GETADDR
+Fetches interface addresses in the current VNET matching conditions.
+Each address is reported as a
+.Dv RTM_NEWADDR
+message.
+The following filters are recognised by the kernel:
+.Pp
+.Bd -literal -offset indent -compact
+ifa_family	required family or AF_UNSPEC
+ifa_index	matching interface index or 0
+.Ed
 .Ss TLVs
 .Bl -tag -width indent
 .It Dv IFA_ADDRESS
 (binary) masked interface address or destination address for p2p interfaces.
 .It Dv IFA_LOCAL
-(binary) local interface address
+(binary) local interface address.
+Set for IPv4 and p2p addresses.
+.It Dv IFA_LABEL
+(string) interface name.
 .It Dv IFA_BROADCAST
-(binary) broacast interface address
+(binary) broacast interface address.
 .El
 .Ss Groups
 The following groups are defined:
diff --git a/sys/netlink/route/iface.c b/sys/netlink/route/iface.c
index 81ae5bc8090f..f4936bb2c35b 100644
--- a/sys/netlink/route/iface.c
+++ b/sys/netlink/route/iface.c
@@ -672,6 +672,36 @@ rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *n
 		return (modify_link(hdr, &attrs, &bm, nlp, npt));
 }
 
+struct nl_parsed_ifa {
+	uint8_t		ifa_family;
+	uint8_t		ifa_prefixlen;
+	uint8_t		ifa_scope;
+	uint32_t	ifa_index;
+	uint32_t	ifa_flags;
+	struct sockaddr	*ifa_address;
+	struct sockaddr	*ifa_local;
+};
+
+#define	_IN(_field)	offsetof(struct ifaddrmsg, _field)
+#define	_OUT(_field)	offsetof(struct nl_parsed_ifa, _field)
+static const struct nlfield_parser nlf_p_ifa[] = {
+	{ .off_in = _IN(ifa_family), .off_out = _OUT(ifa_family), .cb = nlf_get_u8 },
+	{ .off_in = _IN(ifa_prefixlen), .off_out = _OUT(ifa_prefixlen), .cb = nlf_get_u8 },
+	{ .off_in = _IN(ifa_scope), .off_out = _OUT(ifa_scope), .cb = nlf_get_u8 },
+	{ .off_in = _IN(ifa_flags), .off_out = _OUT(ifa_flags), .cb = nlf_get_u8_u32 },
+	{ .off_in = _IN(ifa_index), .off_out = _OUT(ifa_index), .cb = nlf_get_u32 },
+};
+
+static const struct nlattr_parser nla_p_ifa[] = {
+	{ .type = IFA_ADDRESS, .off = _OUT(ifa_address), .cb = nlattr_get_ip },
+	{ .type = IFA_LOCAL, .off = _OUT(ifa_local), .cb = nlattr_get_ip },
+	{ .type = IFA_FLAGS, .off = _OUT(ifa_flags), .cb = nlattr_get_uint32 },
+};
+#undef _IN
+#undef _OUT
+NL_DECLARE_PARSER(ifaddrmsg_parser, struct ifaddrmsg, nlf_p_ifa, nla_p_ifa);
+
+
 /*
 
 {ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")},
@@ -826,15 +856,39 @@ enomem:
 }
 
 static int
-rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+dump_iface_addrs(struct netlink_walkargs *wa, struct ifnet *ifp)
 {
         struct ifaddr *ifa;
+
+	CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
+		if (wa->family != 0 && wa->family != ifa->ifa_addr->sa_family)
+			continue;
+		if (ifa->ifa_addr->sa_family == AF_LINK)
+			continue;
+		wa->count++;
+		if (!dump_iface_addr(wa->nw, ifp, ifa, &wa->hdr))
+			return (ENOMEM);
+		wa->dumped++;
+	}
+
+	return (0);
+}
+
+static int
+rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
         struct ifnet *ifp;
 	int error = 0;
 
+	struct nl_parsed_ifa attrs = {};
+	error = nl_parse_nlmsg(hdr, &ifaddrmsg_parser, npt, &attrs);
+	if (error != 0)
+		return (error);
+
 	struct netlink_walkargs wa = {
 		.so = nlp,
 		.nw = npt->nw,
+		.family = attrs.ifa_family,
 		.hdr.nlmsg_pid = hdr->nlmsg_pid,
 		.hdr.nlmsg_seq = hdr->nlmsg_seq,
 		.hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
@@ -843,22 +897,19 @@ rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *n
 
 	NL_LOG(LOG_DEBUG2, "Start dump");
 
-        CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
-                CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
-                        if (wa.family != 0 && wa.family != ifa->ifa_addr->sa_family)
-                                continue;
-                        if (ifa->ifa_addr->sa_family == AF_LINK)
-                                continue;
-			wa.count++;
-                        if (!dump_iface_addr(wa.nw, ifp, ifa, &wa.hdr)) {
-                                error = ENOMEM;
-                                break;
-                        }
-			wa.dumped++;
-                }
-                if (error != 0)
-                        break;
-        }
+	if (attrs.ifa_index != 0) {
+		ifp = ifnet_byindex(attrs.ifa_index);
+		if (ifp == NULL)
+			error = ENOENT;
+		else
+			error = dump_iface_addrs(&wa, ifp);
+	} else {
+		CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
+			error = dump_iface_addrs(&wa, ifp);
+			if (error != 0)
+				break;
+		}
+	}
 
 	NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped);
 
@@ -991,7 +1042,7 @@ static const struct rtnl_cmd_handler cmd_handlers[] = {
 	},
 };
 
-static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser };
+static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser, &ifaddrmsg_parser };
 
 void
 rtnl_iface_add_cloner(struct nl_cloner *cloner)
diff --git a/sys/netlink/route/ifaddrs.h b/sys/netlink/route/ifaddrs.h
index e2013cb266d7..7ada8f22bf7b 100644
--- a/sys/netlink/route/ifaddrs.h
+++ b/sys/netlink/route/ifaddrs.h
@@ -52,7 +52,7 @@ enum {
 	IFA_UNSPEC,
 	IFA_ADDRESS		= 1, /* binary, prefix address (destination for p2p) */
 	IFA_LOCAL		= 2, /* binary, interface address */
-	IFA_LABEL		= 3, /* not supported */
+	IFA_LABEL		= 3, /* string, interface name */
 	IFA_BROADCAST		= 4, /* binary, broadcast ifa */
 	IFA_ANYCAST		= 5, /* not supported */
 	IFA_CACHEINFO		= 6, /* not supported */
diff --git a/tests/atf_python/sys/net/netlink.py b/tests/atf_python/sys/net/netlink.py
index 046519ce0343..57c8582627cf 100644
--- a/tests/atf_python/sys/net/netlink.py
+++ b/tests/atf_python/sys/net/netlink.py
@@ -49,6 +49,12 @@ class Nlmsghdr(Structure):
     ]
 
 
+class Nlmsgdone(Structure):
+    _fields_ = [
+        ("error", c_int),
+    ]
+
+
 class Nlmsgerr(Structure):
     _fields_ = [
         ("error", c_int),
@@ -961,6 +967,8 @@ rtnl_route_attrs = [
     ),
 ]
 
+nldone_attrs = []
+
 nlerr_attrs = [
     AttrDescr(NlErrattrType.NLMSGERR_ATTR_MSG, NlAttrStr),
     AttrDescr(NlErrattrType.NLMSGERR_ATTR_OFFS, NlAttrU32),
@@ -989,6 +997,7 @@ rtnl_ifla_attrs = [
 rtnl_ifa_attrs = [
     AttrDescr(IfattrType.IFA_ADDRESS, NlAttrIp),
     AttrDescr(IfattrType.IFA_LOCAL, NlAttrIp),
+    AttrDescr(IfattrType.IFA_LABEL, NlAttrStr),
     AttrDescr(IfattrType.IFA_BROADCAST, NlAttrIp),
     AttrDescr(IfattrType.IFA_ANYCAST, NlAttrIp),
     AttrDescr(IfattrType.IFA_FLAGS, NlAttrU32),
@@ -1167,6 +1176,25 @@ class StdNetlinkMessage(BaseNetlinkMessage):
             nla.print_attr("  ")
 
 
+class NetlinkDoneMessage(StdNetlinkMessage):
+    messages = [NlMsgType.NLMSG_DONE.value]
+    nl_attrs_map = prepare_attrs_map(nldone_attrs)
+
+    @property
+    def error_code(self):
+        return self.base_hdr.error
+
+    def parse_base_header(self, data):
+        if len(data) < sizeof(Nlmsgdone):
+            raise ValueError("length less than nlmsgdone header")
+        done_hdr = Nlmsgdone.from_buffer_copy(data)
+        sz = sizeof(Nlmsgdone)
+        return (done_hdr, sz)
+
+    def print_base_header(self, hdr, prepend=""):
+        print("{}error={}".format(prepend, hdr.error))
+
+
 class NetlinkErrorMessage(StdNetlinkMessage):
     messages = [NlMsgType.NLMSG_ERROR.value]
     nl_attrs_map = prepare_attrs_map(nlerr_attrs)
@@ -1340,6 +1368,7 @@ class Nlsock:
             NetlinkRtMessage,
             NetlinkIflaMessage,
             NetlinkIfaMessage,
+            NetlinkDoneMessage,
             NetlinkErrorMessage,
         ]
         xmap = {}
@@ -1476,20 +1505,63 @@ class Nlsock:
         self.write_data(msg_bytes)
 
 
-def main():
-    helper = NlHelper()
-    if False:
-        nl = Nlsock(NlConst.NETLINK_GENERIC, helper)
-        nl.request_families()
-    else:
-        nl = Nlsock(NlConst.NETLINK_ROUTE, helper)
-        # nl.request_ifaddrs(socket.AF_INET)
-        # nl.request_raw()
-        nl.request_routes(0)
-        # nl.request_ifaces()
-    while True:
-        msg = nl.read_message()
+class NetlinkMultipartIterator(object):
+    def __init__(self, obj, seq_number: int, msg_type):
+        self._obj = obj
+        self._seq = seq_number
+        self._msg_type = msg_type
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        msg = self._obj.read_message()
+        if self._seq != msg.nl_hdr.nlmsg_seq:
+            raise ValueError("bad sequence number")
+        if msg.is_type(NlMsgType.NLMSG_ERROR):
+            raise ValueError(
+                "error while handling multipart msg: {}".format(msg.error_code)
+            )
+        elif msg.is_type(NlMsgType.NLMSG_DONE):
+            if msg.error_code == 0:
+                raise StopIteration
+            raise ValueError(
+                "error listing some parts of the multipart msg: {}".format(
+                    msg.error_code
+                )
+            )
+        elif not msg.is_type(self._msg_type):
+            raise ValueError("bad message type: {}".format(msg))
+        return msg
+
+
+class NetlinkTestTemplate(object):
+    REQUIRED_MODULES = ["netlink"]
+
+    def setup_netlink(self, netlink_family: NlConst):
+        self.helper = NlHelper()
+        self.nlsock = Nlsock(netlink_family, self.helper)
+
+    def write_message(self, msg):
         print("")
+        print("============= >> TX MESSAGE =============")
         msg.print_message()
-        msg.print_as_bytes(msg._orig_data, "-- DATA --")
-    pass
+        self.nlsock.write_data(bytes(msg))
+        msg.print_as_bytes(bytes(msg), "-- DATA --")
+
+    def read_message(self):
+        msg = self.nlsock.read_message()
+        print("")
+        print("============= << RX MESSAGE =============")
+        msg.print_message()
+        return msg
+
+    def get_reply(self, tx_msg):
+        self.write_message(tx_msg)
+        while True:
+            rx_msg = self.read_message()
+            if tx_msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq:
+                return rx_msg
+
+    def read_msg_list(self, seq, msg_type):
+        return list(NetlinkMultipartIterator(self, seq, msg_type))
diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile
index c112a9079cd2..87cee15c9d33 100644
--- a/tests/sys/netlink/Makefile
+++ b/tests/sys/netlink/Makefile
@@ -7,6 +7,7 @@ TESTSDIR=       ${TESTSBASE}/sys/netlink
 
 ATF_TESTS_C +=	test_snl
 ATF_TESTS_PYTEST +=	test_rtnl_iface.py
+ATF_TESTS_PYTEST +=	test_rtnl_ifaddr.py
 
 CFLAGS+=	-I${.CURDIR:H:H:H}
 
diff --git a/tests/sys/netlink/test_rtnl_iface.py b/tests/sys/netlink/test_rtnl_iface.py
index 3340eaa4d16d..8660051be8e2 100644
--- a/tests/sys/netlink/test_rtnl_iface.py
+++ b/tests/sys/netlink/test_rtnl_iface.py
@@ -6,49 +6,24 @@ from atf_python.sys.net.netlink import IflattrType
 from atf_python.sys.net.netlink import IflinkInfo
 from atf_python.sys.net.netlink import IfLinkInfoDataVlan
 from atf_python.sys.net.netlink import NetlinkIflaMessage
+from atf_python.sys.net.netlink import NetlinkTestTemplate
 from atf_python.sys.net.netlink import NlAttrNested
 from atf_python.sys.net.netlink import NlAttrStr
 from atf_python.sys.net.netlink import NlAttrStrn
 from atf_python.sys.net.netlink import NlAttrU16
 from atf_python.sys.net.netlink import NlAttrU32
 from atf_python.sys.net.netlink import NlConst
-from atf_python.sys.net.netlink import NlHelper
 from atf_python.sys.net.netlink import NlmBaseFlags
 from atf_python.sys.net.netlink import NlmNewFlags
 from atf_python.sys.net.netlink import NlMsgType
 from atf_python.sys.net.netlink import NlRtMsgType
-from atf_python.sys.net.netlink import Nlsock
 from atf_python.sys.net.vnet import SingleVnetTestTemplate
 
 
-class TestRtNlIface(SingleVnetTestTemplate):
-    REQUIRED_MODULES = ["netlink"]
-
+class TestRtNlIface(SingleVnetTestTemplate, NetlinkTestTemplate):
     def setup_method(self, method):
         super().setup_method(method)
-        self.helper = NlHelper()
-        self.nlsock = Nlsock(NlConst.NETLINK_ROUTE, self.helper)
-
-    def write_message(self, msg):
-        print("")
-        print("============= >> TX MESSAGE =============")
-        msg.print_message()
-        self.nlsock.write_data(bytes(msg))
-        msg.print_as_bytes(bytes(msg), "-- DATA --")
-
-    def read_message(self):
-        msg = self.nlsock.read_message()
-        print("")
-        print("============= << RX MESSAGE =============")
-        msg.print_message()
-        return msg
-
-    def get_reply(self, tx_msg):
-        self.write_message(tx_msg)
-        while True:
-            rx_msg = self.read_message()
-            if tx_msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq:
-                return rx_msg
+        self.setup_netlink(NlConst.NETLINK_ROUTE)
 
     def get_interface_byname(self, ifname):
         msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value)
@@ -236,13 +211,11 @@ class TestRtNlIface(SingleVnetTestTemplate):
     # *
     # * {len=76, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1662892737, pid=0},
     # *  {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
-    # *   [
     # *    {{nla_len=8, nla_type=IFLA_LINK}, 2},
     # *    {{nla_len=12, nla_type=IFLA_IFNAME}, "xvlan22"},
     # *    {{nla_len=24, nla_type=IFLA_LINKINFO},
-    # *     [
     # *      {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...},
-    # *      {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"}]}]}, iov_len=76}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 76
+    # *      {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"}
     # */
     @pytest.mark.skip(reason="vlan support needs more work")
     @pytest.mark.require_user("root")
diff --git a/tests/sys/netlink/test_rtnl_ifaddr.py b/tests/sys/netlink/test_rtnl_ifaddr.py
new file mode 100644
index 000000000000..5574644f7b2f
--- /dev/null
+++ b/tests/sys/netlink/test_rtnl_ifaddr.py
@@ -0,0 +1,144 @@
+import ipaddress
+import socket
+import struct
+
+from atf_python.sys.net.netlink import IfattrType
+from atf_python.sys.net.netlink import NetlinkIfaMessage
+from atf_python.sys.net.netlink import NetlinkTestTemplate
+from atf_python.sys.net.netlink import NlConst
+from atf_python.sys.net.netlink import NlHelper
+from atf_python.sys.net.netlink import NlmBaseFlags
+from atf_python.sys.net.netlink import NlMsgType
+from atf_python.sys.net.netlink import NlRtMsgType
+from atf_python.sys.net.netlink import Nlsock
+from atf_python.sys.net.netlink import RtScope
+from atf_python.sys.net.vnet import SingleVnetTestTemplate
+
+
+class TestRtNlIfaddr(SingleVnetTestTemplate, NetlinkTestTemplate):
+    def setup_method(self, method):
+        method_name = method.__name__
+        if "4" in method_name:
+            self.IPV4_PREFIXES = ["192.0.2.1/24"]
+        if "6" in method_name:
+            self.IPV6_PREFIXES = ["2001:db8::1/64"]
+        super().setup_method(method)
+        self.setup_netlink(NlConst.NETLINK_ROUTE)
+
+    def test_46_nofilter(self):
+        """Tests that listing outputs both IPv4/IPv6 and interfaces"""
+        msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value)
+        msg.nl_hdr.nlmsg_flags = (
+            NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value
+        )
+        self.write_message(msg)
+
+        ret = []
+        for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
+            ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index)
+            family = rx_msg.base_hdr.ifa_family
+            ret.append((ifname, family, rx_msg))
+
+        ifname = "lo0"
+        assert len([r for r in ret if r[0] == ifname]) > 0
+
+        ifname = self.vnet.iface_alias_map["if1"].name
+        assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET]) == 1
+        assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2
+
+    def test_46_filter_iface(self):
+        """Tests that listing outputs both IPv4/IPv6 for the specific interface"""
+        epair_ifname = self.vnet.iface_alias_map["if1"].name
+
+        msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value)
+        msg.nl_hdr.nlmsg_flags = (
+            NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value
+        )
+        msg.base_hdr.ifa_index = socket.if_nametoindex(epair_ifname)
+        self.write_message(msg)
+
+        ret = []
+        for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
+            ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index)
+            family = rx_msg.base_hdr.ifa_family
+            ret.append((ifname, family, rx_msg))
+
+        ifname = epair_ifname
+        assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET]) == 1
+        assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2
+        assert len(ret) == 3
+
+    def filter_iface_family(self, family, num_items):
+        """Tests that listing outputs IPv4 for the specific interface"""
+        epair_ifname = self.vnet.iface_alias_map["if1"].name
+
+        msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value)
+        msg.nl_hdr.nlmsg_flags = (
+            NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value
+        )
+        msg.base_hdr.ifa_family = family
+        msg.base_hdr.ifa_index = socket.if_nametoindex(epair_ifname)
+        self.write_message(msg)
+
+        ret = []
+        for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
+            assert family == rx_msg.base_hdr.ifa_family
+            assert epair_ifname == socket.if_indextoname(rx_msg.base_hdr.ifa_index)
+            ret.append(rx_msg)
+        assert len(ret) == num_items
+        return ret
+
+    def test_4_broadcast(self):
+        """Tests header/attr output for listing IPv4 ifas on broadcast iface"""
+        ret = self.filter_iface_family(socket.AF_INET, 1)
+        # Should be 192.0.2.1/24
+        msg = ret[0]
+        # Family and ifindex has been checked already
+        assert msg.base_hdr.ifa_prefixlen == 24
+        # Ignore IFA_FLAGS for now
+        assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value
+
+        assert msg.get_nla(IfattrType.IFA_ADDRESS).addr == "192.0.2.1"
+        assert msg.get_nla(IfattrType.IFA_LOCAL).addr == "192.0.2.1"
+        assert msg.get_nla(IfattrType.IFA_BROADCAST).addr == "192.0.2.255"
+
+        epair_ifname = self.vnet.iface_alias_map["if1"].name
+        assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname
+
+    def test_6_broadcast(self):
+        """Tests header/attr output for listing IPv6 ifas on broadcast iface"""
+        ret = self.filter_iface_family(socket.AF_INET6, 2)
+        # Should be 192.0.2.1/24
+        if ret[0].base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value:
+            (gmsg, lmsg) = ret
+        else:
+            (lmsg, gmsg) = ret
+        # Start with global ( 2001:db8::1/64 )
+        msg = gmsg
+        # Family and ifindex has been checked already
+        assert msg.base_hdr.ifa_prefixlen == 64
+        # Ignore IFA_FLAGS for now
+        assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value
+
+        assert msg.get_nla(IfattrType.IFA_ADDRESS).addr == "2001:db8::1"
+        assert msg.get_nla(IfattrType.IFA_LOCAL) is None
+        assert msg.get_nla(IfattrType.IFA_BROADCAST) is None
+
+        epair_ifname = self.vnet.iface_alias_map["if1"].name
+        assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname
+
+        # Local: fe80::/64
+        msg = lmsg
+        assert msg.base_hdr.ifa_prefixlen == 64
+        # Ignore IFA_FLAGS for now
+        assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_LINK.value
+
+        addr = ipaddress.ip_address(msg.get_nla(IfattrType.IFA_ADDRESS).addr)
+        assert addr.is_link_local
+        # Verify that ifindex is not emmbedded
+        assert struct.unpack("!H", addr.packed[2:4])[0] == 0
+        assert msg.get_nla(IfattrType.IFA_LOCAL) is None
+        assert msg.get_nla(IfattrType.IFA_BROADCAST) is None
+
+        epair_ifname = self.vnet.iface_alias_map["if1"].name
+        assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname