Allow using custom name-prefix with constants, just like it is for enum and flags declarations. This is needed for generating WG_KEY_LEN in include/uapi/linux/wireguard.h from a spec. Signed-off-by: Asbjørn Sloth Tønnesen Reviewed-by: Donald Hunter Reviewed-by: Jakub Kicinski Reviewed-by: Jacob Keller --- tools/net/ynl/pyynl/ynl_gen_c.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index fb7e03805a11..1543d4911bf5 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -3211,8 +3211,9 @@ def render_uapi(family, cw): cw.block_end(line=';') cw.nl() elif const['type'] == 'const': + name_pfx = const.get('name-prefix', f"{family.ident_name}-") defines.append([c_upper(family.get('c-define-name', - f"{family.ident_name}-{const['name']}")), + f"{name_pfx}{const['name']}")), const['value']]) if defines: -- 2.51.0 This patch adds support for NLA_POLICY_NESTED_ARRAY() policies. Example spec (from future wireguard.yaml): - name: wgpeer attributes: - name: allowedips type: indexed-array sub-type: nest nested-attributes: wgallowedip yields NLA_POLICY_NESTED_ARRAY(wireguard_wgallowedip_nl_policy). This doesn't change any currently generated code, as it isn't used in any specs currently used for generating code. Signed-off-by: Asbjørn Sloth Tønnesen Reviewed-by: Donald Hunter Reviewed-by: Jakub Kicinski --- tools/net/ynl/pyynl/ynl_gen_c.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 1543d4911bf5..b7de7f6b1fc7 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -816,6 +816,11 @@ class TypeArrayNest(Type): f'unsigned int n_{self.c_name}'] return super().arg_member(ri) + def _attr_policy(self, policy): + if self.attr['sub-type'] == 'nest': + return f'NLA_POLICY_NESTED_ARRAY({self.nested_render_name}_nl_policy)' + return super()._attr_policy(policy) + def _attr_typol(self): if self.attr['sub-type'] in scalars: return f'.type = YNL_PT_U{c_upper(self.sub_type[1:])}, ' -- 2.51.0 Add a check to verify that the sub-type is "nest", and throw an exception if no policy could be generated, as a guard to prevent against generating a bad policy. This is a trivial patch with no behavioural changes intended. Signed-off-by: Asbjørn Sloth Tønnesen Reviewed-by: Donald Hunter Reviewed-by: Jakub Kicinski Reviewed-by: Jacob Keller --- tools/net/ynl/pyynl/ynl_gen_c.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index b7de7f6b1fc7..04c26ed92ca3 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -826,8 +826,10 @@ class TypeArrayNest(Type): return f'.type = YNL_PT_U{c_upper(self.sub_type[1:])}, ' elif self.attr['sub-type'] == 'binary' and 'exact-len' in self.checks: return f'.type = YNL_PT_BINARY, .len = {self.checks["exact-len"]}, ' - else: + elif self.attr['sub-type'] == 'nest': return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' + else: + raise Exception(f"Typol for ArrayNest sub-type {self.attr['sub-type']} not supported, yet") def _attr_get(self, ri, var): local_vars = ['const struct nlattr *attr2;'] -- 2.51.0 Refactor the generation of local variables needed when building requests, by moving the logic into the Type classes, and use the same helper in all places where .attr_put() is called. If any attributes requests identical local_vars, then they will be deduplicated, attributes are assumed to only use their local variables transiently. This patch fixes the build errors below: $ make -C tools/net/ynl/generated/ [...] -e GEN wireguard-user.c -e GEN wireguard-user.h -e CC wireguard-user.o wireguard-user.c: In function ‘wireguard_get_device_dump’: wireguard-user.c:480:9: error: ‘array’ undeclared (first use in func) 480 | array = ynl_attr_nest_start(nlh, WGDEVICE_A_PEERS); | ^~~~~ wireguard-user.c:480:9: note: each undeclared identifier is reported only once for each function it appears in wireguard-user.c:481:14: error: ‘i’ undeclared (first use in func) 481 | for (i = 0; i < req->_count.peers; i++) | ^ wireguard-user.c: In function ‘wireguard_set_device’: wireguard-user.c:533:9: error: ‘array’ undeclared (first use in func) 533 | array = ynl_attr_nest_start(nlh, WGDEVICE_A_PEERS); | ^~~~~ make: *** [Makefile:52: wireguard-user.o] Error 1 make: Leaving directory '/usr/src/linux/tools/net/ynl/generated' Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/pyynl/ynl_gen_c.py | 37 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 04c26ed92ca3..6441d5a31391 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -236,6 +236,12 @@ class Type(SpecAttr): line = f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name})" self._attr_put_line(ri, var, line) + def attr_put_local_vars(self): + local_vars = [] + if self.presence_type() == 'count': + local_vars.append('unsigned int i;') + return local_vars + def attr_put(self, ri, var): raise Exception(f"Put not implemented for class type {self.type}") @@ -841,6 +847,10 @@ class TypeArrayNest(Type): '}'] return get_lines, None, local_vars + def attr_put_local_vars(self): + local_vars = ['struct nlattr *array;'] + return local_vars + super().attr_put_local_vars() + def attr_put(self, ri, var): ri.cw.p(f'array = ynl_attr_nest_start(nlh, {self.enum_name});') if self.sub_type in scalars: @@ -2041,6 +2051,15 @@ def put_enum_to_str(family, cw, enum): _put_enum_to_str_helper(cw, enum.render_name, map_name, 'value', enum=enum) +def put_local_vars(struct): + local_vars = [] + for _, attr in struct.member_list(): + for local_var in attr.attr_put_local_vars(): + if local_var not in local_vars: + local_vars.append(local_var) + return local_vars + + def put_req_nested_prototype(ri, struct, suffix=';'): func_args = ['struct nlmsghdr *nlh', 'unsigned int attr_type', @@ -2063,15 +2082,7 @@ def put_req_nested(ri, struct): init_lines.append(f"hdr = ynl_nlmsg_put_extra_header(nlh, {struct_sz});") init_lines.append(f"memcpy(hdr, &obj->_hdr, {struct_sz});") - has_anest = False - has_count = False - for _, arg in struct.member_list(): - has_anest |= arg.type == 'indexed-array' - has_count |= arg.presence_type() == 'count' - if has_anest: - local_vars.append('struct nlattr *array;') - if has_count: - local_vars.append('unsigned int i;') + local_vars += put_local_vars(struct) put_req_nested_prototype(ri, struct, suffix='') ri.cw.block_start() @@ -2355,10 +2366,7 @@ def print_req(ri): local_vars += ['size_t hdr_len;', 'void *hdr;'] - for _, attr in ri.struct["request"].member_list(): - if attr.presence_type() == 'count': - local_vars += ['unsigned int i;'] - break + local_vars += put_local_vars(ri.struct["request"]) print_prototype(ri, direction, terminate=False) ri.cw.block_start() @@ -2425,6 +2433,9 @@ def print_dump(ri): local_vars += ['size_t hdr_len;', 'void *hdr;'] + if "request" in ri.op[ri.op_mode]: + local_vars += put_local_vars(ri.struct["request"]) + ri.cw.write_func_lvar(local_vars) ri.cw.p('yds.yarg.ys = ys;') -- 2.51.0 Add a helper for writing an array of lines, and convert all the existing loops doing that, to use the new helper. This is a trivial patch with no behavioural changes intended, there are no changes to the generated code. Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/pyynl/ynl_gen_c.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 6441d5a31391..18c6ed0044b9 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -181,8 +181,7 @@ class Type(SpecAttr): def free(self, ri, var, ref): lines = self._free_lines(ri, var, ref) - for line in lines: - ri.cw.p(line) + ri.cw.p_lines(lines) def arg_member(self, ri): member = self._complex_member_type(ri) @@ -268,13 +267,9 @@ class Type(SpecAttr): if self.presence_type() == 'present': ri.cw.p(f"{var}->_present.{self.c_name} = 1;") - if init_lines: - ri.cw.nl() - for line in init_lines: - ri.cw.p(line) + ri.cw.p_lines(init_lines, nl_before=True) - for line in lines: - ri.cw.p(line) + ri.cw.p_lines(lines) ri.cw.block_end() return True @@ -1789,8 +1784,7 @@ class CodeWriter: self.block_start() self.write_func_lvar(local_vars=local_vars) - for line in body: - self.p(line) + self.p_lines(body) self.block_end() def writes_defines(self, defines): @@ -1831,6 +1825,12 @@ class CodeWriter: self.p('#ifdef ' + config_option) self._ifdef_block = config_option + def p_lines(self, lines, nl_before=False): + if lines and nl_before: + self.nl() + for line in lines or []: + self.p(line) + scalars = {'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'uint', 'sint'} @@ -2088,8 +2088,7 @@ def put_req_nested(ri, struct): ri.cw.block_start() ri.cw.write_func_lvar(local_vars) - for line in init_lines: - ri.cw.p(line) + ri.cw.p_lines(init_lines) for _, arg in struct.member_list(): arg.attr_put(ri, "obj") @@ -2150,8 +2149,7 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.block_start() ri.cw.write_func_lvar(local_vars) - for line in init_lines: - ri.cw.p(line) + ri.cw.p_lines(init_lines) ri.cw.nl() for arg in struct.inherited: @@ -2280,10 +2278,8 @@ def parse_rsp_submsg(ri, struct): ri.cw.block_start(line=f'{kw} (!strcmp(sel, "{name}"))') get_lines, init_lines, _ = arg._attr_get(ri, var) - for line in init_lines or []: - ri.cw.p(line) - for line in get_lines: - ri.cw.p(line) + ri.cw.p_lines(init_lines) + ri.cw.p_lines(get_lines) if arg.presence_type() == 'present': ri.cw.p(f"{var}->_present.{arg.c_name} = 1;") ri.cw.block_end() -- 2.51.0 Fixed headers are handled nearly identical in print_dump(), print_req() and put_req_nested(), generalize them and use a common function to generate them. This only causes cosmetic changes to tc_netem_attrs_put() in tc-user.c. Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/pyynl/ynl_gen_c.py | 39 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 18c6ed0044b9..f149c68ae84e 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -1829,7 +1829,10 @@ class CodeWriter: if lines and nl_before: self.nl() for line in lines or []: - self.p(line) + if line == '': + self.nl() + else: + self.p(line) scalars = {'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'uint', 'sint'} @@ -1922,6 +1925,15 @@ def type_name(ri, direction, deref=False): return f"struct {op_prefix(ri, direction, deref=deref)}" +def prepare_fixed_header(var, local_vars, init_lines): + local_vars += ['size_t hdr_len;', + 'void *hdr;'] + init_lines += [f'hdr_len = sizeof({var}->_hdr);', + 'hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);', + f'memcpy(hdr, &{var}->_hdr, hdr_len);', + ''] + + def print_prototype(ri, direction, terminate=True, doc=None): suffix = ';' if terminate else '' @@ -2077,10 +2089,7 @@ def put_req_nested(ri, struct): local_vars.append('struct nlattr *nest;') init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);") if struct.fixed_header: - local_vars.append('void *hdr;') - struct_sz = f'sizeof({struct.fixed_header})' - init_lines.append(f"hdr = ynl_nlmsg_put_extra_header(nlh, {struct_sz});") - init_lines.append(f"memcpy(hdr, &obj->_hdr, {struct_sz});") + prepare_fixed_header('obj', local_vars, init_lines) local_vars += put_local_vars(struct) @@ -2349,6 +2358,7 @@ def print_req(ri): ret_ok = '0' ret_err = '-1' direction = "request" + init_lines = [] local_vars = ['struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };', 'struct nlmsghdr *nlh;', 'int err;'] @@ -2359,8 +2369,7 @@ def print_req(ri): local_vars += [f'{type_name(ri, rdir(direction))} *rsp;'] if ri.struct["request"].fixed_header: - local_vars += ['size_t hdr_len;', - 'void *hdr;'] + prepare_fixed_header('req', local_vars, init_lines) local_vars += put_local_vars(ri.struct["request"]) @@ -2379,11 +2388,7 @@ def print_req(ri): ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") ri.cw.nl() - if ri.struct['request'].fixed_header: - ri.cw.p("hdr_len = sizeof(req->_hdr);") - ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);") - ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") - ri.cw.nl() + ri.cw.p_lines(init_lines) for _, attr in ri.struct["request"].member_list(): attr.attr_put(ri, "req") @@ -2421,13 +2426,13 @@ def print_dump(ri): direction = "request" print_prototype(ri, direction, terminate=False) ri.cw.block_start() + init_lines = [] local_vars = ['struct ynl_dump_state yds = {};', 'struct nlmsghdr *nlh;', 'int err;'] if ri.struct['request'].fixed_header: - local_vars += ['size_t hdr_len;', - 'void *hdr;'] + prepare_fixed_header('req', local_vars, init_lines) if "request" in ri.op[ri.op_mode]: local_vars += put_local_vars(ri.struct["request"]) @@ -2449,11 +2454,7 @@ def print_dump(ri): else: ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") - if ri.struct['request'].fixed_header: - ri.cw.p("hdr_len = sizeof(req->_hdr);") - ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);") - ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") - ri.cw.nl() + ri.cw.p_lines(init_lines) if "request" in ri.op[ri.op_mode]: ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") -- 2.51.0 In nested arrays don't require that the intermediate attribute type should be a valid attribute type, it might just be zero or an incrementing index, it is often not even used. See include/net/netlink.h about NLA_NESTED_ARRAY: > The difference to NLA_NESTED is the structure: > NLA_NESTED has the nested attributes directly inside > while an array has the nested attributes at another > level down and the attribute types directly in the > nesting don't matter. Example based on include/uapi/linux/wireguard.h: > WGDEVICE_A_PEERS: NLA_NESTED > 0: NLA_NESTED > WGPEER_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN > [..] > 0: NLA_NESTED > ... > ... Previous the check required that the nested type was valid in the parent attribute set, which in this case resolves to WGDEVICE_A_UNSPEC, which is YNL_PT_REJECT, and it took the early exit and returned YNL_PARSE_CB_ERROR. This patch adds a new helper, ynl_attr_validate_payload(), which we can use to validate the payload of the nested attribute, in the context of the parents attribute type, and it's policy, which in the above case is generated as: [WGDEVICE_A_PEERS] = { .name = "peers", .type = YNL_PT_NEST, .nest = &wireguard_wgpeer_nest, }, Some other examples are NL80211_BAND_ATTR_FREQS (nest) and NL80211_ATTR_SUPPORTED_COMMANDS (u32). Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/lib/ynl-priv.h | 2 ++ tools/net/ynl/lib/ynl.c | 17 ++++++++++++++--- tools/net/ynl/pyynl/ynl_gen_c.py | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h index 824777d7e05e..70ea14c0a0e9 100644 --- a/tools/net/ynl/lib/ynl-priv.h +++ b/tools/net/ynl/lib/ynl-priv.h @@ -107,6 +107,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_attr_validate_payload(struct ynl_parse_arg *yarg, + const struct nlattr *attr, unsigned int type); int ynl_submsg_failed(struct ynl_parse_arg *yarg, const char *field_name, const char *sel_name); diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c index 2a169c3c0797..d52aa188401f 100644 --- a/tools/net/ynl/lib/ynl.c +++ b/tools/net/ynl/lib/ynl.c @@ -360,15 +360,15 @@ static int ynl_cb_done(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) /* Attribute validation */ -int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr) +int __ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr, + unsigned int type) { const struct ynl_policy_attr *policy; - unsigned int type, len; unsigned char *data; + unsigned int len; data = ynl_attr_data(attr); len = ynl_attr_data_len(attr); - type = ynl_attr_type(attr); if (type > yarg->rsp_policy->max_attr) { yerr(yarg->ys, YNL_ERROR_INTERNAL, "Internal error, validating unknown attribute"); @@ -450,6 +450,17 @@ int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr) return 0; } +int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr) +{ + return __ynl_attr_validate(yarg, attr, ynl_attr_type(attr)); +} + +int ynl_attr_validate_payload(struct ynl_parse_arg *yarg, + const struct nlattr *attr, unsigned int type) +{ + return __ynl_attr_validate(yarg, attr, type); +} + int ynl_submsg_failed(struct ynl_parse_arg *yarg, const char *field_name, const char *sel_name) { diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index f149c68ae84e..e93f5d724b42 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -836,7 +836,7 @@ class TypeArrayNest(Type): local_vars = ['const struct nlattr *attr2;'] get_lines = [f'attr_{self.c_name} = attr;', 'ynl_attr_for_each_nested(attr2, attr) {', - '\tif (ynl_attr_validate(yarg, attr2))', + '\tif (ynl_attr_validate_payload(yarg, attr2, type))', '\t\treturn YNL_PARSE_CB_ERROR;', f'\tn_{self.c_name}++;', '}'] -- 2.51.0 Since TypeArrayNest can now be used with many other sub-types than nest, then rename it to TypeIndexedArray, to reduce confusion. This patch continues the rename, that was started in commit aa6485d813ad ("ynl: rename array-nest to indexed-array"), when the YNL type was renamed. In order to get rid of all references to the old naming, within ynl, then renaming some variables in _multi_parse(). This is a trivial patch with no behavioural changes intended. Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/pyynl/ynl_gen_c.py | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index e93f5d724b42..574faa89b1f7 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -793,7 +793,7 @@ class TypeMultiAttr(Type): f"{presence} = n_{self.c_name};"] -class TypeArrayNest(Type): +class TypeIndexedArray(Type): def is_multi_val(self): return True @@ -830,7 +830,7 @@ class TypeArrayNest(Type): elif self.attr['sub-type'] == 'nest': return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' else: - raise Exception(f"Typol for ArrayNest sub-type {self.attr['sub-type']} not supported, yet") + raise Exception(f"Typol for IndexedArray sub-type {self.attr['sub-type']} not supported, yet") def _attr_get(self, ri, var): local_vars = ['const struct nlattr *attr2;'] @@ -860,7 +860,7 @@ class TypeArrayNest(Type): ri.cw.p(f'for (i = 0; i < {var}->_count.{self.c_name}; i++)') ri.cw.p(f"{self.nested_render_name}_put(nlh, i, &{var}->{self.c_name}[i]);") else: - raise Exception(f"Put for ArrayNest sub-type {self.attr['sub-type']} not supported, yet") + raise Exception(f"Put for IndexedArray sub-type {self.attr['sub-type']} not supported, yet") ri.cw.p('ynl_attr_nest_end(nlh, array);') def _setter_lines(self, ri, member, presence): @@ -1137,7 +1137,7 @@ class AttrSet(SpecAttrSet): t = TypeNest(self.family, self, elem, value) elif elem['type'] == 'indexed-array' and 'sub-type' in elem: if elem["sub-type"] in ['binary', 'nest', 'u32']: - t = TypeArrayNest(self.family, self, elem, value) + t = TypeIndexedArray(self.family, self, elem, value) else: raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}') elif elem['type'] == 'nest-type-value': @@ -2127,33 +2127,33 @@ def _multi_parse(ri, struct, init_lines, local_vars): else: raise Exception(f"Per-op fixed header not supported, yet") - array_nests = set() + indexed_arrays = set() multi_attrs = set() needs_parg = False for arg, aspec in struct.member_list(): if aspec['type'] == 'indexed-array' and 'sub-type' in aspec: if aspec["sub-type"] in {'binary', 'nest'}: local_vars.append(f'const struct nlattr *attr_{aspec.c_name};') - array_nests.add(arg) + indexed_arrays.add(arg) elif aspec['sub-type'] in scalars: local_vars.append(f'const struct nlattr *attr_{aspec.c_name};') - array_nests.add(arg) + indexed_arrays.add(arg) else: raise Exception(f'Not supported sub-type {aspec["sub-type"]}') 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: + if indexed_arrays or multi_attrs: local_vars.append('int i;') if needs_parg: local_vars.append('struct ynl_parse_arg parg;') init_lines.append('parg.ys = yarg->ys;') - all_multi = array_nests | multi_attrs + all_multi = indexed_arrays | multi_attrs - for anest in sorted(all_multi): - local_vars.append(f"unsigned int n_{struct[anest].c_name} = 0;") + for arg in sorted(all_multi): + local_vars.append(f"unsigned int n_{struct[arg].c_name} = 0;") ri.cw.block_start() ri.cw.write_func_lvar(local_vars) @@ -2172,8 +2172,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): else: ri.cw.p('hdr = ynl_nlmsg_data_offset(nlh, sizeof(struct genlmsghdr));') ri.cw.p(f"memcpy(&dst->_hdr, hdr, sizeof({struct.fixed_header}));") - for anest in sorted(all_multi): - aspec = struct[anest] + for arg in sorted(all_multi): + aspec = struct[arg] ri.cw.p(f"if (dst->{aspec.c_name})") ri.cw.p(f'return ynl_error_parse(yarg, "attribute already present ({struct.attr_set.name}.{aspec.name})");') @@ -2191,8 +2191,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.block_end() ri.cw.nl() - for anest in sorted(array_nests): - aspec = struct[anest] + for arg in sorted(indexed_arrays): + aspec = struct[arg] ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") @@ -2217,8 +2217,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.block_end() ri.cw.nl() - for anest in sorted(multi_attrs): - aspec = struct[anest] + for arg in sorted(multi_attrs): + aspec = struct[arg] ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") -- 2.51.0 This patch moves nest packing into a helper function, that can also be used for packing indexed arrays. No behavioural changes intended. Signed-off-by: Asbjørn Sloth Tønnesen Reviewed-by: Donald Hunter --- tools/net/ynl/pyynl/lib/ynl.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 8244a5f440b2..4928b41c636a 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -562,11 +562,8 @@ class YnlFamily(SpecFamily): if attr["type"] == 'nest': nl_type |= Netlink.NLA_F_NESTED - attr_payload = b'' sub_space = attr['nested-attributes'] - sub_attrs = SpaceAttrs(self.attr_sets[sub_space], value, search_attrs) - for subname, subvalue in value.items(): - attr_payload += self._add_attr(sub_space, subname, subvalue, sub_attrs) + attr_payload = self._add_nest_attrs(value, sub_space, search_attrs) elif attr["type"] == 'flag': if not value: # If value is absent or false then skip attribute creation. @@ -623,6 +620,14 @@ class YnlFamily(SpecFamily): pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad + def _add_nest_attrs(self, value, sub_space, search_attrs): + sub_attrs = SpaceAttrs(self.attr_sets[sub_space], value, search_attrs) + attr_payload = b'' + for subname, subvalue in value.items(): + attr_payload += self._add_attr(sub_space, subname, subvalue, + sub_attrs) + return attr_payload + def _get_enum_or_unknown(self, enum, raw): try: name = enum.entries_by_val[raw].name -- 2.51.0 This patch adds support for encoding indexed-array attributes with sub-type nest in pyynl. Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/pyynl/lib/ynl.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 4928b41c636a..7ddf689a2a6e 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -564,6 +564,11 @@ class YnlFamily(SpecFamily): nl_type |= Netlink.NLA_F_NESTED sub_space = attr['nested-attributes'] attr_payload = self._add_nest_attrs(value, sub_space, search_attrs) + elif attr['type'] == 'indexed-array' and attr['sub-type'] == 'nest': + nl_type |= Netlink.NLA_F_NESTED + sub_space = attr['nested-attributes'] + attr_payload = self._encode_indexed_array(value, sub_space, + search_attrs) elif attr["type"] == 'flag': if not value: # If value is absent or false then skip attribute creation. @@ -617,6 +622,9 @@ class YnlFamily(SpecFamily): else: raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') + return self._add_attr_raw(nl_type, attr_payload) + + def _add_attr_raw(self, nl_type, attr_payload): pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad @@ -628,6 +636,14 @@ class YnlFamily(SpecFamily): sub_attrs) return attr_payload + def _encode_indexed_array(self, vals, sub_space, search_attrs): + attr_payload = b'' + for i, val in enumerate(vals): + idx = i | Netlink.NLA_F_NESTED + val_payload = self._add_nest_attrs(val, sub_space, search_attrs) + attr_payload += self._add_attr_raw(idx, val_payload) + return attr_payload + def _get_enum_or_unknown(self, enum, raw): try: name = enum.entries_by_val[raw].name -- 2.51.0 This patch adds support for decoding hex input, so that binary attributes can be read through --json. Example (using future wireguard.yaml): $ sudo ./tools/net/ynl/pyynl/cli.py --family wireguard \ --do set-device --json '{"ifindex":3, "private-key":"2a ae 6c 35 c9 4f cf <... to 32 bytes>"}' In order to somewhat mirror what is done in _formatted_string(), then for non-binary attributes attempt to convert it to an int. Signed-off-by: Asbjørn Sloth Tønnesen --- tools/net/ynl/pyynl/lib/ynl.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 7ddf689a2a6e..50f4889e721b 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -972,6 +972,11 @@ class YnlFamily(SpecFamily): raw = ip.packed else: raw = int(ip) + elif attr_spec.display_hint == 'hex': + if attr_spec['type'] == 'binary': + raw = bytes.fromhex(string) + else: + raw = int(string, 16) else: raise Exception(f"Display hint '{attr_spec.display_hint}' not implemented" f" when parsing '{attr_spec['name']}'") -- 2.51.0 The attribute WGALLOWEDIP_A_IPADDR can contain either an IPv4 or an IPv6 address depending on WGALLOWEDIP_A_FAMILY, however in practice it is enough to look at the attribute length. This patch implements an ipv4-or-v6 display hint, that can deal with this kind of attribute. It only implements this display hint for genetlink-legacy, it can be added to other protocol variants if needed, but we don't want to encourage it's use. Signed-off-by: Asbjørn Sloth Tønnesen Reviewed-by: Donald Hunter --- Documentation/netlink/genetlink-legacy.yaml | 2 +- tools/net/ynl/pyynl/lib/ynl.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/netlink/genetlink-legacy.yaml b/Documentation/netlink/genetlink-legacy.yaml index b29d62eefa16..66fb8653a344 100644 --- a/Documentation/netlink/genetlink-legacy.yaml +++ b/Documentation/netlink/genetlink-legacy.yaml @@ -154,7 +154,7 @@ properties: Optional format indicator that is intended only for choosing the right formatting mechanism when displaying values of this type. - enum: [ hex, mac, fddi, ipv4, ipv6, uuid ] + enum: [ hex, mac, fddi, ipv4, ipv6, ipv4-or-v6, uuid ] struct: description: Name of the nested struct type. type: string diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 50f4889e721b..4f2c8126d6e9 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -957,7 +957,7 @@ class YnlFamily(SpecFamily): formatted = hex(raw) else: formatted = bytes.hex(raw, ' ') - elif display_hint in [ 'ipv4', 'ipv6' ]: + elif display_hint in [ 'ipv4', 'ipv6', 'ipv4-or-v6' ]: formatted = format(ipaddress.ip_address(raw)) elif display_hint == 'uuid': formatted = str(uuid.UUID(bytes=raw)) @@ -966,7 +966,7 @@ class YnlFamily(SpecFamily): return formatted def _from_string(self, string, attr_spec): - if attr_spec.display_hint in ['ipv4', 'ipv6']: + if attr_spec.display_hint in ['ipv4', 'ipv6', 'ipv4-or-v6']: ip = ipaddress.ip_address(string) if attr_spec['type'] == 'binary': raw = ip.packed -- 2.51.0