* [PATCH net-next 0/5] tools: ynl: policy query support
@ 2026-03-10 0:53 Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 1/5] tools: ynl: handle pad type during decode Jakub Kicinski
` (5 more replies)
0 siblings, 6 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-10 0:53 UTC (permalink / raw)
To: davem, donald.hunter
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest, Jakub Kicinski
Improve the Netlink policy support in YNL. This series grew out of
improvements to policy checking, when writing selftests I realized
that instead of doing all the policy parsing in the test we're
better off making it part of YNL itself.
Patch 1 adds pad handling, apparently we never hit pad with commonly
used families. nlctrl policy dumps use pad more frequently.
Patch 2 is a trivial refactor.
Patch 3 pays off some technical debt in terms of documentation.
The YnlFamily class is growing in size and it's quite hard to
find its members. So document it a little bit.
Patch 4 is the main dish, the implementation of get_policy(op)
in YnlFamily.
Patch 5 plugs the new functionality into the CLI.
Jakub Kicinski (5):
tools: ynl: handle pad type during decode
tools: ynl: move policy decoding out of NlMsg
tools: ynl: add short doc to class YnlFamily
tools: ynl: add Python API for easier access to policies
tools: ynl: cli: add --policy support
tools/net/ynl/pyynl/cli.py | 12 ++
tools/net/ynl/pyynl/lib/__init__.py | 5 +-
tools/net/ynl/pyynl/lib/ynl.py | 216 +++++++++++++++++++---
tools/testing/selftests/net/lib/py/ynl.py | 6 +-
4 files changed, 208 insertions(+), 31 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next 1/5] tools: ynl: handle pad type during decode
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
@ 2026-03-10 0:53 ` Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 2/5] tools: ynl: move policy decoding out of NlMsg Jakub Kicinski
` (4 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-10 0:53 UTC (permalink / raw)
To: davem, donald.hunter
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest, Jakub Kicinski
Apparently Python code only handled the 'pad' type in structs
until now. Add it to attr decoding. nlctrl policy dumps need it.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/net/ynl/pyynl/lib/ynl.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 9774005e7ad1..8302c19ab55c 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -814,7 +814,9 @@ from .nlspec import SpecFamily
continue
try:
- if attr_spec["type"] == 'nest':
+ if attr_spec["type"] == 'pad':
+ continue
+ elif attr_spec["type"] == 'nest':
subdict = self._decode(NlAttrs(attr.raw),
attr_spec['nested-attributes'],
search_attrs)
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next 2/5] tools: ynl: move policy decoding out of NlMsg
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 1/5] tools: ynl: handle pad type during decode Jakub Kicinski
@ 2026-03-10 0:53 ` Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 3/5] tools: ynl: add short doc to class YnlFamily Jakub Kicinski
` (3 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-10 0:53 UTC (permalink / raw)
To: davem, donald.hunter
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest, Jakub Kicinski
We'll soon need to decode policies from dump so move _decode_policy()
out of class NlMsg.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/net/ynl/pyynl/lib/ynl.py | 53 ++++++++++++++++++----------------
1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 8302c19ab55c..17482c17a976 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -247,7 +247,7 @@ from .nlspec import SpecFamily
elif extack.type == Netlink.NLMSGERR_ATTR_OFFS:
self.extack['bad-attr-offs'] = extack.as_scalar('u32')
elif extack.type == Netlink.NLMSGERR_ATTR_POLICY:
- self.extack['policy'] = self._decode_policy(extack.raw)
+ self.extack['policy'] = _genl_decode_policy(extack.raw)
else:
if 'unknown' not in self.extack:
self.extack['unknown'] = []
@@ -256,30 +256,6 @@ from .nlspec import SpecFamily
if attr_space:
self.annotate_extack(attr_space)
- def _decode_policy(self, raw):
- policy = {}
- for attr in NlAttrs(raw):
- if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE:
- type_ = attr.as_scalar('u32')
- policy['type'] = Netlink.AttrType(type_).name
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S:
- policy['min-value'] = attr.as_scalar('s64')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S:
- policy['max-value'] = attr.as_scalar('s64')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U:
- policy['min-value'] = attr.as_scalar('u64')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U:
- policy['max-value'] = attr.as_scalar('u64')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH:
- policy['min-length'] = attr.as_scalar('u32')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH:
- policy['max-length'] = attr.as_scalar('u32')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK:
- policy['bitfield32-mask'] = attr.as_scalar('u32')
- elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK:
- policy['mask'] = attr.as_scalar('u64')
- return policy
-
def annotate_extack(self, attr_space):
""" Make extack more human friendly with attribute information """
@@ -333,6 +309,33 @@ from .nlspec import SpecFamily
return struct.pack("I", len(msg) + 4) + msg
+def _genl_decode_policy(raw):
+ policy = {}
+ for attr in NlAttrs(raw):
+ if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE:
+ type_ = attr.as_scalar('u32')
+ policy['type'] = Netlink.AttrType(type_).name
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S:
+ policy['min-value'] = attr.as_scalar('s64')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S:
+ policy['max-value'] = attr.as_scalar('s64')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U:
+ policy['min-value'] = attr.as_scalar('u64')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U:
+ policy['max-value'] = attr.as_scalar('u64')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH:
+ policy['min-length'] = attr.as_scalar('u32')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH:
+ policy['max-length'] = attr.as_scalar('u32')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_POLICY_IDX:
+ policy['policy-idx'] = attr.as_scalar('u32')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK:
+ policy['bitfield32-mask'] = attr.as_scalar('u32')
+ elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK:
+ policy['mask'] = attr.as_scalar('u64')
+ return policy
+
+
# pylint: disable=too-many-nested-blocks
def _genl_load_families():
genl_family_name_to_id = {}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next 3/5] tools: ynl: add short doc to class YnlFamily
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 1/5] tools: ynl: handle pad type during decode Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 2/5] tools: ynl: move policy decoding out of NlMsg Jakub Kicinski
@ 2026-03-10 0:53 ` Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 4/5] tools: ynl: add Python API for easier access to policies Jakub Kicinski
` (2 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-10 0:53 UTC (permalink / raw)
To: davem, donald.hunter
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest, Jakub Kicinski
The class is quite long. It's getting hard to find the user-facing
methods. Add a short doc at the class level explaining the main API.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/net/ynl/pyynl/lib/ynl.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 17482c17a976..ec4d95f583fe 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -491,6 +491,32 @@ from .nlspec import SpecFamily
class YnlFamily(SpecFamily):
+ """
+ YNL family -- a Netlink interface built from a YAML spec.
+
+ Primary use of the class is to execute Netlink commands:
+
+ ynl.<op_name>(attrs, ...)
+
+ By default this will execute the <op_name> as "do", pass dump=True
+ to perform a dump operation.
+
+ ynl.<op_name> is a shorthand / convenience wrapper for the following
+ methods which take the op_name as a string:
+
+ ynl.do(op_name, attrs, flags=None) -- execute a do operation
+ ynl.dump(op_name, attrs) -- execute a dump operation
+ ynl.do_multi(ops) -- batch multiple do operations
+
+ The flags argument in ynl.do() allows passing in extra NLM_F_* flags
+ which may be necessary for old families.
+
+ Notification API:
+
+ ynl.ntf_subscribe(mcast_name) -- join a multicast group
+ ynl.check_ntf() -- drain pending notifications
+ ynl.poll_ntf(duration=None) -- yield notifications
+ """
def __init__(self, def_path, schema=None, process_unknown=False,
recv_size=0):
super().__init__(def_path, schema)
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next 4/5] tools: ynl: add Python API for easier access to policies
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
` (2 preceding siblings ...)
2026-03-10 0:53 ` [PATCH net-next 3/5] tools: ynl: add short doc to class YnlFamily Jakub Kicinski
@ 2026-03-10 0:53 ` Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 5/5] tools: ynl: cli: add --policy support Jakub Kicinski
2026-03-11 2:40 ` [PATCH net-next 0/5] tools: ynl: policy query support patchwork-bot+netdevbpf
5 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-10 0:53 UTC (permalink / raw)
To: davem, donald.hunter
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest, Jakub Kicinski
The format of Netlink policy dump is a bit curious with messages
in the same dump carrying both attrs and mapping info. Plus each
message carries a single piece of the puzzle the caller must then
reassemble.
I need to do this reassembly for a test, but I think it's generally
useful. So let's add proper support to YnlFamily to return more
user-friendly representation. See the various docs in the patch
for more details.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/net/ynl/pyynl/lib/__init__.py | 5 +-
tools/net/ynl/pyynl/lib/ynl.py | 133 ++++++++++++++++++++++
tools/testing/selftests/net/lib/py/ynl.py | 6 +-
3 files changed, 139 insertions(+), 5 deletions(-)
diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py
index 33a96155fb3b..be741985ae4e 100644
--- a/tools/net/ynl/pyynl/lib/__init__.py
+++ b/tools/net/ynl/pyynl/lib/__init__.py
@@ -5,11 +5,12 @@
from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \
SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \
SpecException
-from .ynl import YnlFamily, Netlink, NlError, YnlException
+from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException
from .doc_generator import YnlDocGenerator
__all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet",
"SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat",
"SpecException",
- "YnlFamily", "Netlink", "NlError", "YnlDocGenerator", "YnlException"]
+ "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException",
+ "YnlDocGenerator"]
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index ec4d95f583fe..e6fcedb597a8 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -77,15 +77,22 @@ from .nlspec import SpecFamily
# nlctrl
CTRL_CMD_GETFAMILY = 3
+ CTRL_CMD_GETPOLICY = 10
CTRL_ATTR_FAMILY_ID = 1
CTRL_ATTR_FAMILY_NAME = 2
CTRL_ATTR_MAXATTR = 5
CTRL_ATTR_MCAST_GROUPS = 7
+ CTRL_ATTR_POLICY = 8
+ CTRL_ATTR_OP_POLICY = 9
+ CTRL_ATTR_OP = 10
CTRL_ATTR_MCAST_GRP_NAME = 1
CTRL_ATTR_MCAST_GRP_ID = 2
+ CTRL_ATTR_POLICY_DO = 1
+ CTRL_ATTR_POLICY_DUMP = 2
+
# Extack types
NLMSGERR_ATTR_MSG = 1
NLMSGERR_ATTR_OFFS = 2
@@ -136,6 +143,34 @@ from .nlspec import SpecFamily
pass
+# pylint: disable=too-few-public-methods
+class NlPolicy:
+ """Kernel policy for one mode (do or dump) of one operation.
+
+ Returned by YnlFamily.get_policy(). Contains a dict of attributes
+ the kernel accepts, with their validation constraints.
+
+ Attributes:
+ attrs: dict mapping attribute names to policy dicts, e.g.
+ page-pool-stats-get do policy::
+
+ {
+ 'info': {'type': 'nested', 'policy': {
+ 'id': {'type': 'uint', 'min-value': 1,
+ 'max-value': 4294967295},
+ 'ifindex': {'type': 'u32', 'min-value': 1,
+ 'max-value': 2147483647},
+ }},
+ }
+
+ Each policy dict always contains 'type' (e.g. u32, string,
+ nested). Optional keys: min-value, max-value, min-length,
+ max-length, mask, policy.
+ """
+ def __init__(self, attrs):
+ self.attrs = attrs
+
+
class NlAttr:
ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little'])
type_formats = {
@@ -384,6 +419,52 @@ from .nlspec import SpecFamily
genl_family_name_to_id[fam['name']] = fam
+# pylint: disable=too-many-nested-blocks
+def _genl_policy_dump(family_id, op):
+ op_policy = {}
+ policy_table = {}
+
+ with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock:
+ sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
+
+ msg = _genl_msg(Netlink.GENL_ID_CTRL,
+ Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP,
+ Netlink.CTRL_CMD_GETPOLICY, 1)
+ msg += struct.pack('HHHxx', 6, Netlink.CTRL_ATTR_FAMILY_ID, family_id)
+ msg += struct.pack('HHI', 8, Netlink.CTRL_ATTR_OP, op)
+ msg = _genl_msg_finalize(msg)
+
+ sock.send(msg, 0)
+
+ while True:
+ reply = sock.recv(128 * 1024)
+ nms = NlMsgs(reply)
+ for nl_msg in nms:
+ if nl_msg.error:
+ raise YnlException(f"Netlink error: {nl_msg.error}")
+ if nl_msg.done:
+ return op_policy, policy_table
+
+ gm = GenlMsg(nl_msg)
+ for attr in NlAttrs(gm.raw):
+ if attr.type == Netlink.CTRL_ATTR_OP_POLICY:
+ for op_attr in NlAttrs(attr.raw):
+ for method_attr in NlAttrs(op_attr.raw):
+ if method_attr.type == Netlink.CTRL_ATTR_POLICY_DO:
+ op_policy['do'] = method_attr.as_scalar('u32')
+ elif method_attr.type == Netlink.CTRL_ATTR_POLICY_DUMP:
+ op_policy['dump'] = method_attr.as_scalar('u32')
+ elif attr.type == Netlink.CTRL_ATTR_POLICY:
+ for pidx_attr in NlAttrs(attr.raw):
+ policy_idx = pidx_attr.type
+ for aid_attr in NlAttrs(pidx_attr.raw):
+ attr_id = aid_attr.type
+ decoded = _genl_decode_policy(aid_attr.raw)
+ if policy_idx not in policy_table:
+ policy_table[policy_idx] = {}
+ policy_table[policy_idx][attr_id] = decoded
+
+
class GenlMsg:
def __init__(self, nl_msg):
self.nl = nl_msg
@@ -516,6 +597,11 @@ from .nlspec import SpecFamily
ynl.ntf_subscribe(mcast_name) -- join a multicast group
ynl.check_ntf() -- drain pending notifications
ynl.poll_ntf(duration=None) -- yield notifications
+
+ Policy introspection allows querying validation criteria from the running
+ kernel. Allows checking whether kernel supports a given attribute or value.
+
+ ynl.get_policy(op_name, mode) -- query kernel policy for an op
"""
def __init__(self, def_path, schema=None, process_unknown=False,
recv_size=0):
@@ -1221,3 +1307,50 @@ from .nlspec import SpecFamily
def do_multi(self, ops):
return self._ops(ops)
+
+ def _resolve_policy(self, policy_idx, policy_table, attr_set):
+ attrs = {}
+ if policy_idx not in policy_table:
+ return attrs
+ for attr_id, decoded in policy_table[policy_idx].items():
+ if attr_set and attr_id in attr_set.attrs_by_val:
+ spec = attr_set.attrs_by_val[attr_id]
+ name = spec['name']
+ else:
+ spec = None
+ name = f'attr-{attr_id}'
+ if 'policy-idx' in decoded:
+ sub_set = None
+ if spec and 'nested-attributes' in spec.yaml:
+ sub_set = self.attr_sets[spec.yaml['nested-attributes']]
+ nested = self._resolve_policy(decoded['policy-idx'],
+ policy_table, sub_set)
+ del decoded['policy-idx']
+ decoded['policy'] = nested
+ attrs[name] = decoded
+ return attrs
+
+ def get_policy(self, op_name, mode):
+ """Query running kernel for the Netlink policy of an operation.
+
+ Allows checking whether kernel supports a given attribute or value.
+ This method consults the running kernel, not the YAML spec.
+
+ Args:
+ op_name: operation name as it appears in the YAML spec
+ mode: 'do' or 'dump'
+
+ Returns:
+ NlPolicy with an attrs dict mapping attribute names to
+ their policy properties (type, min/max, nested, etc.),
+ or None if the operation has no policy for the given mode.
+ No policy usually implies that the operation rejects all attributes.
+ """
+ op = self.ops[op_name]
+ op_policy, policy_table = _genl_policy_dump(self.nlproto.family_id,
+ op.req_value)
+ if mode not in op_policy:
+ return None
+ policy_idx = op_policy[mode]
+ attrs = self._resolve_policy(policy_idx, policy_table, op.attr_set)
+ return NlPolicy(attrs)
diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py
index b42986bf39e3..e8aa97936f6c 100644
--- a/tools/testing/selftests/net/lib/py/ynl.py
+++ b/tools/testing/selftests/net/lib/py/ynl.py
@@ -13,14 +13,14 @@ from .ksft import ksft_pr, ktap_result
SPEC_PATH = KSFT_DIR / "net/lib/specs"
sys.path.append(tools_full_path.as_posix())
- from net.lib.ynl.pyynl.lib import YnlFamily, NlError, Netlink
+ from net.lib.ynl.pyynl.lib import YnlFamily, NlError, NlPolicy, Netlink
else:
# Running in tree
tools_full_path = KSRC / "tools"
SPEC_PATH = KSRC / "Documentation/netlink/specs"
sys.path.append(tools_full_path.as_posix())
- from net.ynl.pyynl.lib import YnlFamily, NlError, Netlink
+ from net.ynl.pyynl.lib import YnlFamily, NlError, NlPolicy, Netlink
except ModuleNotFoundError as e:
ksft_pr("Failed importing `ynl` library from kernel sources")
ksft_pr(str(e))
@@ -28,7 +28,7 @@ from .ksft import ksft_pr, ktap_result
sys.exit(4)
__all__ = [
- "NlError", "Netlink", "YnlFamily", "SPEC_PATH",
+ "NlError", "NlPolicy", "Netlink", "YnlFamily", "SPEC_PATH",
"EthtoolFamily", "RtnlFamily", "RtnlAddrFamily",
"NetdevFamily", "NetshaperFamily", "DevlinkFamily", "PSPFamily",
]
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next 5/5] tools: ynl: cli: add --policy support
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
` (3 preceding siblings ...)
2026-03-10 0:53 ` [PATCH net-next 4/5] tools: ynl: add Python API for easier access to policies Jakub Kicinski
@ 2026-03-10 0:53 ` Jakub Kicinski
2026-03-11 2:40 ` [PATCH net-next 0/5] tools: ynl: policy query support patchwork-bot+netdevbpf
5 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-10 0:53 UTC (permalink / raw)
To: davem, donald.hunter
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest, Jakub Kicinski
Add --policy flag which can be combined with --do or --dump to query
the kernel's netlink policy for an operation instead of executing it.
Examples:
$ ynl --family netdev --do dev-get --policy
{'ifindex': {'max-value': 4294967295, 'min-value': 1, 'type': 'u32'}}
$ ynl --family ethtool --do channels-get --policy --output-json
{"header": {"type": "nested", "policy": {"dev-index": ...}}}
$ ynl --family netdev --dump dev-get --policy
None
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/net/ynl/pyynl/cli.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py
index b452d4fb9434..fc9e84754e4b 100755
--- a/tools/net/ynl/pyynl/cli.py
+++ b/tools/net/ynl/pyynl/cli.py
@@ -256,6 +256,8 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
schema_group.add_argument('--no-schema', action='store_true')
dbg_group = parser.add_argument_group('Debug options')
+ io_group.add_argument('--policy', action='store_true',
+ help='Query kernel policy for the operation instead of executing it')
dbg_group.add_argument('--dbg-small-recv', default=0, const=4000,
action='store', nargs='?', type=int, metavar='INT',
help="Length of buffers used for recv()")
@@ -308,6 +310,16 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
if args.dbg_small_recv:
ynl.set_recv_dbg(True)
+ if args.policy:
+ if args.do:
+ pol = ynl.get_policy(args.do, 'do')
+ output(pol.attrs if pol else None)
+ args.do = None
+ if args.dump:
+ pol = ynl.get_policy(args.dump, 'dump')
+ output(pol.attrs if pol else None)
+ args.dump = None
+
if args.ntf:
ynl.ntf_subscribe(args.ntf)
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 0/5] tools: ynl: policy query support
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
` (4 preceding siblings ...)
2026-03-10 0:53 ` [PATCH net-next 5/5] tools: ynl: cli: add --policy support Jakub Kicinski
@ 2026-03-11 2:40 ` patchwork-bot+netdevbpf
2026-03-11 11:30 ` Donald Hunter
5 siblings, 1 reply; 10+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-03-11 2:40 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, donald.hunter, netdev, edumazet, pabeni, andrew+netdev,
horms, shuah, sdf, linux-kselftest
Hello:
This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Mon, 9 Mar 2026 17:53:32 -0700 you wrote:
> Improve the Netlink policy support in YNL. This series grew out of
> improvements to policy checking, when writing selftests I realized
> that instead of doing all the policy parsing in the test we're
> better off making it part of YNL itself.
>
> Patch 1 adds pad handling, apparently we never hit pad with commonly
> used families. nlctrl policy dumps use pad more frequently.
> Patch 2 is a trivial refactor.
> Patch 3 pays off some technical debt in terms of documentation.
> The YnlFamily class is growing in size and it's quite hard to
> find its members. So document it a little bit.
> Patch 4 is the main dish, the implementation of get_policy(op)
> in YnlFamily.
> Patch 5 plugs the new functionality into the CLI.
>
> [...]
Here is the summary with links:
- [net-next,1/5] tools: ynl: handle pad type during decode
https://git.kernel.org/netdev/net-next/c/7b1309c33927
- [net-next,2/5] tools: ynl: move policy decoding out of NlMsg
https://git.kernel.org/netdev/net-next/c/c26fda6212b8
- [net-next,3/5] tools: ynl: add short doc to class YnlFamily
https://git.kernel.org/netdev/net-next/c/8bbcfce5db97
- [net-next,4/5] tools: ynl: add Python API for easier access to policies
https://git.kernel.org/netdev/net-next/c/77a6401a8722
- [net-next,5/5] tools: ynl: cli: add --policy support
https://git.kernel.org/netdev/net-next/c/d6df5e9b2a56
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 0/5] tools: ynl: policy query support
2026-03-11 2:40 ` [PATCH net-next 0/5] tools: ynl: policy query support patchwork-bot+netdevbpf
@ 2026-03-11 11:30 ` Donald Hunter
2026-03-11 18:35 ` Jakub Kicinski
0 siblings, 1 reply; 10+ messages in thread
From: Donald Hunter @ 2026-03-11 11:30 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest
patchwork-bot+netdevbpf@kernel.org writes:
> Hello:
>
> This series was applied to netdev/net-next.git (main)
> by Jakub Kicinski <kuba@kernel.org>:
>
> On Mon, 9 Mar 2026 17:53:32 -0700 you wrote:
>> Improve the Netlink policy support in YNL. This series grew out of
>> improvements to policy checking, when writing selftests I realized
>> that instead of doing all the policy parsing in the test we're
>> better off making it part of YNL itself.
>>
>> Patch 1 adds pad handling, apparently we never hit pad with commonly
>> used families. nlctrl policy dumps use pad more frequently.
>> Patch 2 is a trivial refactor.
>> Patch 3 pays off some technical debt in terms of documentation.
>> The YnlFamily class is growing in size and it's quite hard to
>> find its members. So document it a little bit.
>> Patch 4 is the main dish, the implementation of get_policy(op)
>> in YnlFamily.
>> Patch 5 plugs the new functionality into the CLI.
I was mid review, looking at a couple of issues:
- It would be good to fail more gracefully for netlink-raw families
- I get "RecursionError: maximum recursion depth exceeded" for nl80211
Happy to address these in a followup.
Cheers,
Donald.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 0/5] tools: ynl: policy query support
2026-03-11 11:30 ` Donald Hunter
@ 2026-03-11 18:35 ` Jakub Kicinski
2026-03-12 17:17 ` Donald Hunter
0 siblings, 1 reply; 10+ messages in thread
From: Jakub Kicinski @ 2026-03-11 18:35 UTC (permalink / raw)
To: Donald Hunter
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest
On Wed, 11 Mar 2026 11:30:59 +0000 Donald Hunter wrote:
> > On Mon, 9 Mar 2026 17:53:32 -0700 you wrote:
> >> Improve the Netlink policy support in YNL. This series grew out of
> >> improvements to policy checking, when writing selftests I realized
> >> that instead of doing all the policy parsing in the test we're
> >> better off making it part of YNL itself.
> >>
> >> Patch 1 adds pad handling, apparently we never hit pad with commonly
> >> used families. nlctrl policy dumps use pad more frequently.
> >> Patch 2 is a trivial refactor.
> >> Patch 3 pays off some technical debt in terms of documentation.
> >> The YnlFamily class is growing in size and it's quite hard to
> >> find its members. So document it a little bit.
> >> Patch 4 is the main dish, the implementation of get_policy(op)
> >> in YnlFamily.
> >> Patch 5 plugs the new functionality into the CLI.
>
> I was mid review
Sorry about that! :( I didn't see any reply to the one liner for a few
days I thought you may be AFK :)
> looking at a couple of issues:
>
> - It would be good to fail more gracefully for netlink-raw families
This one I saw, but I figured the message that gets printed is...
reasonable. For low level functionality like policies we should
assume user is relatively advanced? The policies are spotty
even in genetlink, a lot of families don't link them up properly :(
So explaining all of this is a bit of a rabbit hole.
> - I get "RecursionError: maximum recursion depth exceeded" for nl80211
:o Missed this one completely! My feeling is that we should lean into
the NlPolicy class more, and avoid rendering the full structure upfront.
WDYT about the following?
diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py
index fc9e84754e4b..ff0cfbdc82e4 100755
--- a/tools/net/ynl/pyynl/cli.py
+++ b/tools/net/ynl/pyynl/cli.py
@@ -16,7 +16,8 @@ import textwrap
# pylint: disable=no-name-in-module,wrong-import-position
sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
-from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException
+from lib import SpecFamily, SpecException
+from lib import YnlFamily, Netlink, NlError, NlPolicy, YnlException
SYS_SCHEMA_DIR='/usr/share/ynl'
RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
@@ -79,6 +80,8 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
return bytes.hex(o)
if isinstance(o, set):
return sorted(o)
+ if isinstance(o, NlPolicy):
+ return o.to_dict()
return json.JSONEncoder.default(self, o)
@@ -313,11 +316,11 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
if args.policy:
if args.do:
pol = ynl.get_policy(args.do, 'do')
- output(pol.attrs if pol else None)
+ output(pol if pol else None)
args.do = None
if args.dump:
pol = ynl.get_policy(args.dump, 'dump')
- output(pol.attrs if pol else None)
+ output(pol if pol else None)
args.dump = None
if args.ntf:
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 0eedeee465d8..4411f1902ae4 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -143,32 +143,113 @@ from .nlspec import SpecFamily
pass
-# pylint: disable=too-few-public-methods
class NlPolicy:
"""Kernel policy for one mode (do or dump) of one operation.
- Returned by YnlFamily.get_policy(). Contains a dict of attributes
- the kernel accepts, with their validation constraints.
+ Returned by YnlFamily.get_policy(). Attributes of the policy
+ are accessible as attributes of the object. Nested policies
+ can be accessed indexing the object like a dictionary::
- Attributes:
- attrs: dict mapping attribute names to policy dicts, e.g.
- page-pool-stats-get do policy::
+ pol = ynl.get_policy('page-pool-stats-get', 'do')
+ pol['info'].type # 'nested'
+ pol['info']['id'].type # 'uint'
+ pol['info']['id'].min_value # 1
- {
- 'info': {'type': 'nested', 'policy': {
- 'id': {'type': 'uint', 'min-value': 1,
- 'max-value': 4294967295},
- 'ifindex': {'type': 'u32', 'min-value': 1,
- 'max-value': 2147483647},
- }},
- }
+ Each policy entry always has a 'type' attribute (e.g. u32, string,
+ nested). Optional attributes depending on the 'type': min-value,
+ max-value, min-length, max-length, mask.
- Each policy dict always contains 'type' (e.g. u32, string,
- nested). Optional keys: min-value, max-value, min-length,
- max-length, mask, policy.
+ Policies can form infinite nesting loops. These loops are trimmed
+ when policy is converted to a dict with pol.to_dict().
"""
- def __init__(self, attrs):
- self.attrs = attrs
+ def __init__(self, ynl, policy_idx, policy_table, attr_set, props=None):
+ self._policy_idx = policy_idx
+ self._policy_table = policy_table
+ self._ynl = ynl
+ self._props = props or {}
+ self._entries = {}
+ if policy_idx is not None and policy_idx in policy_table:
+ for attr_id, decoded in policy_table[policy_idx].items():
+ if attr_set and attr_id in attr_set.attrs_by_val:
+ spec = attr_set.attrs_by_val[attr_id]
+ name = spec['name']
+ else:
+ spec = None
+ name = f'attr-{attr_id}'
+ self._entries[name] = (spec, decoded)
+
+ def __getitem__(self, name):
+ """Descend into a nested policy by attribute name."""
+ spec, decoded = self._entries[name]
+ props = dict(decoded)
+ child_idx = None
+ child_set = None
+ if 'policy-idx' in props:
+ child_idx = props.pop('policy-idx')
+ if spec and 'nested-attributes' in spec.yaml:
+ child_set = self._ynl.attr_sets[spec.yaml['nested-attributes']]
+ return NlPolicy(self._ynl, child_idx, self._policy_table,
+ child_set, props)
+
+ def __getattr__(self, name):
+ """Access this policy entry's own properties (type, min-value, etc.).
+
+ Underscores in the name are converted to dashes, so that
+ pol.min_value looks up "min-value".
+ """
+ key = name.replace('_', '-')
+ try:
+ # Hack for level-0 which we still want to have .type but we don't
+ # want type to pointlessly show up in the dict / JSON form.
+ if not self._props and name == "type":
+ return "nested"
+ return self._props[key]
+ except KeyError:
+ raise AttributeError(name)
+
+ def get(self, name, default=None):
+ """Look up a child policy entry by attribute name, with a default."""
+ try:
+ return self[name]
+ except KeyError:
+ return default
+
+ def __contains__(self, name):
+ return name in self._entries
+
+ def __len__(self):
+ return len(self._entries)
+
+ def __iter__(self):
+ return iter(self._entries)
+
+ def keys(self):
+ """Return attribute names accepted by this policy."""
+ return self._entries.keys()
+
+ def to_dict(self, seen=None):
+ """Convert to a plain dict, suitable for JSON serialization.
+
+ Nested NlPolicy objects are expanded recursively. Cyclic
+ references are trimmed (resolved to just {"type": "nested"}).
+ """
+ if seen is None:
+ seen = set()
+ result = dict(self._props)
+ if self._policy_idx is not None:
+ if self._policy_idx not in seen:
+ seen = seen | {self._policy_idx}
+ children = {}
+ for name in self:
+ children[name] = self[name].to_dict(seen)
+ if self._props:
+ result['policy'] = children
+ else:
+ result = children
+ return result
+
+ def __repr__(self):
+ return repr(self.to_dict())
class NlAttr:
@@ -1308,28 +1389,6 @@ from .nlspec import SpecFamily
def do_multi(self, ops):
return self._ops(ops)
- def _resolve_policy(self, policy_idx, policy_table, attr_set):
- attrs = {}
- if policy_idx not in policy_table:
- return attrs
- for attr_id, decoded in policy_table[policy_idx].items():
- if attr_set and attr_id in attr_set.attrs_by_val:
- spec = attr_set.attrs_by_val[attr_id]
- name = spec['name']
- else:
- spec = None
- name = f'attr-{attr_id}'
- if 'policy-idx' in decoded:
- sub_set = None
- if spec and 'nested-attributes' in spec.yaml:
- sub_set = self.attr_sets[spec.yaml['nested-attributes']]
- nested = self._resolve_policy(decoded['policy-idx'],
- policy_table, sub_set)
- del decoded['policy-idx']
- decoded['policy'] = nested
- attrs[name] = decoded
- return attrs
-
def get_policy(self, op_name, mode):
"""Query running kernel for the Netlink policy of an operation.
@@ -1341,8 +1400,8 @@ from .nlspec import SpecFamily
mode: 'do' or 'dump'
Returns:
- NlPolicy with an attrs dict mapping attribute names to
- their policy properties (type, min/max, nested, etc.),
+ NlPolicy acting as a read-only dict mapping attribute names
+ to their policy properties (type, min/max, nested, etc.),
or None if the operation has no policy for the given mode.
Empty policy usually implies that the operation rejects
all attributes.
@@ -1353,5 +1412,4 @@ from .nlspec import SpecFamily
if mode not in op_policy:
return None
policy_idx = op_policy[mode]
- attrs = self._resolve_policy(policy_idx, policy_table, op.attr_set)
- return NlPolicy(attrs)
+ return NlPolicy(self, policy_idx, policy_table, op.attr_set)
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 0/5] tools: ynl: policy query support
2026-03-11 18:35 ` Jakub Kicinski
@ 2026-03-12 17:17 ` Donald Hunter
0 siblings, 0 replies; 10+ messages in thread
From: Donald Hunter @ 2026-03-12 17:17 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, shuah, sdf,
linux-kselftest
Jakub Kicinski <kuba@kernel.org> writes:
> On Wed, 11 Mar 2026 11:30:59 +0000 Donald Hunter wrote:
>> > On Mon, 9 Mar 2026 17:53:32 -0700 you wrote:
>> >> Improve the Netlink policy support in YNL. This series grew out of
>> >> improvements to policy checking, when writing selftests I realized
>> >> that instead of doing all the policy parsing in the test we're
>> >> better off making it part of YNL itself.
>> >>
>> >> Patch 1 adds pad handling, apparently we never hit pad with commonly
>> >> used families. nlctrl policy dumps use pad more frequently.
>> >> Patch 2 is a trivial refactor.
>> >> Patch 3 pays off some technical debt in terms of documentation.
>> >> The YnlFamily class is growing in size and it's quite hard to
>> >> find its members. So document it a little bit.
>> >> Patch 4 is the main dish, the implementation of get_policy(op)
>> >> in YnlFamily.
>> >> Patch 5 plugs the new functionality into the CLI.
>>
>> I was mid review
>
> Sorry about that! :( I didn't see any reply to the one liner for a few
> days I thought you may be AFK :)
>
>> looking at a couple of issues:
>>
>> - It would be good to fail more gracefully for netlink-raw families
>
> This one I saw, but I figured the message that gets printed is...
> reasonable. For low level functionality like policies we should
> assume user is relatively advanced? The policies are spotty
> even in genetlink, a lot of families don't link them up properly :(
> So explaining all of this is a bit of a rabbit hole.
That's fair. I had it in mind to work on avoiding emitting stack traces
for "normal usage". If I get to that then I can see about handling this
case as well.
>> - I get "RecursionError: maximum recursion depth exceeded" for nl80211
>
> :o Missed this one completely! My feeling is that we should lean into
> the NlPolicy class more, and avoid rendering the full structure upfront.
> WDYT about the following?
I agree with the approach. The code below looks reasonable, though I
haven't been thorough - I'll wait for the patch.
> diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py
> index fc9e84754e4b..ff0cfbdc82e4 100755
> --- a/tools/net/ynl/pyynl/cli.py
> +++ b/tools/net/ynl/pyynl/cli.py
> @@ -16,7 +16,8 @@ import textwrap
>
> # pylint: disable=no-name-in-module,wrong-import-position
> sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
> -from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException
> +from lib import SpecFamily, SpecException
> +from lib import YnlFamily, Netlink, NlError, NlPolicy, YnlException
>
> SYS_SCHEMA_DIR='/usr/share/ynl'
> RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
> @@ -79,6 +80,8 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
> return bytes.hex(o)
> if isinstance(o, set):
> return sorted(o)
> + if isinstance(o, NlPolicy):
> + return o.to_dict()
> return json.JSONEncoder.default(self, o)
>
>
> @@ -313,11 +316,11 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
> if args.policy:
> if args.do:
> pol = ynl.get_policy(args.do, 'do')
> - output(pol.attrs if pol else None)
> + output(pol if pol else None)
> args.do = None
> if args.dump:
> pol = ynl.get_policy(args.dump, 'dump')
> - output(pol.attrs if pol else None)
> + output(pol if pol else None)
> args.dump = None
>
> if args.ntf:
> diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
> index 0eedeee465d8..4411f1902ae4 100644
> --- a/tools/net/ynl/pyynl/lib/ynl.py
> +++ b/tools/net/ynl/pyynl/lib/ynl.py
> @@ -143,32 +143,113 @@ from .nlspec import SpecFamily
> pass
>
>
> -# pylint: disable=too-few-public-methods
> class NlPolicy:
> """Kernel policy for one mode (do or dump) of one operation.
>
> - Returned by YnlFamily.get_policy(). Contains a dict of attributes
> - the kernel accepts, with their validation constraints.
> + Returned by YnlFamily.get_policy(). Attributes of the policy
> + are accessible as attributes of the object. Nested policies
> + can be accessed indexing the object like a dictionary::
>
> - Attributes:
> - attrs: dict mapping attribute names to policy dicts, e.g.
> - page-pool-stats-get do policy::
> + pol = ynl.get_policy('page-pool-stats-get', 'do')
> + pol['info'].type # 'nested'
> + pol['info']['id'].type # 'uint'
> + pol['info']['id'].min_value # 1
>
> - {
> - 'info': {'type': 'nested', 'policy': {
> - 'id': {'type': 'uint', 'min-value': 1,
> - 'max-value': 4294967295},
> - 'ifindex': {'type': 'u32', 'min-value': 1,
> - 'max-value': 2147483647},
> - }},
> - }
> + Each policy entry always has a 'type' attribute (e.g. u32, string,
> + nested). Optional attributes depending on the 'type': min-value,
> + max-value, min-length, max-length, mask.
>
> - Each policy dict always contains 'type' (e.g. u32, string,
> - nested). Optional keys: min-value, max-value, min-length,
> - max-length, mask, policy.
> + Policies can form infinite nesting loops. These loops are trimmed
> + when policy is converted to a dict with pol.to_dict().
> """
> - def __init__(self, attrs):
> - self.attrs = attrs
> + def __init__(self, ynl, policy_idx, policy_table, attr_set, props=None):
> + self._policy_idx = policy_idx
> + self._policy_table = policy_table
> + self._ynl = ynl
> + self._props = props or {}
> + self._entries = {}
> + if policy_idx is not None and policy_idx in policy_table:
> + for attr_id, decoded in policy_table[policy_idx].items():
> + if attr_set and attr_id in attr_set.attrs_by_val:
> + spec = attr_set.attrs_by_val[attr_id]
> + name = spec['name']
> + else:
> + spec = None
> + name = f'attr-{attr_id}'
> + self._entries[name] = (spec, decoded)
> +
> + def __getitem__(self, name):
> + """Descend into a nested policy by attribute name."""
> + spec, decoded = self._entries[name]
> + props = dict(decoded)
> + child_idx = None
> + child_set = None
> + if 'policy-idx' in props:
> + child_idx = props.pop('policy-idx')
> + if spec and 'nested-attributes' in spec.yaml:
> + child_set = self._ynl.attr_sets[spec.yaml['nested-attributes']]
> + return NlPolicy(self._ynl, child_idx, self._policy_table,
> + child_set, props)
> +
> + def __getattr__(self, name):
> + """Access this policy entry's own properties (type, min-value, etc.).
> +
> + Underscores in the name are converted to dashes, so that
> + pol.min_value looks up "min-value".
> + """
> + key = name.replace('_', '-')
> + try:
> + # Hack for level-0 which we still want to have .type but we don't
> + # want type to pointlessly show up in the dict / JSON form.
> + if not self._props and name == "type":
> + return "nested"
> + return self._props[key]
> + except KeyError:
> + raise AttributeError(name)
> +
> + def get(self, name, default=None):
> + """Look up a child policy entry by attribute name, with a default."""
> + try:
> + return self[name]
> + except KeyError:
> + return default
> +
> + def __contains__(self, name):
> + return name in self._entries
> +
> + def __len__(self):
> + return len(self._entries)
> +
> + def __iter__(self):
> + return iter(self._entries)
> +
> + def keys(self):
> + """Return attribute names accepted by this policy."""
> + return self._entries.keys()
> +
> + def to_dict(self, seen=None):
> + """Convert to a plain dict, suitable for JSON serialization.
> +
> + Nested NlPolicy objects are expanded recursively. Cyclic
> + references are trimmed (resolved to just {"type": "nested"}).
> + """
> + if seen is None:
> + seen = set()
> + result = dict(self._props)
> + if self._policy_idx is not None:
> + if self._policy_idx not in seen:
> + seen = seen | {self._policy_idx}
> + children = {}
> + for name in self:
> + children[name] = self[name].to_dict(seen)
> + if self._props:
> + result['policy'] = children
> + else:
> + result = children
> + return result
> +
> + def __repr__(self):
> + return repr(self.to_dict())
>
>
> class NlAttr:
> @@ -1308,28 +1389,6 @@ from .nlspec import SpecFamily
> def do_multi(self, ops):
> return self._ops(ops)
>
> - def _resolve_policy(self, policy_idx, policy_table, attr_set):
> - attrs = {}
> - if policy_idx not in policy_table:
> - return attrs
> - for attr_id, decoded in policy_table[policy_idx].items():
> - if attr_set and attr_id in attr_set.attrs_by_val:
> - spec = attr_set.attrs_by_val[attr_id]
> - name = spec['name']
> - else:
> - spec = None
> - name = f'attr-{attr_id}'
> - if 'policy-idx' in decoded:
> - sub_set = None
> - if spec and 'nested-attributes' in spec.yaml:
> - sub_set = self.attr_sets[spec.yaml['nested-attributes']]
> - nested = self._resolve_policy(decoded['policy-idx'],
> - policy_table, sub_set)
> - del decoded['policy-idx']
> - decoded['policy'] = nested
> - attrs[name] = decoded
> - return attrs
> -
> def get_policy(self, op_name, mode):
> """Query running kernel for the Netlink policy of an operation.
>
> @@ -1341,8 +1400,8 @@ from .nlspec import SpecFamily
> mode: 'do' or 'dump'
>
> Returns:
> - NlPolicy with an attrs dict mapping attribute names to
> - their policy properties (type, min/max, nested, etc.),
> + NlPolicy acting as a read-only dict mapping attribute names
> + to their policy properties (type, min/max, nested, etc.),
> or None if the operation has no policy for the given mode.
> Empty policy usually implies that the operation rejects
> all attributes.
> @@ -1353,5 +1412,4 @@ from .nlspec import SpecFamily
> if mode not in op_policy:
> return None
> policy_idx = op_policy[mode]
> - attrs = self._resolve_policy(policy_idx, policy_table, op.attr_set)
> - return NlPolicy(attrs)
> + return NlPolicy(self, policy_idx, policy_table, op.attr_set)
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-03-12 17:18 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-10 0:53 [PATCH net-next 0/5] tools: ynl: policy query support Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 1/5] tools: ynl: handle pad type during decode Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 2/5] tools: ynl: move policy decoding out of NlMsg Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 3/5] tools: ynl: add short doc to class YnlFamily Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 4/5] tools: ynl: add Python API for easier access to policies Jakub Kicinski
2026-03-10 0:53 ` [PATCH net-next 5/5] tools: ynl: cli: add --policy support Jakub Kicinski
2026-03-11 2:40 ` [PATCH net-next 0/5] tools: ynl: policy query support patchwork-bot+netdevbpf
2026-03-11 11:30 ` Donald Hunter
2026-03-11 18:35 ` Jakub Kicinski
2026-03-12 17:17 ` Donald Hunter
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox