Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'tools-ynl-policy-query-support'

Jakub Kicinski says:

====================
tools: ynl: policy query support

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.
====================

Link: https://patch.msgid.link/20260310005337.3594225-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+209 -31
+12
tools/net/ynl/pyynl/cli.py
··· 256 256 schema_group.add_argument('--no-schema', action='store_true') 257 257 258 258 dbg_group = parser.add_argument_group('Debug options') 259 + io_group.add_argument('--policy', action='store_true', 260 + help='Query kernel policy for the operation instead of executing it') 259 261 dbg_group.add_argument('--dbg-small-recv', default=0, const=4000, 260 262 action='store', nargs='?', type=int, metavar='INT', 261 263 help="Length of buffers used for recv()") ··· 309 307 recv_size=args.dbg_small_recv) 310 308 if args.dbg_small_recv: 311 309 ynl.set_recv_dbg(True) 310 + 311 + if args.policy: 312 + if args.do: 313 + pol = ynl.get_policy(args.do, 'do') 314 + output(pol.attrs if pol else None) 315 + args.do = None 316 + if args.dump: 317 + pol = ynl.get_policy(args.dump, 'dump') 318 + output(pol.attrs if pol else None) 319 + args.dump = None 312 320 313 321 if args.ntf: 314 322 ynl.ntf_subscribe(args.ntf)
+3 -2
tools/net/ynl/pyynl/lib/__init__.py
··· 5 5 from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ 6 6 SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \ 7 7 SpecException 8 - from .ynl import YnlFamily, Netlink, NlError, YnlException 8 + from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException 9 9 10 10 from .doc_generator import YnlDocGenerator 11 11 12 12 __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", 13 13 "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat", 14 14 "SpecException", 15 - "YnlFamily", "Netlink", "NlError", "YnlDocGenerator", "YnlException"] 15 + "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException", 16 + "YnlDocGenerator"]
+191 -26
tools/net/ynl/pyynl/lib/ynl.py
··· 77 77 78 78 # nlctrl 79 79 CTRL_CMD_GETFAMILY = 3 80 + CTRL_CMD_GETPOLICY = 10 80 81 81 82 CTRL_ATTR_FAMILY_ID = 1 82 83 CTRL_ATTR_FAMILY_NAME = 2 83 84 CTRL_ATTR_MAXATTR = 5 84 85 CTRL_ATTR_MCAST_GROUPS = 7 86 + CTRL_ATTR_POLICY = 8 87 + CTRL_ATTR_OP_POLICY = 9 88 + CTRL_ATTR_OP = 10 85 89 86 90 CTRL_ATTR_MCAST_GRP_NAME = 1 87 91 CTRL_ATTR_MCAST_GRP_ID = 2 92 + 93 + CTRL_ATTR_POLICY_DO = 1 94 + CTRL_ATTR_POLICY_DUMP = 2 88 95 89 96 # Extack types 90 97 NLMSGERR_ATTR_MSG = 1 ··· 141 134 142 135 class ConfigError(Exception): 143 136 pass 137 + 138 + 139 + # pylint: disable=too-few-public-methods 140 + class NlPolicy: 141 + """Kernel policy for one mode (do or dump) of one operation. 142 + 143 + Returned by YnlFamily.get_policy(). Contains a dict of attributes 144 + the kernel accepts, with their validation constraints. 145 + 146 + Attributes: 147 + attrs: dict mapping attribute names to policy dicts, e.g. 148 + page-pool-stats-get do policy:: 149 + 150 + { 151 + 'info': {'type': 'nested', 'policy': { 152 + 'id': {'type': 'uint', 'min-value': 1, 153 + 'max-value': 4294967295}, 154 + 'ifindex': {'type': 'u32', 'min-value': 1, 155 + 'max-value': 2147483647}, 156 + }}, 157 + } 158 + 159 + Each policy dict always contains 'type' (e.g. u32, string, 160 + nested). Optional keys: min-value, max-value, min-length, 161 + max-length, mask, policy. 162 + """ 163 + def __init__(self, attrs): 164 + self.attrs = attrs 144 165 145 166 146 167 class NlAttr: ··· 282 247 elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 283 248 self.extack['bad-attr-offs'] = extack.as_scalar('u32') 284 249 elif extack.type == Netlink.NLMSGERR_ATTR_POLICY: 285 - self.extack['policy'] = self._decode_policy(extack.raw) 250 + self.extack['policy'] = _genl_decode_policy(extack.raw) 286 251 else: 287 252 if 'unknown' not in self.extack: 288 253 self.extack['unknown'] = [] ··· 290 255 291 256 if attr_space: 292 257 self.annotate_extack(attr_space) 293 - 294 - def _decode_policy(self, raw): 295 - policy = {} 296 - for attr in NlAttrs(raw): 297 - if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE: 298 - type_ = attr.as_scalar('u32') 299 - policy['type'] = Netlink.AttrType(type_).name 300 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S: 301 - policy['min-value'] = attr.as_scalar('s64') 302 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S: 303 - policy['max-value'] = attr.as_scalar('s64') 304 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U: 305 - policy['min-value'] = attr.as_scalar('u64') 306 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U: 307 - policy['max-value'] = attr.as_scalar('u64') 308 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH: 309 - policy['min-length'] = attr.as_scalar('u32') 310 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH: 311 - policy['max-length'] = attr.as_scalar('u32') 312 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: 313 - policy['bitfield32-mask'] = attr.as_scalar('u32') 314 - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK: 315 - policy['mask'] = attr.as_scalar('u64') 316 - return policy 317 258 318 259 def annotate_extack(self, attr_space): 319 260 """ Make extack more human friendly with attribute information """ ··· 344 333 return struct.pack("I", len(msg) + 4) + msg 345 334 346 335 336 + def _genl_decode_policy(raw): 337 + policy = {} 338 + for attr in NlAttrs(raw): 339 + if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE: 340 + type_ = attr.as_scalar('u32') 341 + policy['type'] = Netlink.AttrType(type_).name 342 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S: 343 + policy['min-value'] = attr.as_scalar('s64') 344 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S: 345 + policy['max-value'] = attr.as_scalar('s64') 346 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U: 347 + policy['min-value'] = attr.as_scalar('u64') 348 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U: 349 + policy['max-value'] = attr.as_scalar('u64') 350 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH: 351 + policy['min-length'] = attr.as_scalar('u32') 352 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH: 353 + policy['max-length'] = attr.as_scalar('u32') 354 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_POLICY_IDX: 355 + policy['policy-idx'] = attr.as_scalar('u32') 356 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: 357 + policy['bitfield32-mask'] = attr.as_scalar('u32') 358 + elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK: 359 + policy['mask'] = attr.as_scalar('u64') 360 + return policy 361 + 362 + 347 363 # pylint: disable=too-many-nested-blocks 348 364 def _genl_load_families(): 349 365 genl_family_name_to_id = {} ··· 417 379 fam['mcast'][mcast_name] = mcast_id 418 380 if 'name' in fam and 'id' in fam: 419 381 genl_family_name_to_id[fam['name']] = fam 382 + 383 + 384 + # pylint: disable=too-many-nested-blocks 385 + def _genl_policy_dump(family_id, op): 386 + op_policy = {} 387 + policy_table = {} 388 + 389 + with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 390 + sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 391 + 392 + msg = _genl_msg(Netlink.GENL_ID_CTRL, 393 + Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 394 + Netlink.CTRL_CMD_GETPOLICY, 1) 395 + msg += struct.pack('HHHxx', 6, Netlink.CTRL_ATTR_FAMILY_ID, family_id) 396 + msg += struct.pack('HHI', 8, Netlink.CTRL_ATTR_OP, op) 397 + msg = _genl_msg_finalize(msg) 398 + 399 + sock.send(msg, 0) 400 + 401 + while True: 402 + reply = sock.recv(128 * 1024) 403 + nms = NlMsgs(reply) 404 + for nl_msg in nms: 405 + if nl_msg.error: 406 + raise YnlException(f"Netlink error: {nl_msg.error}") 407 + if nl_msg.done: 408 + return op_policy, policy_table 409 + 410 + gm = GenlMsg(nl_msg) 411 + for attr in NlAttrs(gm.raw): 412 + if attr.type == Netlink.CTRL_ATTR_OP_POLICY: 413 + for op_attr in NlAttrs(attr.raw): 414 + for method_attr in NlAttrs(op_attr.raw): 415 + if method_attr.type == Netlink.CTRL_ATTR_POLICY_DO: 416 + op_policy['do'] = method_attr.as_scalar('u32') 417 + elif method_attr.type == Netlink.CTRL_ATTR_POLICY_DUMP: 418 + op_policy['dump'] = method_attr.as_scalar('u32') 419 + elif attr.type == Netlink.CTRL_ATTR_POLICY: 420 + for pidx_attr in NlAttrs(attr.raw): 421 + policy_idx = pidx_attr.type 422 + for aid_attr in NlAttrs(pidx_attr.raw): 423 + attr_id = aid_attr.type 424 + decoded = _genl_decode_policy(aid_attr.raw) 425 + if policy_idx not in policy_table: 426 + policy_table[policy_idx] = {} 427 + policy_table[policy_idx][attr_id] = decoded 420 428 421 429 422 430 class GenlMsg: ··· 572 488 573 489 574 490 class YnlFamily(SpecFamily): 491 + """ 492 + YNL family -- a Netlink interface built from a YAML spec. 493 + 494 + Primary use of the class is to execute Netlink commands: 495 + 496 + ynl.<op_name>(attrs, ...) 497 + 498 + By default this will execute the <op_name> as "do", pass dump=True 499 + to perform a dump operation. 500 + 501 + ynl.<op_name> is a shorthand / convenience wrapper for the following 502 + methods which take the op_name as a string: 503 + 504 + ynl.do(op_name, attrs, flags=None) -- execute a do operation 505 + ynl.dump(op_name, attrs) -- execute a dump operation 506 + ynl.do_multi(ops) -- batch multiple do operations 507 + 508 + The flags argument in ynl.do() allows passing in extra NLM_F_* flags 509 + which may be necessary for old families. 510 + 511 + Notification API: 512 + 513 + ynl.ntf_subscribe(mcast_name) -- join a multicast group 514 + ynl.check_ntf() -- drain pending notifications 515 + ynl.poll_ntf(duration=None) -- yield notifications 516 + 517 + Policy introspection allows querying validation criteria from the running 518 + kernel. Allows checking whether kernel supports a given attribute or value. 519 + 520 + ynl.get_policy(op_name, mode) -- query kernel policy for an op 521 + """ 575 522 def __init__(self, def_path, schema=None, process_unknown=False, 576 523 recv_size=0): 577 524 super().__init__(def_path, schema) ··· 929 814 continue 930 815 931 816 try: 932 - if attr_spec["type"] == 'nest': 817 + if attr_spec["type"] == 'pad': 818 + continue 819 + elif attr_spec["type"] == 'nest': 933 820 subdict = self._decode(NlAttrs(attr.raw), 934 821 attr_spec['nested-attributes'], 935 822 search_attrs) ··· 1307 1190 1308 1191 def do_multi(self, ops): 1309 1192 return self._ops(ops) 1193 + 1194 + def _resolve_policy(self, policy_idx, policy_table, attr_set): 1195 + attrs = {} 1196 + if policy_idx not in policy_table: 1197 + return attrs 1198 + for attr_id, decoded in policy_table[policy_idx].items(): 1199 + if attr_set and attr_id in attr_set.attrs_by_val: 1200 + spec = attr_set.attrs_by_val[attr_id] 1201 + name = spec['name'] 1202 + else: 1203 + spec = None 1204 + name = f'attr-{attr_id}' 1205 + if 'policy-idx' in decoded: 1206 + sub_set = None 1207 + if spec and 'nested-attributes' in spec.yaml: 1208 + sub_set = self.attr_sets[spec.yaml['nested-attributes']] 1209 + nested = self._resolve_policy(decoded['policy-idx'], 1210 + policy_table, sub_set) 1211 + del decoded['policy-idx'] 1212 + decoded['policy'] = nested 1213 + attrs[name] = decoded 1214 + return attrs 1215 + 1216 + def get_policy(self, op_name, mode): 1217 + """Query running kernel for the Netlink policy of an operation. 1218 + 1219 + Allows checking whether kernel supports a given attribute or value. 1220 + This method consults the running kernel, not the YAML spec. 1221 + 1222 + Args: 1223 + op_name: operation name as it appears in the YAML spec 1224 + mode: 'do' or 'dump' 1225 + 1226 + Returns: 1227 + NlPolicy with an attrs dict mapping attribute names to 1228 + their policy properties (type, min/max, nested, etc.), 1229 + or None if the operation has no policy for the given mode. 1230 + Empty policy usually implies that the operation rejects 1231 + all attributes. 1232 + """ 1233 + op = self.ops[op_name] 1234 + op_policy, policy_table = _genl_policy_dump(self.nlproto.family_id, 1235 + op.req_value) 1236 + if mode not in op_policy: 1237 + return None 1238 + policy_idx = op_policy[mode] 1239 + attrs = self._resolve_policy(policy_idx, policy_table, op.attr_set) 1240 + return NlPolicy(attrs)
+3 -3
tools/testing/selftests/net/lib/py/ynl.py
··· 13 13 SPEC_PATH = KSFT_DIR / "net/lib/specs" 14 14 15 15 sys.path.append(tools_full_path.as_posix()) 16 - from net.lib.ynl.pyynl.lib import YnlFamily, NlError, Netlink 16 + from net.lib.ynl.pyynl.lib import YnlFamily, NlError, NlPolicy, Netlink 17 17 else: 18 18 # Running in tree 19 19 tools_full_path = KSRC / "tools" 20 20 SPEC_PATH = KSRC / "Documentation/netlink/specs" 21 21 22 22 sys.path.append(tools_full_path.as_posix()) 23 - from net.ynl.pyynl.lib import YnlFamily, NlError, Netlink 23 + from net.ynl.pyynl.lib import YnlFamily, NlError, NlPolicy, Netlink 24 24 except ModuleNotFoundError as e: 25 25 ksft_pr("Failed importing `ynl` library from kernel sources") 26 26 ksft_pr(str(e)) ··· 28 28 sys.exit(4) 29 29 30 30 __all__ = [ 31 - "NlError", "Netlink", "YnlFamily", "SPEC_PATH", 31 + "NlError", "NlPolicy", "Netlink", "YnlFamily", "SPEC_PATH", 32 32 "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", 33 33 "NetdevFamily", "NetshaperFamily", "DevlinkFamily", "PSPFamily", 34 34 ]