From dc3f63bc3e33a485f8c7112f1520d597a588639c Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:42 -0700 Subject: [PATCH 1/9] netlink: specs: rt-link: add C naming info for ovpn C naming info for OVPN which was added since I adjusted the existing attrs. Also add missing reference to a header needed for a bridge struct. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index 7f91f474ff25..5ec3d35b7a38 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -594,6 +594,7 @@ definitions: name: reasm-overlaps - name: br-boolopt-multi type: struct + header: linux/if_bridge.h members: - name: optval @@ -826,6 +827,8 @@ definitions: - name: default - name: ovpn-mode + enum-name: ovpn-mode + name-prefix: ovpn-mode type: enum entries: - p2p @@ -2195,6 +2198,7 @@ attribute-sets: type: u16 - name: linkinfo-ovpn-attrs + name-prefix: ifla-ovpn- attributes: - name: mode From c9c048993d4c0b8a188ae717ca4a1dadd02d29b5 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:43 -0700 Subject: [PATCH 2/9] tools: ynl-gen: factor out the annotation of pure nested struct We're about to add some code here for sub-messages. Factor out the nest-related logic to make the code readable. No functional change. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/ynl_gen_c.py | 39 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 9e143520e2f7..84140ce3a48d 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -1239,6 +1239,25 @@ class Family(SpecFamily): else: pns_key_list.append(name) + def _load_nested_set_nest(self, spec): + inherit = set() + nested = spec['nested-attributes'] + if nested not in self.root_sets: + if nested not in self.pure_nested_structs: + self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) + else: + raise Exception(f'Using attr set as root and nested not supported - {nested}') + + if 'type-value' in spec: + if nested in self.root_sets: + raise Exception("Inheriting members to a space used as root not supported") + inherit.update(set(spec['type-value'])) + elif spec['type'] == 'indexed-array': + inherit.add('idx') + self.pure_nested_structs[nested].set_inherited(inherit) + + return nested + def _load_nested_sets(self): attr_set_queue = list(self.root_sets.keys()) attr_set_seen = set(self.root_sets.keys()) @@ -1246,29 +1265,15 @@ class Family(SpecFamily): while len(attr_set_queue): a_set = attr_set_queue.pop(0) for attr, spec in self.attr_sets[a_set].items(): - if 'nested-attributes' not in spec: + if 'nested-attributes' in spec: + nested = self._load_nested_set_nest(spec) + else: continue - nested = spec['nested-attributes'] if nested not in attr_set_seen: attr_set_queue.append(nested) attr_set_seen.add(nested) - inherit = set() - if nested not in self.root_sets: - if nested not in self.pure_nested_structs: - self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) - else: - raise Exception(f'Using attr set as root and nested not supported - {nested}') - - if 'type-value' in spec: - if nested in self.root_sets: - raise Exception("Inheriting members to a space used as root not supported") - inherit.update(set(spec['type-value'])) - elif spec['type'] == 'indexed-array': - inherit.add('idx') - self.pure_nested_structs[nested].set_inherited(inherit) - for root_set, rs_members in self.root_sets.items(): for attr, spec in self.attr_sets[root_set].items(): if 'nested-attributes' in spec: From 99b76908a7a3df629a83555333ce0b97824e4734 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:44 -0700 Subject: [PATCH 3/9] tools: ynl-gen: prepare for submsg structs Prepare for constructing Struct() instances which represent sub-messages rather than nested attributes. Restructure the code / indentation to more easily insert a case where nested reference comes from annotation other than the 'nested-attributes' property. Make sure we don't construct the Struct() object from scratch in multiple places as the constructor will soon have more arguments. This should cause no functional change. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/ynl_gen_c.py | 62 ++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 84140ce3a48d..c8b2a2ab2e5d 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -60,7 +60,12 @@ class Type(SpecAttr): self.len = attr['len'] if 'nested-attributes' in attr: - self.nested_attrs = attr['nested-attributes'] + nested = attr['nested-attributes'] + else: + nested = None + + if nested: + self.nested_attrs = nested if self.nested_attrs == family.name: self.nested_render_name = c_lower(f"{family.ident_name}") else: @@ -1225,15 +1230,18 @@ class Family(SpecFamily): for _, spec in self.attr_sets[name].items(): if 'nested-attributes' in spec: nested = spec['nested-attributes'] - # If the unknown nest we hit is recursive it's fine, it'll be a pointer - if self.pure_nested_structs[nested].recursive: - continue - if nested not in pns_key_seen: - # Dicts are sorted, this will make struct last - struct = self.pure_nested_structs.pop(name) - self.pure_nested_structs[name] = struct - finished = False - break + else: + continue + + # If the unknown nest we hit is recursive it's fine, it'll be a pointer + if self.pure_nested_structs[nested].recursive: + continue + if nested not in pns_key_seen: + # Dicts are sorted, this will make struct last + struct = self.pure_nested_structs.pop(name) + self.pure_nested_structs[name] = struct + finished = False + break if finished: pns_key_seen.add(name) else: @@ -1278,10 +1286,15 @@ class Family(SpecFamily): for attr, spec in self.attr_sets[root_set].items(): if 'nested-attributes' in spec: nested = spec['nested-attributes'] + else: + nested = None + + if nested: if attr in rs_members['request']: self.pure_nested_structs[nested].request = True if attr in rs_members['reply']: self.pure_nested_structs[nested].reply = True + if spec.is_multi_val(): child = self.pure_nested_structs.get(nested) child.in_multi_val = True @@ -1291,20 +1304,24 @@ class Family(SpecFamily): # Propagate the request / reply / recursive for attr_set, struct in reversed(self.pure_nested_structs.items()): for _, spec in self.attr_sets[attr_set].items(): - if 'nested-attributes' in spec: - child_name = spec['nested-attributes'] - struct.child_nests.add(child_name) - child = self.pure_nested_structs.get(child_name) - if child: - if not child.recursive: - struct.child_nests.update(child.child_nests) - child.request |= struct.request - child.reply |= struct.reply - if spec.is_multi_val(): - child.in_multi_val = True if attr_set in struct.child_nests: struct.recursive = True + if 'nested-attributes' in spec: + child_name = spec['nested-attributes'] + else: + continue + + struct.child_nests.add(child_name) + child = self.pure_nested_structs.get(child_name) + if child: + if not child.recursive: + struct.child_nests.update(child.child_nests) + child.request |= struct.request + child.reply |= struct.reply + if spec.is_multi_val(): + child.in_multi_val = True + self._sort_pure_types() def _load_attr_use(self): @@ -3307,8 +3324,7 @@ def main(): has_recursive_nests = True if has_recursive_nests: cw.nl() - for name in parsed.pure_nested_structs: - struct = Struct(parsed, name) + for struct in parsed.pure_nested_structs.values(): put_typol(cw, struct) for name in parsed.root_sets: struct = Struct(parsed, name) From 3186a8e55ae3428ec1e06af09075e20885376e4e Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:45 -0700 Subject: [PATCH 4/9] tools: ynl-gen: submsg: plumb thru an empty type Hook in handling of sub-messages, for now treat them as ignored attrs. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/lib/__init__.py | 5 +++-- tools/net/ynl/pyynl/ynl_gen_c.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py index 9137b83e580a..71518b9842ee 100644 --- a/tools/net/ynl/pyynl/lib/__init__.py +++ b/tools/net/ynl/pyynl/lib/__init__.py @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ - SpecFamily, SpecOperation + SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat from .ynl import YnlFamily, Netlink, NlError __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", - "SpecFamily", "SpecOperation", "YnlFamily", "Netlink", "NlError"] + "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat", + "YnlFamily", "Netlink", "NlError"] diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index c8b2a2ab2e5d..2292bbb68836 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -14,6 +14,7 @@ import yaml sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry +from lib import SpecSubMessage, SpecSubMessageFormat def c_upper(name): @@ -872,6 +873,10 @@ class TypeNestTypeValue(Type): return get_lines, init_lines, local_vars +class TypeSubMessage(TypeUnused): + pass + + class Struct: def __init__(self, family, space_name, type_list=None, inherited=None): self.family = family @@ -1052,6 +1057,8 @@ class AttrSet(SpecAttrSet): raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}') elif elem['type'] == 'nest-type-value': t = TypeNestTypeValue(self.family, self, elem, value) + elif elem['type'] == 'sub-message': + t = TypeSubMessage(self.family, self, elem, value) else: raise Exception(f"No typed class for type {elem['type']}") @@ -1096,6 +1103,16 @@ class Operation(SpecOperation): self.has_ntf = True +class SubMessage(SpecSubMessage): + def __init__(self, family, yaml): + super().__init__(family, yaml) + + self.render_name = c_lower(family.ident_name + '-' + yaml['name']) + + def resolve(self): + self.resolve_up(super()) + + class Family(SpecFamily): def __init__(self, file_name, exclude_ops): # Added by resolve: @@ -1178,6 +1195,9 @@ class Family(SpecFamily): def new_operation(self, elem, req_value, rsp_value): return Operation(self, elem, req_value, rsp_value) + def new_sub_message(self, elem): + return SubMessage(self, elem) + def is_classic(self): return self.proto == 'netlink-raw' From 6366d267788fd9dba20d6dff61e3e05d48ddb0b8 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:46 -0700 Subject: [PATCH 5/9] tools: ynl-gen: submsg: render the structs The easiest (or perhaps only sane) way to support submessages in C is to treat them as if they were nests. Build fake attributes to that effect in the codegen. Render the submsg as a big nest of all possible values. With this in place the main missing part is to hook in the switch which selects how to parse based on the key. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/ynl_gen_c.py | 46 +++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 2292bbb68836..020aa34b890b 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -62,6 +62,8 @@ class Type(SpecAttr): if 'nested-attributes' in attr: nested = attr['nested-attributes'] + elif 'sub-message' in attr: + nested = attr['sub-message'] else: nested = None @@ -125,7 +127,9 @@ class Type(SpecAttr): return c_upper(value) def resolve(self): - if 'name-prefix' in self.attr: + if 'parent-sub-message' in self.attr: + enum_name = self.attr['parent-sub-message'].enum_name + elif 'name-prefix' in self.attr: enum_name = f"{self.attr['name-prefix']}{self.name}" else: enum_name = f"{self.attr_set.name_prefix}{self.name}" @@ -873,18 +877,20 @@ class TypeNestTypeValue(Type): return get_lines, init_lines, local_vars -class TypeSubMessage(TypeUnused): +class TypeSubMessage(TypeNest): pass class Struct: - def __init__(self, family, space_name, type_list=None, inherited=None): + def __init__(self, family, space_name, type_list=None, + inherited=None, submsg=None): self.family = family self.space_name = space_name self.attr_set = family.attr_sets[space_name] # Use list to catch comparisons with empty sets self._inherited = inherited if inherited is not None else [] self.inherited = [] + self.submsg = submsg self.nested = type_list is None if family.name == c_lower(space_name): @@ -1250,6 +1256,8 @@ class Family(SpecFamily): for _, spec in self.attr_sets[name].items(): if 'nested-attributes' in spec: nested = spec['nested-attributes'] + elif 'sub-message' in spec: + nested = spec.sub_message else: continue @@ -1286,6 +1294,32 @@ class Family(SpecFamily): return nested + def _load_nested_set_submsg(self, spec): + # Fake the struct type for the sub-message itself + # its not a attr_set but codegen wants attr_sets. + submsg = self.sub_msgs[spec["sub-message"]] + nested = submsg.name + + attrs = [] + for name, fmt in submsg.formats.items(): + attrs.append({ + "name": name, + "type": "nest", + "parent-sub-message": spec, + "nested-attributes": fmt['attribute-set'] + }) + + self.attr_sets[nested] = AttrSet(self, { + "name": nested, + "name-pfx": self.name + '-' + spec.name + '-', + "attributes": attrs + }) + + if nested not in self.pure_nested_structs: + self.pure_nested_structs[nested] = Struct(self, nested, submsg=submsg) + + return nested + def _load_nested_sets(self): attr_set_queue = list(self.root_sets.keys()) attr_set_seen = set(self.root_sets.keys()) @@ -1295,6 +1329,8 @@ class Family(SpecFamily): for attr, spec in self.attr_sets[a_set].items(): if 'nested-attributes' in spec: nested = self._load_nested_set_nest(spec) + elif 'sub-message' in spec: + nested = self._load_nested_set_submsg(spec) else: continue @@ -1306,6 +1342,8 @@ class Family(SpecFamily): for attr, spec in self.attr_sets[root_set].items(): if 'nested-attributes' in spec: nested = spec['nested-attributes'] + elif 'sub-message' in spec: + nested = spec.sub_message else: nested = None @@ -1329,6 +1367,8 @@ class Family(SpecFamily): if 'nested-attributes' in spec: child_name = spec['nested-attributes'] + elif 'sub-message' in spec: + child_name = spec.sub_message else: continue From b9e03e263610a8d872c753bc882676d3cba1797b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:47 -0700 Subject: [PATCH 6/9] tools: ynl-gen: submsg: support parsing and rendering sub-messages Adjust parsing and rendering appropriately to make sub-messages work. Rendering is pretty trivial, as the submsg -> netlink conversion looks like rendering a nest in which only one attr was set. Only trick is that we use the enum value of the sub-message rather than the nest as the type, and effectively skip one layer of nesting. A real double nested struct would look like this: [SELECTOR] [SUBMSG] [NEST] [MSG1-ATTR] A submsg "is" the nest so by skipping I mean: [SELECTOR] [SUBMSG] [MSG1-ATTR] There is no extra validation in YNL if caller has set the selector matching the submsg type (e.g. link type = "macvlan" but the nest attrs are set to carry "veth"). Let the kernel handle that. Parsing side is a little more specialized as we need to render and insert a new kind of function which switches between what to parse based on the selector. But code isn't too complicated. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-7-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/lib/ynl-priv.h | 3 ++ tools/net/ynl/lib/ynl.c | 9 ++++ tools/net/ynl/lib/ynl.h | 1 + tools/net/ynl/pyynl/ynl_gen_c.py | 80 ++++++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h index 5debb09491e7..fbc058dd1c3e 100644 --- a/tools/net/ynl/lib/ynl-priv.h +++ b/tools/net/ynl/lib/ynl-priv.h @@ -25,6 +25,7 @@ enum ynl_policy_type { YNL_PT_UINT, YNL_PT_NUL_STR, YNL_PT_BITFIELD32, + YNL_PT_SUBMSG, }; enum ynl_parse_result { @@ -103,6 +104,8 @@ struct nlmsghdr * ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version); int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr); +int ynl_submsg_failed(struct ynl_parse_arg *yarg, const char *field_name, + const char *sel_name); /* YNL specific helpers used by the auto-generated code */ diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c index a0b54ad4c073..25fc6501349b 100644 --- a/tools/net/ynl/lib/ynl.c +++ b/tools/net/ynl/lib/ynl.c @@ -384,6 +384,15 @@ int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr) return 0; } +int ynl_submsg_failed(struct ynl_parse_arg *yarg, const char *field_name, + const char *sel_name) +{ + yerr(yarg->ys, YNL_ERROR_SUBMSG_KEY, + "Parsing error: Sub-message key not set (msg %s, key %s)", + field_name, sel_name); + return YNL_PARSE_CB_ERROR; +} + /* Generic code */ static void ynl_err_reset(struct ynl_sock *ys) diff --git a/tools/net/ynl/lib/ynl.h b/tools/net/ynl/lib/ynl.h index 32efeb224829..db7c0591a63f 100644 --- a/tools/net/ynl/lib/ynl.h +++ b/tools/net/ynl/lib/ynl.h @@ -23,6 +23,7 @@ enum ynl_error_code { YNL_ERROR_INV_RESP, YNL_ERROR_INPUT_INVALID, YNL_ERROR_INPUT_TOO_BIG, + YNL_ERROR_SUBMSG_KEY, }; /** diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 020aa34b890b..b6b54d6fa906 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -878,7 +878,16 @@ class TypeNestTypeValue(Type): class TypeSubMessage(TypeNest): - pass + def _attr_get(self, ri, var): + sel = c_lower(self['selector']) + get_lines = [f'if (!{var}->{sel})', + f'return ynl_submsg_failed(yarg, "%s", "%s");' % + (self.name, self['selector']), + f"if ({self.nested_render_name}_parse(&parg, {var}->{sel}, attr))", + "return YNL_PARSE_CB_ERROR;"] + init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", + f"parg.data = &{var}->{self.c_name};"] + return get_lines, init_lines, None class Struct: @@ -1818,11 +1827,34 @@ def print_dump_prototype(ri): print_prototype(ri, "request") +def put_typol_submsg(cw, struct): + cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[] =') + + i = 0 + for name, arg in struct.member_list(): + cw.p('[%d] = { .type = YNL_PT_SUBMSG, .name = "%s", .nest = &%s_nest, },' % + (i, name, arg.nested_render_name)) + i += 1 + + cw.block_end(line=';') + cw.nl() + + cw.block_start(line=f'const struct ynl_policy_nest {struct.render_name}_nest =') + cw.p(f'.max_attr = {i - 1},') + cw.p(f'.table = {struct.render_name}_policy,') + cw.block_end(line=';') + cw.nl() + + def put_typol_fwd(cw, struct): cw.p(f'extern const struct ynl_policy_nest {struct.render_name}_nest;') def put_typol(cw, struct): + if struct.submsg: + put_typol_submsg(cw, struct) + return + type_max = struct.attr_set.max_name cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =') @@ -1908,8 +1940,9 @@ def put_req_nested(ri, struct): local_vars = [] init_lines = [] - local_vars.append('struct nlattr *nest;') - init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);") + if struct.submsg is None: + local_vars.append('struct nlattr *nest;') + init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);") has_anest = False has_count = False @@ -1931,7 +1964,8 @@ def put_req_nested(ri, struct): for _, arg in struct.member_list(): arg.attr_put(ri, "obj") - ri.cw.p("ynl_attr_nest_end(nlh, nest);") + if struct.submsg is None: + ri.cw.p("ynl_attr_nest_end(nlh, nest);") ri.cw.nl() ri.cw.p('return 0;') @@ -1968,6 +2002,7 @@ def _multi_parse(ri, struct, init_lines, local_vars): if 'multi-attr' in aspec: multi_attrs.add(arg) needs_parg |= 'nested-attributes' in aspec + needs_parg |= 'sub-message' in aspec if array_nests or multi_attrs: local_vars.append('int i;') if needs_parg: @@ -2086,9 +2121,43 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.nl() +def parse_rsp_submsg(ri, struct): + parse_rsp_nested_prototype(ri, struct, suffix='') + + var = 'dst' + + ri.cw.block_start() + ri.cw.write_func_lvar(['const struct nlattr *attr = nested;', + f'{struct.ptr_name}{var} = yarg->data;', + 'struct ynl_parse_arg parg;']) + + ri.cw.p('parg.ys = yarg->ys;') + ri.cw.nl() + + first = True + for name, arg in struct.member_list(): + kw = 'if' if first else 'else if' + first = False + + ri.cw.block_start(line=f'{kw} (!strcmp(sel, "{name}"))') + get_lines, init_lines, _ = arg._attr_get(ri, var) + for line in init_lines: + ri.cw.p(line) + for line in get_lines: + ri.cw.p(line) + if arg.presence_type() == 'present': + ri.cw.p(f"{var}->_present.{arg.c_name} = 1;") + ri.cw.block_end() + ri.cw.p('return 0;') + ri.cw.block_end() + ri.cw.nl() + + def parse_rsp_nested_prototype(ri, struct, suffix=';'): func_args = ['struct ynl_parse_arg *yarg', 'const struct nlattr *nested'] + if struct.submsg: + func_args.insert(1, 'const char *sel') for arg in struct.inherited: func_args.append('__u32 ' + arg) @@ -2097,6 +2166,9 @@ def parse_rsp_nested_prototype(ri, struct, suffix=';'): def parse_rsp_nested(ri, struct): + if struct.submsg: + return parse_rsp_submsg(ri, struct) + parse_rsp_nested_prototype(ri, struct, suffix='') local_vars = ['const struct nlattr *attr;', From 0939a418b3b092aa8a97a59752084896e4a7c813 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:48 -0700 Subject: [PATCH 7/9] tools: ynl: submsg: reverse parse / error reporting Reverse parsing lets YNL convert bad and missing attr pointers from extack into a string like "missing attribute nest1.nest2.attr_name". It's a feature that's unique to YNL C AFAIU (even the Python YNL can't do nested reverse parsing). Add support for reverse-parsing of sub-messages. To simplify the logic and the code annotate the type policies with extra metadata. Mark the selectors and the messages with the information we need. We assume that key / selector always precedes the sub-message while parsing (and also if there are multiple sub-messages like in rt-link they are interleaved selector 1 ... submsg 1 ... selector 2 .. submsg 2, not selector 1 ... selector 2 ... submsg 1 ... submsg 2). The rt-link sample in a subsequent changes shows reverse parsing of sub-messages in action. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-8-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/lib/ynl-priv.h | 5 +- tools/net/ynl/lib/ynl.c | 84 ++++++++++++++++++++++++++++---- tools/net/ynl/pyynl/ynl_gen_c.py | 29 ++++++++++- 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h index fbc058dd1c3e..416866f85820 100644 --- a/tools/net/ynl/lib/ynl-priv.h +++ b/tools/net/ynl/lib/ynl-priv.h @@ -43,7 +43,10 @@ typedef int (*ynl_parse_cb_t)(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg); struct ynl_policy_attr { - enum ynl_policy_type type; + enum ynl_policy_type type:8; + __u8 is_submsg:1; + __u8 is_selector:1; + __u16 selector_type; unsigned int len; const char *name; const struct ynl_policy_nest *nest; diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c index 25fc6501349b..2a169c3c0797 100644 --- a/tools/net/ynl/lib/ynl.c +++ b/tools/net/ynl/lib/ynl.c @@ -45,8 +45,39 @@ #define perr(_ys, _msg) __yerr(&(_ys)->err, errno, _msg) /* -- Netlink boiler plate */ +static bool +ynl_err_walk_is_sel(const struct ynl_policy_nest *policy, + const struct nlattr *attr) +{ + unsigned int type = ynl_attr_type(attr); + + return policy && type <= policy->max_attr && + policy->table[type].is_selector; +} + +static const struct ynl_policy_nest * +ynl_err_walk_sel_policy(const struct ynl_policy_attr *policy_attr, + const struct nlattr *selector) +{ + const struct ynl_policy_nest *policy = policy_attr->nest; + const char *sel; + unsigned int i; + + if (!policy_attr->is_submsg) + return policy; + + sel = ynl_attr_get_str(selector); + for (i = 0; i <= policy->max_attr; i++) { + if (!strcmp(sel, policy->table[i].name)) + return policy->table[i].nest; + } + + return NULL; +} + static int -ynl_err_walk_report_one(const struct ynl_policy_nest *policy, unsigned int type, +ynl_err_walk_report_one(const struct ynl_policy_nest *policy, + const struct nlattr *selector, unsigned int type, char *str, int str_sz, int *n) { if (!policy) { @@ -67,9 +98,34 @@ ynl_err_walk_report_one(const struct ynl_policy_nest *policy, unsigned int type, return 1; } - if (*n < str_sz) - *n += snprintf(str, str_sz - *n, - ".%s", policy->table[type].name); + if (*n < str_sz) { + int sz; + + sz = snprintf(str, str_sz - *n, + ".%s", policy->table[type].name); + *n += sz; + str += sz; + } + + if (policy->table[type].is_submsg) { + if (!selector) { + if (*n < str_sz) + *n += snprintf(str, str_sz, "(!selector)"); + return 1; + } + + if (ynl_attr_type(selector) != + policy->table[type].selector_type) { + if (*n < str_sz) + *n += snprintf(str, str_sz, "(!=selector)"); + return 1; + } + + if (*n < str_sz) + *n += snprintf(str, str_sz - *n, "(%s)", + ynl_attr_get_str(selector)); + } + return 0; } @@ -78,6 +134,8 @@ ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off, const struct ynl_policy_nest *policy, char *str, int str_sz, const struct ynl_policy_nest **nest_pol) { + const struct ynl_policy_nest *next_pol; + const struct nlattr *selector = NULL; unsigned int astart_off, aend_off; const struct nlattr *attr; unsigned int data_len; @@ -96,6 +154,10 @@ ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off, ynl_attr_for_each_payload(start, data_len, attr) { astart_off = (char *)attr - (char *)start; aend_off = (char *)ynl_attr_data_end(attr) - (char *)start; + + if (ynl_err_walk_is_sel(policy, attr)) + selector = attr; + if (aend_off <= off) continue; @@ -109,16 +171,20 @@ ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off, type = ynl_attr_type(attr); - if (ynl_err_walk_report_one(policy, type, str, str_sz, &n)) + if (ynl_err_walk_report_one(policy, selector, type, str, str_sz, &n)) + return n; + + next_pol = ynl_err_walk_sel_policy(&policy->table[type], selector); + if (!next_pol) return n; if (!off) { if (nest_pol) - *nest_pol = policy->table[type].nest; + *nest_pol = next_pol; return n; } - if (!policy->table[type].nest) { + if (!next_pol) { if (n < str_sz) n += snprintf(str, str_sz, "!nest"); return n; @@ -128,7 +194,7 @@ ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off, start = ynl_attr_data(attr); end = start + ynl_attr_data_len(attr); - return n + ynl_err_walk(ys, start, end, off, policy->table[type].nest, + return n + ynl_err_walk(ys, start, end, off, next_pol, &str[n], str_sz - n, nest_pol); } @@ -231,7 +297,7 @@ ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh, } n2 = 0; - ynl_err_walk_report_one(nest_pol, type, &miss_attr[n], + ynl_err_walk_report_one(nest_pol, NULL, type, &miss_attr[n], sizeof(miss_attr) - n, &n2); n += n2; diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index b6b54d6fa906..1f8cc34ab3f0 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -57,6 +57,8 @@ class Type(SpecAttr): self.request = False self.reply = False + self.is_selector = False + if 'len' in attr: self.len = attr['len'] @@ -484,7 +486,10 @@ class TypeString(Type): ri.cw.p(f"char *{self.c_name};") def _attr_typol(self): - return f'.type = YNL_PT_NUL_STR, ' + typol = f'.type = YNL_PT_NUL_STR, ' + if self.is_selector: + typol += '.is_selector = 1, ' + return typol def _attr_policy(self, policy): if 'exact-len' in self.checks: @@ -878,6 +883,16 @@ class TypeNestTypeValue(Type): class TypeSubMessage(TypeNest): + def __init__(self, family, attr_set, attr, value): + super().__init__(family, attr_set, attr, value) + + self.selector = Selector(attr, attr_set) + + def _attr_typol(self): + typol = f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' + typol += f'.is_submsg = 1, .selector_type = {self.attr_set[self["selector"]].value} ' + return typol + def _attr_get(self, ri, var): sel = c_lower(self['selector']) get_lines = [f'if (!{var}->{sel})', @@ -890,6 +905,18 @@ class TypeSubMessage(TypeNest): return get_lines, init_lines, None +class Selector: + def __init__(self, msg_attr, attr_set): + self.name = msg_attr["selector"] + + if self.name in attr_set: + self.attr = attr_set[self.name] + self.attr.is_selector = True + self._external = False + else: + raise Exception("Passing selectors from external nests not supported") + + class Struct: def __init__(self, family, space_name, type_list=None, inherited=None, submsg=None): From 6bab77ced3ffbce3d6c5b5bcce17da7c8a3f8266 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:49 -0700 Subject: [PATCH 8/9] tools: ynl: enable codegen for all rt- families Switch from including Classic netlink families one by one to excluding. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-9-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/Makefile.deps | 4 ++++ tools/net/ynl/generated/Makefile | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps index a5e6093903fb..e5a5cb1b2cff 100644 --- a/tools/net/ynl/Makefile.deps +++ b/tools/net/ynl/Makefile.deps @@ -33,5 +33,9 @@ CFLAGS_ovs_flow:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_ovs_vport:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_rt-addr:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \ $(call get_hdr_inc,__LINUX_IF_ADDR_H,if_addr.h) +CFLAGS_rt-link:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \ + $(call get_hdr_inc,_LINUX_IF_LINK_H,if_link.h) +CFLAGS_rt-neigh:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) CFLAGS_rt-route:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) +CFLAGS_rt-rule:=$(call get_hdr_inc,__LINUX_FIB_RULES_H,fib_rules.h) CFLAGS_tcp_metrics:=$(call get_hdr_inc,_LINUX_TCP_METRICS_H,tcp_metrics.h) diff --git a/tools/net/ynl/generated/Makefile b/tools/net/ynl/generated/Makefile index 6603ad8d4ce1..9208feed28c1 100644 --- a/tools/net/ynl/generated/Makefile +++ b/tools/net/ynl/generated/Makefile @@ -22,10 +22,9 @@ TOOL:=../pyynl/ynl_gen_c.py TOOL_RST:=../pyynl/ynl_gen_rst.py SPECS_DIR:=../../../../Documentation/netlink/specs -GENS_PATHS=$(shell grep -nrI --files-without-match \ - 'protocol: netlink' \ - $(SPECS_DIR)) -GENS=$(patsubst $(SPECS_DIR)/%.yaml,%,${GENS_PATHS}) rt-addr rt-route +SPECS_PATHS=$(wildcard $(SPECS_DIR)/*.yaml) +GENS_UNSUP=conntrack nftables tc +GENS=$(filter-out ${GENS_UNSUP},$(patsubst $(SPECS_DIR)/%.yaml,%,${SPECS_PATHS})) SRCS=$(patsubst %,%-user.c,${GENS}) HDRS=$(patsubst %,%-user.h,${GENS}) OBJS=$(patsubst %,%-user.o,${GENS}) From d5d1813b28b9a02e4d014ea898ab5dfb32818d01 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 15 May 2025 16:16:50 -0700 Subject: [PATCH 9/9] tools: ynl: add a sample for rt-link Add a fairly complete example of rt-link usage. If run without any arguments it simply lists the interfaces and some of their attrs. If run with an arg it tries to create and delete a netkit device. 1 # ./tools/net/ynl/samples/rt-link 1 2 Trying to create a Netkit interface 3 Testing error message for policy being bad: 4 Kernel error: 'Provided default xmit policy not supported' (bad attribute: .linkinfo.data(netkit).policy) 5 1: lo: mtu 65536 6 2: wlp0s1: mtu 1500 7 3: enp0s13: mtu 1500 8 4: dummy0: mtu 1500 kind dummy altname one two 9 5: nk0: mtu 1500 kind netkit primary 0 policy forward 10 6: nk1: mtu 1500 kind netkit primary 1 policy blackhole 11 Trying to delete a Netkit interface (ifindex 6) Sample creates the device first, it sets an invalid value for a netkit attribute to trigger reverse parsing. Line 4 shows the error with the attribute path correctly generated by YNL. Then sample fixes the bad attribute and re-issues the request, with NLM_F_ECHO set. This flag causes the notification to be looped back to the initiating socket (our socket). Sample parses this notification to save the ifindex of the created netkit. Sample then proceeds to list the devices. Line 8 above shows a dummy device with two alt names. Lines 9 and 10 show the netkit devices the sample itself created. The "primary" and "policy" attrs are from inside the netkit submsg. The string values are auto-generated for the enums by YNL. To clean up sample deletes the interface it created (line 11). Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250515231650.1325372-10-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/samples/.gitignore | 1 + tools/net/ynl/samples/rt-link.c | 184 +++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 tools/net/ynl/samples/rt-link.c diff --git a/tools/net/ynl/samples/.gitignore b/tools/net/ynl/samples/.gitignore index 7f9781cf532f..b3ec3fb0929f 100644 --- a/tools/net/ynl/samples/.gitignore +++ b/tools/net/ynl/samples/.gitignore @@ -4,4 +4,5 @@ netdev ovs page-pool rt-addr +rt-link rt-route diff --git a/tools/net/ynl/samples/rt-link.c b/tools/net/ynl/samples/rt-link.c new file mode 100644 index 000000000000..acdd4b4a0f74 --- /dev/null +++ b/tools/net/ynl/samples/rt-link.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +#include + +#include +#include + +#include "rt-link-user.h" + +static void rt_link_print(struct rt_link_getlink_rsp *r) +{ + unsigned int i; + + printf("%3d: ", r->_hdr.ifi_index); + + if (r->_len.ifname) + printf("%16s: ", r->ifname); + + if (r->_present.mtu) + printf("mtu %5d ", r->mtu); + + if (r->linkinfo._len.kind) + printf("kind %-8s ", r->linkinfo.kind); + else + printf(" %8s ", ""); + + if (r->prop_list._count.alt_ifname) { + printf("altname "); + for (i = 0; i < r->prop_list._count.alt_ifname; i++) + printf("%s ", r->prop_list.alt_ifname[i]->str); + printf(" "); + } + + if (r->linkinfo._present.data && r->linkinfo.data._present.netkit) { + struct rt_link_linkinfo_netkit_attrs *netkit; + const char *name; + + netkit = &r->linkinfo.data.netkit; + printf("primary %d ", netkit->primary); + + name = NULL; + if (netkit->_present.policy) + name = rt_link_netkit_policy_str(netkit->policy); + if (name) + printf("policy %s ", name); + } + + printf("\n"); +} + +static int rt_link_create_netkit(struct ynl_sock *ys) +{ + struct rt_link_getlink_ntf *ntf_gl; + struct rt_link_newlink_req *req; + struct ynl_ntf_base_type *ntf; + int ret; + + req = rt_link_newlink_req_alloc(); + if (!req) { + fprintf(stderr, "Can't alloc req\n"); + return -1; + } + + /* rtnetlink doesn't provide info about the created object. + * It expects us to set the ECHO flag and the dig the info out + * of the notifications... + */ + rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE | NLM_F_ECHO); + + rt_link_newlink_req_set_linkinfo_kind(req, "netkit"); + + /* Test error messages */ + rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, 10); + ret = rt_link_newlink(ys, req); + if (ret) { + printf("Testing error message for policy being bad:\n\t%s\n", ys->err.msg); + } else { + fprintf(stderr, "Warning: unexpected success creating netkit with bad attrs\n"); + goto created; + } + + rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, NETKIT_DROP); + + ret = rt_link_newlink(ys, req); +created: + rt_link_newlink_req_free(req); + if (ret) { + fprintf(stderr, "YNL: %s\n", ys->err.msg); + return -1; + } + + if (!ynl_has_ntf(ys)) { + fprintf(stderr, + "Warning: interface created but received no notification, won't delete the interface\n"); + return 0; + } + + ntf = ynl_ntf_dequeue(ys); + if (ntf->cmd != RTM_NEWLINK) { + fprintf(stderr, + "Warning: unexpected notification type, won't delete the interface\n"); + return 0; + } + ntf_gl = (void *)ntf; + ret = ntf_gl->obj._hdr.ifi_index; + ynl_ntf_free(ntf); + + return ret; +} + +static void rt_link_del(struct ynl_sock *ys, int ifindex) +{ + struct rt_link_dellink_req *req; + + req = rt_link_dellink_req_alloc(); + if (!req) { + fprintf(stderr, "Can't alloc req\n"); + return; + } + + req->_hdr.ifi_index = ifindex; + if (rt_link_dellink(ys, req)) + fprintf(stderr, "YNL: %s\n", ys->err.msg); + else + fprintf(stderr, + "Trying to delete a Netkit interface (ifindex %d)\n", + ifindex); + + rt_link_dellink_req_free(req); +} + +int main(int argc, char **argv) +{ + struct rt_link_getlink_req_dump *req; + struct rt_link_getlink_list *rsp; + struct ynl_error yerr; + struct ynl_sock *ys; + int created = 0; + + ys = ynl_sock_create(&ynl_rt_link_family, &yerr); + if (!ys) { + fprintf(stderr, "YNL: %s\n", yerr.msg); + return 1; + } + + if (argc > 1) { + fprintf(stderr, "Trying to create a Netkit interface\n"); + created = rt_link_create_netkit(ys); + if (created < 0) + goto err_destroy; + } + + req = rt_link_getlink_req_dump_alloc(); + if (!req) + goto err_del_ifc; + + rsp = rt_link_getlink_dump(ys, req); + rt_link_getlink_req_dump_free(req); + if (!rsp) + goto err_close; + + if (ynl_dump_empty(rsp)) + fprintf(stderr, "Error: no links reported\n"); + ynl_dump_foreach(rsp, link) + rt_link_print(link); + rt_link_getlink_list_free(rsp); + + if (created) + rt_link_del(ys, created); + + ynl_sock_destroy(ys); + return 0; + +err_close: + fprintf(stderr, "YNL: %s\n", ys->err.msg); +err_del_ifc: + if (created) + rt_link_del(ys, created); +err_destroy: + ynl_sock_destroy(ys); + return 2; +}