git: fc2538cb7bdb - main - tests: add support for parsing generic netlink families.

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Sat, 01 Apr 2023 19:36:56 UTC
The branch main has been updated by melifaro:

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

commit fc2538cb7bdb8e22afc331c3733f2e306624be9e
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-03-31 15:37:32 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-04-01 19:33:47 +0000

    tests: add support for parsing generic netlink families.
    
    MFC after:      2 weeks
    Differential Revision: https://reviews.freebsd.org/D39370
---
 tests/atf_python/sys/netlink/Makefile           |   3 +-
 tests/atf_python/sys/netlink/message.py         |  78 ++++++++++++--
 tests/atf_python/sys/netlink/netlink.py         | 135 +++++++++++++++---------
 tests/atf_python/sys/netlink/netlink_generic.py | 110 +++++++++++++++++++
 tests/atf_python/sys/netlink/netlink_route.py   |  36 ++++---
 tests/atf_python/sys/netlink/utils.py           |   8 +-
 6 files changed, 299 insertions(+), 71 deletions(-)

diff --git a/tests/atf_python/sys/netlink/Makefile b/tests/atf_python/sys/netlink/Makefile
index 057415cf87f6..73ce5ac50261 100644
--- a/tests/atf_python/sys/netlink/Makefile
+++ b/tests/atf_python/sys/netlink/Makefile
@@ -2,7 +2,8 @@
 
 .PATH:	${.CURDIR}
 
-FILES=	__init__.py attrs.py base_headers.py message.py netlink.py netlink_route.py utils.py
+FILES=	__init__.py attrs.py base_headers.py message.py netlink.py \
+	netlink_generic.py netlink_route.py utils.py
 
 .include <bsd.own.mk>
 FILESDIR=	${TESTSBASE}/atf_python/sys/netlink
diff --git a/tests/atf_python/sys/netlink/message.py b/tests/atf_python/sys/netlink/message.py
index 6bc9f2932868..b6fb2f8e357a 100644
--- a/tests/atf_python/sys/netlink/message.py
+++ b/tests/atf_python/sys/netlink/message.py
@@ -1,15 +1,35 @@
 #!/usr/local/bin/python3
 import struct
 from ctypes import sizeof
+from enum import Enum
 from typing import List
+from typing import NamedTuple
 
 from atf_python.sys.netlink.attrs import NlAttr
 from atf_python.sys.netlink.attrs import NlAttrNested
+from atf_python.sys.netlink.base_headers import NlmAckFlags
+from atf_python.sys.netlink.base_headers import NlmNewFlags
+from atf_python.sys.netlink.base_headers import NlmGetFlags
+from atf_python.sys.netlink.base_headers import NlmDeleteFlags
 from atf_python.sys.netlink.base_headers import NlmBaseFlags
 from atf_python.sys.netlink.base_headers import Nlmsghdr
 from atf_python.sys.netlink.base_headers import NlMsgType
 from atf_python.sys.netlink.utils import align4
 from atf_python.sys.netlink.utils import enum_or_int
+from atf_python.sys.netlink.utils import get_bitmask_str
+
+
+class NlMsgCategory(Enum):
+    UNKNOWN = 0
+    GET = 1
+    NEW = 2
+    DELETE = 3
+    ACK = 4
+
+
+class NlMsgProps(NamedTuple):
+    msg: Enum
+    category: NlMsgCategory
 
 
 class BaseNetlinkMessage(object):
@@ -60,18 +80,40 @@ class BaseNetlinkMessage(object):
     def is_reply(self, hdr):
         return hdr.nlmsg_type == NlMsgType.NLMSG_ERROR.value
 
-    def print_nl_header(self, hdr, prepend=""):
+    @property
+    def msg_name(self):
+        return "msg#{}".format(self._get_msg_type())
+
+    def _get_nl_category(self):
+        if self.is_reply(self.nl_hdr):
+            return NlMsgCategory.ACK
+        return NlMsgCategory.UNKNOWN
+
+    def get_nlm_flags_str(self):
+        category = self._get_nl_category()
+        flags = self.nl_hdr.nlmsg_flags
+
+        if category == NlMsgCategory.UNKNOWN:
+            return self.helper.get_bitmask_str(NlmBaseFlags, flags)
+        elif category == NlMsgCategory.GET:
+            flags_enum = NlmGetFlags
+        elif category == NlMsgCategory.NEW:
+            flags_enum = NlmNewFlags
+        elif category == NlMsgCategory.DELETE:
+            flags_enum = NlmDeleteFlags
+        elif category == NlMsgCategory.ACK:
+            flags_enum = NlmAckFlags
+        return get_bitmask_str([NlmBaseFlags, flags_enum], flags)
+
+    def print_nl_header(self, 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)
+        hdr = self.nl_hdr
         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
+                self.msg_name,
+                self.get_nlm_flags_str(),
                 hdr.nlmsg_flags,
                 hdr.nlmsg_seq,
                 hdr.nlmsg_pid,
@@ -92,7 +134,7 @@ class BaseNetlinkMessage(object):
         return self
 
     def print_message(self):
-        self.print_nl_header(self.nl_hdr)
+        self.print_nl_header()
 
     @staticmethod
     def print_as_bytes(data: bytes, descr: str):
@@ -191,11 +233,29 @@ class StdNetlinkMessage(BaseNetlinkMessage):
         self.nl_hdr.nlmsg_len = len(ret) + sizeof(Nlmsghdr)
         return bytes(self.nl_hdr) + ret
 
+    def _get_msg_type(self):
+        return self.nl_hdr.nlmsg_type
+
+    @property
+    def msg_props(self):
+        msg_type = self._get_msg_type()
+        for msg_props in self.messages:
+            if msg_props.msg.value == msg_type:
+                return msg_props
+        return None
+
+    @property
+    def msg_name(self):
+        msg_props = self.msg_props
+        if msg_props is not None:
+            return msg_props.msg.name
+        return super().msg_name
+
     def print_base_header(self, hdr, prepend=""):
         pass
 
     def print_message(self):
-        self.print_nl_header(self.nl_hdr)
+        self.print_nl_header()
         self.print_base_header(self.base_hdr, " ")
         for nla in self.nla_list:
             nla.print_attr("  ")
diff --git a/tests/atf_python/sys/netlink/netlink.py b/tests/atf_python/sys/netlink/netlink.py
index e10efadee656..f813727d55b4 100644
--- a/tests/atf_python/sys/netlink/netlink.py
+++ b/tests/atf_python/sys/netlink/netlink.py
@@ -14,23 +14,22 @@ from enum import Enum
 from atf_python.sys.netlink.attrs import NlAttr
 from atf_python.sys.netlink.attrs import NlAttrStr
 from atf_python.sys.netlink.attrs import NlAttrU32
-from atf_python.sys.netlink.base_headers import NlmAckFlags
+from atf_python.sys.netlink.base_headers import GenlMsgHdr
 from atf_python.sys.netlink.base_headers import NlmBaseFlags
-from atf_python.sys.netlink.base_headers import NlmDeleteFlags
-from atf_python.sys.netlink.base_headers import NlmGetFlags
-from atf_python.sys.netlink.base_headers import NlmNewFlags
 from atf_python.sys.netlink.base_headers import Nlmsghdr
 from atf_python.sys.netlink.base_headers import NlMsgType
 from atf_python.sys.netlink.message import BaseNetlinkMessage
+from atf_python.sys.netlink.message import NlMsgCategory
+from atf_python.sys.netlink.message import NlMsgProps
 from atf_python.sys.netlink.message import StdNetlinkMessage
-from atf_python.sys.netlink.netlink_route import NetlinkIfaMessage
-from atf_python.sys.netlink.netlink_route import NetlinkIflaMessage
-from atf_python.sys.netlink.netlink_route import NetlinkNdMessage
-from atf_python.sys.netlink.netlink_route import NetlinkRtMessage
-from atf_python.sys.netlink.netlink_route import NlRtMsgType
+from atf_python.sys.netlink.netlink_generic import GenlCtrlMsgType
+from atf_python.sys.netlink.netlink_generic import GenlCtrlAttrType
+from atf_python.sys.netlink.netlink_generic import handler_classes as genl_classes
+from atf_python.sys.netlink.netlink_route import handler_classes as rt_classes
 from atf_python.sys.netlink.utils import align4
 from atf_python.sys.netlink.utils import AttrDescr
 from atf_python.sys.netlink.utils import build_propmap
+from atf_python.sys.netlink.utils import enum_or_int
 from atf_python.sys.netlink.utils import get_bitmask_map
 from atf_python.sys.netlink.utils import NlConst
 from atf_python.sys.netlink.utils import prepare_attrs_map
@@ -114,13 +113,6 @@ class NlHelper:
         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:
@@ -141,18 +133,6 @@ class NlHelper:
         bmap = NlHelper.get_bitmask_map(pmap, 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)
-
 
 nldone_attrs = prepare_attrs_map([])
 
@@ -166,7 +146,7 @@ nlerr_attrs = prepare_attrs_map(
 
 
 class NetlinkDoneMessage(StdNetlinkMessage):
-    messages = [NlMsgType.NLMSG_DONE.value]
+    messages = [NlMsgProps(NlMsgType.NLMSG_DONE, NlMsgCategory.ACK)]
     nl_attrs_map = nldone_attrs
 
     @property
@@ -185,7 +165,7 @@ class NetlinkDoneMessage(StdNetlinkMessage):
 
 
 class NetlinkErrorMessage(StdNetlinkMessage):
-    messages = [NlMsgType.NLMSG_ERROR.value]
+    messages = [NlMsgProps(NlMsgType.NLMSG_ERROR, NlMsgCategory.ACK)]
     nl_attrs_map = nlerr_attrs
 
     @property
@@ -221,30 +201,52 @@ class NetlinkErrorMessage(StdNetlinkMessage):
 
     def print_base_header(self, errhdr, prepend=""):
         print("{}error={}, ".format(prepend, errhdr.error), end="")
-        self.print_nl_header(errhdr.msg, prepend)
+        hdr = errhdr.msg
+        print(
+            "{}len={}, type={}, flags={}(0x{:X}), seq={}, pid={}".format(
+                prepend,
+                hdr.nlmsg_len,
+                "msg#{}".format(hdr.nlmsg_type),
+                self.helper.get_bitmask_str(NlmBaseFlags, hdr.nlmsg_flags),
+                hdr.nlmsg_flags,
+                hdr.nlmsg_seq,
+                hdr.nlmsg_pid,
+            )
+        )
+
+
+core_classes = {
+    "netlink_core": [
+        NetlinkDoneMessage,
+        NetlinkErrorMessage,
+    ],
+}
 
 
 class Nlsock:
+    HANDLER_CLASSES = [core_classes, rt_classes, genl_classes]
+
     def __init__(self, family, helper):
         self.helper = helper
         self.sock_fd = self._setup_netlink(family)
+        self._sock_family = family
         self._data = bytes()
         self.msgmap = self.build_msgmap()
-        # self.set_groups(NlRtGroup.RTNLGRP_IPV4_ROUTE.value | NlRtGroup.RTNLGRP_IPV6_ROUTE.value)  # noqa: E501
+        self._family_map = {
+            NlConst.GENL_ID_CTRL: "nlctrl",
+        }
 
     def build_msgmap(self):
-        classes = [
-            NetlinkRtMessage,
-            NetlinkIflaMessage,
-            NetlinkIfaMessage,
-            NetlinkNdMessage,
-            NetlinkDoneMessage,
-            NetlinkErrorMessage,
-        ]
+        handler_classes = {}
+        for d in self.HANDLER_CLASSES:
+            handler_classes.update(d)
         xmap = {}
-        for cls in classes:
-            for message in cls.messages:
-                xmap[message] = cls
+        # 'family_name': [class.messages[MsgProps.msg],  ]
+        for family_id, family_classes in handler_classes.items():
+            xmap[family_id] = {}
+            for cls in family_classes:
+                for msg_props in cls.messages:
+                    xmap[family_id][enum_or_int(msg_props.msg)] = cls
         return xmap
 
     def _setup_netlink(self, netlink_family) -> int:
@@ -263,9 +265,10 @@ class Nlsock:
         # 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()
+    def write_message(self, msg, verbose=True):
+        if verbose:
+            print("vvvvvvvv OUT vvvvvvvv")
+            msg.print_message()
         msg_bytes = bytes(msg)
         try:
             ret = os.write(self.sock_fd.fileno(), msg_bytes)
@@ -277,12 +280,48 @@ class Nlsock:
         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 hdr.nlmsg_type < 16:
+            family_name = "netlink_core"
+            nlmsg_type = hdr.nlmsg_type
+        elif self._sock_family == NlConst.NETLINK_ROUTE:
+            family_name = "netlink_route"
+            nlmsg_type = hdr.nlmsg_type
+        else:
+            # Genetlink
+            if len(data) < sizeof(Nlmsghdr) + sizeof(GenlMsgHdr):
+                raise Exception("Short read from genl: {} bytes".format(len(data)))
+            family_name = self._family_map.get(hdr.nlmsg_type, "")
+            ghdr = GenlMsgHdr.from_buffer_copy(data[sizeof(Nlmsghdr):])
+            nlmsg_type = ghdr.cmd
+        cls = self.msgmap.get(family_name, {}).get(nlmsg_type)
         if not cls:
             cls = BaseNetlinkMessage
         return cls.from_bytes(self.helper, data)
 
+    def get_genl_family_id(self, family_name):
+        hdr = Nlmsghdr(
+            nlmsg_type=NlConst.GENL_ID_CTRL,
+            nlmsg_flags=NlmBaseFlags.NLM_F_REQUEST.value,
+            nlmsg_seq = self.helper.get_seq(),
+        )
+        ghdr = GenlMsgHdr(cmd=GenlCtrlMsgType.CTRL_CMD_GETFAMILY.value)
+        nla = NlAttrStr(GenlCtrlAttrType.CTRL_ATTR_FAMILY_NAME, family_name)
+        hdr.nlmsg_len = sizeof(Nlmsghdr) + sizeof(GenlMsgHdr) + len(bytes(nla))
+
+        msg_bytes = bytes(hdr) + bytes(ghdr) + bytes(nla)
+        self.write_data(msg_bytes)
+        while True:
+            rx_msg = self.read_message()
+            if 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 family {}".format(family_name))
+                else:
+                    family_id = rx_msg.get_nla(GenlCtrlAttrType.CTRL_ATTR_FAMILY_ID).u16
+                    self._family_map[family_id] = family_name
+                    return family_id
+        raise ValueError("unable to get family {}".format(family_name))
+
     def write_data(self, data: bytes):
         self.sock_fd.send(data)
 
diff --git a/tests/atf_python/sys/netlink/netlink_generic.py b/tests/atf_python/sys/netlink/netlink_generic.py
new file mode 100644
index 000000000000..ee75d5bf37f3
--- /dev/null
+++ b/tests/atf_python/sys/netlink/netlink_generic.py
@@ -0,0 +1,110 @@
+#!/usr/local/bin/python3
+from ctypes import sizeof
+from enum import Enum
+
+from atf_python.sys.netlink.attrs import NlAttrStr
+from atf_python.sys.netlink.attrs import NlAttrU16
+from atf_python.sys.netlink.attrs import NlAttrU32
+from atf_python.sys.netlink.base_headers import GenlMsgHdr
+from atf_python.sys.netlink.message import NlMsgCategory
+from atf_python.sys.netlink.message import NlMsgProps
+from atf_python.sys.netlink.message import StdNetlinkMessage
+from atf_python.sys.netlink.utils import AttrDescr
+from atf_python.sys.netlink.utils import prepare_attrs_map
+from atf_python.sys.netlink.utils import enum_or_int
+
+
+class NetlinkGenlMessage(StdNetlinkMessage):
+    messages = []
+    nl_attrs_map = {}
+    family_name = None
+
+    def __init__(self, helper, family_id, cmd=0):
+        super().__init__(helper, family_id)
+        self.base_hdr = GenlMsgHdr(cmd=enum_or_int(cmd))
+
+    def parse_base_header(self, data):
+        if len(data) < sizeof(GenlMsgHdr):
+            raise ValueError("length less than GenlMsgHdr header")
+        ghdr = GenlMsgHdr.from_buffer_copy(data)
+        return (ghdr, sizeof(GenlMsgHdr))
+
+    def _get_msg_type(self):
+        return self.base_hdr.cmd
+
+    def print_nl_header(self, prepend=""):
+        # len=44, type=RTM_DELROUTE, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1641163704, pid=0  # noqa: E501
+        hdr = self.nl_hdr
+        print(
+            "{}len={}, family={}, flags={}(0x{:X}), seq={}, pid={}".format(
+                prepend,
+                hdr.nlmsg_len,
+                self.family_name,
+                self.get_nlm_flags_str(),
+                hdr.nlmsg_flags,
+                hdr.nlmsg_seq,
+                hdr.nlmsg_pid,
+            )
+        )
+
+    def print_base_header(self, hdr, prepend=""):
+        print(
+            "{}cmd={} version={} reserved={}".format(
+                prepend, self.msg_name, hdr.version, hdr.reserved
+            )
+        )
+
+
+GenlCtrlFamilyName = "nlctrl"
+
+class GenlCtrlMsgType(Enum):
+    CTRL_CMD_UNSPEC = 0
+    CTRL_CMD_NEWFAMILY = 1
+    CTRL_CMD_DELFAMILY = 2
+    CTRL_CMD_GETFAMILY = 3
+    CTRL_CMD_NEWOPS = 4
+    CTRL_CMD_DELOPS = 5
+    CTRL_CMD_GETOPS = 6
+    CTRL_CMD_NEWMCAST_GRP = 7
+    CTRL_CMD_DELMCAST_GRP = 8
+    CTRL_CMD_GETMCAST_GRP = 9
+    CTRL_CMD_GETPOLICY = 10
+
+
+class GenlCtrlAttrType(Enum):
+    CTRL_ATTR_FAMILY_ID = 1
+    CTRL_ATTR_FAMILY_NAME = 2
+    CTRL_ATTR_VERSION = 3
+    CTRL_ATTR_HDRSIZE = 4
+    CTRL_ATTR_MAXATTR = 5
+    CTRL_ATTR_OPS = 6
+    CTRL_ATTR_MCAST_GROUPS = 7
+    CTRL_ATTR_POLICY = 8
+    CTRL_ATTR_OP_POLICY = 9
+    CTRL_ATTR_OP = 10
+
+
+genl_ctrl_attrs = prepare_attrs_map(
+    [
+        AttrDescr(GenlCtrlAttrType.CTRL_ATTR_FAMILY_ID, NlAttrU16),
+        AttrDescr(GenlCtrlAttrType.CTRL_ATTR_FAMILY_NAME, NlAttrStr),
+        AttrDescr(GenlCtrlAttrType.CTRL_ATTR_VERSION, NlAttrU32),
+        AttrDescr(GenlCtrlAttrType.CTRL_ATTR_HDRSIZE, NlAttrU32),
+        AttrDescr(GenlCtrlAttrType.CTRL_ATTR_MAXATTR, NlAttrU32),
+    ]
+)
+
+
+class NetlinkGenlCtrlMessage(NetlinkGenlMessage):
+    messages = [
+        NlMsgProps(GenlCtrlMsgType.CTRL_CMD_NEWFAMILY, NlMsgCategory.NEW),
+        NlMsgProps(GenlCtrlMsgType.CTRL_CMD_GETFAMILY, NlMsgCategory.GET),
+        NlMsgProps(GenlCtrlMsgType.CTRL_CMD_DELFAMILY, NlMsgCategory.DELETE),
+    ]
+    nl_attrs_map = genl_ctrl_attrs
+    family_name = GenlCtrlFamilyName
+
+
+handler_classes = {
+    GenlCtrlFamilyName: [NetlinkGenlCtrlMessage],
+}
diff --git a/tests/atf_python/sys/netlink/netlink_route.py b/tests/atf_python/sys/netlink/netlink_route.py
index c6163a0908af..81f4e89d3e57 100644
--- a/tests/atf_python/sys/netlink/netlink_route.py
+++ b/tests/atf_python/sys/netlink/netlink_route.py
@@ -16,6 +16,8 @@ from atf_python.sys.netlink.attrs import NlAttrStr
 from atf_python.sys.netlink.attrs import NlAttrU32
 from atf_python.sys.netlink.attrs import NlAttrU8
 from atf_python.sys.netlink.message import StdNetlinkMessage
+from atf_python.sys.netlink.message import NlMsgProps
+from atf_python.sys.netlink.message import NlMsgCategory
 from atf_python.sys.netlink.utils import AttrDescr
 from atf_python.sys.netlink.utils import get_bitmask_str
 from atf_python.sys.netlink.utils import prepare_attrs_map
@@ -594,9 +596,9 @@ class BaseNetlinkRtMessage(StdNetlinkMessage):
 
 class NetlinkRtMessage(BaseNetlinkRtMessage):
     messages = [
-        NlRtMsgType.RTM_NEWROUTE.value,
-        NlRtMsgType.RTM_DELROUTE.value,
-        NlRtMsgType.RTM_GETROUTE.value,
+        NlMsgProps(NlRtMsgType.RTM_NEWROUTE, NlMsgCategory.NEW),
+        NlMsgProps(NlRtMsgType.RTM_DELROUTE, NlMsgCategory.DELETE),
+        NlMsgProps(NlRtMsgType.RTM_GETROUTE, NlMsgCategory.GET),
     ]
     nl_attrs_map = rtnl_route_attrs
 
@@ -634,9 +636,9 @@ class NetlinkRtMessage(BaseNetlinkRtMessage):
 
 class NetlinkIflaMessage(BaseNetlinkRtMessage):
     messages = [
-        NlRtMsgType.RTM_NEWLINK.value,
-        NlRtMsgType.RTM_DELLINK.value,
-        NlRtMsgType.RTM_GETLINK.value,
+        NlMsgProps(NlRtMsgType.RTM_NEWLINK, NlMsgCategory.NEW),
+        NlMsgProps(NlRtMsgType.RTM_DELLINK, NlMsgCategory.DELETE),
+        NlMsgProps(NlRtMsgType.RTM_GETLINK, NlMsgCategory.GET),
     ]
     nl_attrs_map = rtnl_ifla_attrs
 
@@ -666,9 +668,9 @@ class NetlinkIflaMessage(BaseNetlinkRtMessage):
 
 class NetlinkIfaMessage(BaseNetlinkRtMessage):
     messages = [
-        NlRtMsgType.RTM_NEWADDR.value,
-        NlRtMsgType.RTM_DELADDR.value,
-        NlRtMsgType.RTM_GETADDR.value,
+        NlMsgProps(NlRtMsgType.RTM_NEWADDR, NlMsgCategory.NEW),
+        NlMsgProps(NlRtMsgType.RTM_DELADDR, NlMsgCategory.DELETE),
+        NlMsgProps(NlRtMsgType.RTM_GETADDR, NlMsgCategory.GET),
     ]
     nl_attrs_map = rtnl_ifa_attrs
 
@@ -698,9 +700,9 @@ class NetlinkIfaMessage(BaseNetlinkRtMessage):
 
 class NetlinkNdMessage(BaseNetlinkRtMessage):
     messages = [
-        NlRtMsgType.RTM_NEWNEIGH.value,
-        NlRtMsgType.RTM_DELNEIGH.value,
-        NlRtMsgType.RTM_GETNEIGH.value,
+        NlMsgProps(NlRtMsgType.RTM_NEWNEIGH, NlMsgCategory.NEW),
+        NlMsgProps(NlRtMsgType.RTM_DELNEIGH, NlMsgCategory.DELETE),
+        NlMsgProps(NlRtMsgType.RTM_GETNEIGH, NlMsgCategory.GET),
     ]
     nl_attrs_map = rtnl_nd_attrs
 
@@ -725,3 +727,13 @@ class NetlinkNdMessage(BaseNetlinkRtMessage):
                 hdr.ndm_flags,
             )
         )
+
+
+handler_classes = {
+    "netlink_route": [
+        NetlinkRtMessage,
+        NetlinkIflaMessage,
+        NetlinkIfaMessage,
+        NetlinkNdMessage,
+    ],
+}
diff --git a/tests/atf_python/sys/netlink/utils.py b/tests/atf_python/sys/netlink/utils.py
index 44758148747d..86a910ce6590 100644
--- a/tests/atf_python/sys/netlink/utils.py
+++ b/tests/atf_python/sys/netlink/utils.py
@@ -12,6 +12,7 @@ class NlConst:
     AF_NETLINK = 38
     NETLINK_ROUTE = 0
     NETLINK_GENERIC = 16
+    GENL_ID_CTRL = 16
 
 
 def roundup2(val: int, num: int) -> int:
@@ -69,6 +70,11 @@ def get_bitmask_map(propmap, val):
 
 
 def get_bitmask_str(cls, val):
-    pmap = build_propmap(cls)
+    if isinstance(cls, type):
+        pmap = build_propmap(cls)
+    else:
+        pmap = {}
+        for _cls in cls:
+            pmap.update(build_propmap(_cls))
     bmap = get_bitmask_map(pmap, val)
     return ",".join([v for k, v in bmap.items()])