Merge branch 'tools-ynl-gen-additional-c-types-and-classic-netlink-handling'

Jakub Kicinski says:

====================
tools: ynl-gen: additional C types and classic netlink handling

This series is a bit of a random grab bag adding things we need
to generate code for rt-link.

First two patches are pretty random code cleanups.

Patch 3 adds default values if the spec is missing them.

Patch 4 adds support for setting Netlink request flags
(NLM_F_CREATE, NLM_F_REPLACE etc.). Classic netlink uses those
quite a bit.

Patches 5 and 6 extend the notification handling for variations
used in classic netlink. Patch 6 adds support for when notification
ID is the same as the ID of the response message to GET.

Next 4 patches add support for handling a couple of complex types.
These are supported by the schema and Python but C code gen wasn't
there.

Patch 11 is a bit of a hack, it skips code related to kernel
policy generation, since we don't need it for classic netlink.

Patch 12 adds support for having different fixed headers per op.
Something we could avoid in previous rtnetlink specs but some
specs do mix.

v2: https://lore.kernel.org/20250425024311.1589323-1-kuba@kernel.org
v1: https://lore.kernel.org/20250424021207.1167791-1-kuba@kernel.org
====================

Link: https://patch.msgid.link/20250429154704.2613851-1-kuba@kernel.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2025-05-02 12:41:05 +02:00
commit cb9d6b2c2a
4 changed files with 196 additions and 48 deletions

View File

@ -94,7 +94,7 @@ struct ynl_ntf_base_type {
unsigned char data[] __attribute__((aligned(8)));
};
struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id);
struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id, __u16 flags);
struct nlmsghdr *ynl_msg_start_dump(struct ynl_sock *ys, __u32 id);
struct nlmsghdr *

View File

@ -191,12 +191,12 @@ ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh,
n = snprintf(bad_attr, sizeof(bad_attr), "%sbad attribute: ",
str ? " (" : "");
start = ynl_nlmsg_data_offset(ys->nlh, ys->family->hdr_len);
start = ynl_nlmsg_data_offset(ys->nlh, ys->req_hdr_len);
end = ynl_nlmsg_end_addr(ys->nlh);
off = ys->err.attr_offs;
off -= sizeof(struct nlmsghdr);
off -= ys->family->hdr_len;
off -= ys->req_hdr_len;
n += ynl_err_walk(ys, start, end, off, ys->req_policy,
&bad_attr[n], sizeof(bad_attr) - n, NULL);
@ -216,14 +216,14 @@ ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh,
n = snprintf(miss_attr, sizeof(miss_attr), "%smissing attribute: ",
bad_attr[0] ? ", " : (str ? " (" : ""));
start = ynl_nlmsg_data_offset(ys->nlh, ys->family->hdr_len);
start = ynl_nlmsg_data_offset(ys->nlh, ys->req_hdr_len);
end = ynl_nlmsg_end_addr(ys->nlh);
nest_pol = ys->req_policy;
if (tb[NLMSGERR_ATTR_MISS_NEST]) {
off = ynl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_NEST]);
off -= sizeof(struct nlmsghdr);
off -= ys->family->hdr_len;
off -= ys->req_hdr_len;
n += ynl_err_walk(ys, start, end, off, ys->req_policy,
&miss_attr[n], sizeof(miss_attr) - n,
@ -451,9 +451,9 @@ ynl_gemsg_start(struct ynl_sock *ys, __u32 id, __u16 flags,
return nlh;
}
struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id)
struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id, __u16 flags)
{
return ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK);
return ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | flags);
}
struct nlmsghdr *ynl_msg_start_dump(struct ynl_sock *ys, __u32 id)

View File

@ -80,11 +80,25 @@ struct ynl_sock {
struct nlmsghdr *nlh;
const struct ynl_policy_nest *req_policy;
size_t req_hdr_len;
unsigned char *tx_buf;
unsigned char *rx_buf;
unsigned char raw_buf[];
};
/**
* struct ynl_string - parsed individual string
* @len: length of the string (excluding terminating character)
* @str: value of the string
*
* Parsed and nul-terminated string. This struct is only used for arrays of
* strings. Non-array string members are placed directly in respective types.
*/
struct ynl_string {
unsigned int len;
char str[];
};
struct ynl_sock *
ynl_sock_create(const struct ynl_family *yf, struct ynl_error *e);
void ynl_sock_destroy(struct ynl_sock *ys);

View File

@ -175,21 +175,23 @@ class Type(SpecAttr):
def arg_member(self, ri):
member = self._complex_member_type(ri)
if member:
arg = [member + ' *' + self.c_name]
spc = ' ' if member[-1] != '*' else ''
arg = [member + spc + '*' + self.c_name]
if self.presence_type() == 'count':
arg += ['unsigned int n_' + self.c_name]
return arg
raise Exception(f"Struct member not implemented for class type {self.type}")
def struct_member(self, ri):
if self.is_multi_val():
ri.cw.p(f"unsigned int n_{self.c_name};")
member = self._complex_member_type(ri)
if member:
if self.is_multi_val():
ri.cw.p(f"unsigned int n_{self.c_name};")
ptr = '*' if self.is_multi_val() else ''
if self.is_recursive_for_op(ri):
ptr = '*'
ri.cw.p(f"{member} {ptr}{self.c_name};")
spc = ' ' if member[-1] != '*' else ''
ri.cw.p(f"{member}{spc}{ptr}{self.c_name};")
return
members = self.arg_member(ri)
for one in members:
@ -355,26 +357,10 @@ class TypeScalar(Type):
if 'byte-order' in attr:
self.byte_order_comment = f" /* {attr['byte-order']} */"
if 'enum' in self.attr:
enum = self.family.consts[self.attr['enum']]
low, high = enum.value_range()
if 'min' not in self.checks:
if low != 0 or self.type[0] == 's':
self.checks['min'] = low
if 'max' not in self.checks:
self.checks['max'] = high
if 'min' in self.checks and 'max' in self.checks:
if self.get_limit('min') > self.get_limit('max'):
raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}')
self.checks['range'] = True
low = min(self.get_limit('min', 0), self.get_limit('max', 0))
high = max(self.get_limit('min', 0), self.get_limit('max', 0))
if low < 0 and self.type[0] == 'u':
raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type')
if low < -32768 or high > 32767:
self.checks['full-range'] = True
# Classic families have some funny enums, don't bother
# computing checks, since we only need them for kernel policies
if not family.is_classic():
self._init_checks()
# Added by resolve():
self.is_bitfield = None
@ -399,6 +385,28 @@ class TypeScalar(Type):
else:
self.type_name = '__' + self.type
def _init_checks(self):
if 'enum' in self.attr:
enum = self.family.consts[self.attr['enum']]
low, high = enum.value_range()
if 'min' not in self.checks:
if low != 0 or self.type[0] == 's':
self.checks['min'] = low
if 'max' not in self.checks:
self.checks['max'] = high
if 'min' in self.checks and 'max' in self.checks:
if self.get_limit('min') > self.get_limit('max'):
raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}')
self.checks['range'] = True
low = min(self.get_limit('min', 0), self.get_limit('max', 0))
high = max(self.get_limit('min', 0), self.get_limit('max', 0))
if low < 0 and self.type[0] == 'u':
raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type')
if low < -32768 or high > 32767:
self.checks['full-range'] = True
def _attr_policy(self, policy):
if 'flags-mask' in self.checks or self.is_bitfield:
if self.is_bitfield:
@ -638,19 +646,37 @@ class TypeMultiAttr(Type):
def _complex_member_type(self, ri):
if 'type' not in self.attr or self.attr['type'] == 'nest':
return self.nested_struct_type
elif self.attr['type'] == 'binary' and 'struct' in self.attr:
return None # use arg_member()
elif self.attr['type'] == 'string':
return 'struct ynl_string *'
elif self.attr['type'] in scalars:
scalar_pfx = '__' if ri.ku_space == 'user' else ''
return scalar_pfx + self.attr['type']
else:
raise Exception(f"Sub-type {self.attr['type']} not supported yet")
def arg_member(self, ri):
if self.type == 'binary' and 'struct' in self.attr:
return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}',
f'unsigned int n_{self.c_name}']
return super().arg_member(ri)
def free_needs_iter(self):
return 'type' not in self.attr or self.attr['type'] == 'nest'
return self.attr['type'] in {'nest', 'string'}
def _free_lines(self, ri, var, ref):
lines = []
if self.attr['type'] in scalars:
lines += [f"free({var}->{ref}{self.c_name});"]
elif self.attr['type'] == 'binary' and 'struct' in self.attr:
lines += [f"free({var}->{ref}{self.c_name});"]
elif self.attr['type'] == 'string':
lines += [
f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)",
f"free({var}->{ref}{self.c_name}[i]);",
f"free({var}->{ref}{self.c_name});",
]
elif 'type' not in self.attr or self.attr['type'] == 'nest':
lines += [
f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)",
@ -675,6 +701,12 @@ class TypeMultiAttr(Type):
put_type = self.type
ri.cw.p(f"for (i = 0; i < {var}->n_{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);")
elif self.attr['type'] == 'binary' and 'struct' in self.attr:
ri.cw.p(f"for (i = 0; i < {var}->n_{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}[i], sizeof(struct {c_lower(self.attr['struct'])}));")
elif self.attr['type'] == 'string':
ri.cw.p(f"for (i = 0; i < {var}->n_{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put_str(nlh, {self.enum_name}, {var}->{self.c_name}[i]->str);")
elif 'type' not in self.attr or self.attr['type'] == 'nest':
ri.cw.p(f"for (i = 0; i < {var}->n_{self.c_name}; i++)")
self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " +
@ -702,12 +734,22 @@ class TypeArrayNest(Type):
elif self.attr['sub-type'] in scalars:
scalar_pfx = '__' if ri.ku_space == 'user' else ''
return scalar_pfx + self.attr['sub-type']
elif self.attr['sub-type'] == 'binary' and 'exact-len' in self.checks:
return None # use arg_member()
else:
raise Exception(f"Sub-type {self.attr['sub-type']} not supported yet")
def arg_member(self, ri):
if self.sub_type == 'binary' and 'exact-len' in self.checks:
return [f'unsigned char (*{self.c_name})[{self.checks["exact-len"]}]',
f'unsigned int n_{self.c_name}']
return super().arg_member(ri)
def _attr_typol(self):
if self.attr['sub-type'] in scalars:
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:
return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
@ -721,6 +763,26 @@ class TypeArrayNest(Type):
'}']
return get_lines, None, 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:
put_type = self.sub_type
ri.cw.block_start(line=f'for (i = 0; i < {var}->n_{self.c_name}; i++)')
ri.cw.p(f"ynl_attr_put_{put_type}(nlh, i, {var}->{self.c_name}[i]);")
ri.cw.block_end()
elif self.sub_type == 'binary' and 'exact-len' in self.checks:
ri.cw.p(f'for (i = 0; i < {var}->n_{self.c_name}; i++)')
ri.cw.p(f"ynl_attr_put(nlh, i, {var}->{self.c_name}[i], {self.checks['exact-len']});")
else:
raise Exception(f"Put for ArrayNest 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):
# For multi-attr we have a count, not presence, hack up the presence
presence = presence[:-(len('_present.') + len(self.c_name))] + "n_" + self.c_name
return [f"{member} = {self.c_name};",
f"{presence} = n_{self.c_name};"]
class TypeNestTypeValue(Type):
def _complex_member_type(self, ri):
@ -809,6 +871,12 @@ class Struct:
raise Exception("Inheriting different members not supported")
self.inherited = [c_lower(x) for x in sorted(self._inherited)]
def free_needs_iter(self):
for _, attr in self.attr_list:
if attr.free_needs_iter():
return True
return False
class EnumEntry(SpecEnumEntry):
def __init__(self, enum_set, yaml, prev, value_start):
@ -915,7 +983,7 @@ class AttrSet(SpecAttrSet):
elif elem['type'] == 'nest':
t = TypeNest(self.family, self, elem, value)
elif elem['type'] == 'indexed-array' and 'sub-type' in elem:
if elem["sub-type"] in ['nest', 'u32']:
if elem["sub-type"] in ['binary', 'nest', 'u32']:
t = TypeArrayNest(self.family, self, elem, value)
else:
raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}')
@ -932,6 +1000,14 @@ class AttrSet(SpecAttrSet):
class Operation(SpecOperation):
def __init__(self, family, yaml, req_value, rsp_value):
# Fill in missing operation properties (for fixed hdr-only msgs)
for mode in ['do', 'dump', 'event']:
for direction in ['request', 'reply']:
try:
yaml[mode][direction].setdefault('attributes', [])
except KeyError:
pass
super().__init__(family, yaml, req_value, rsp_value)
self.render_name = c_lower(family.ident_name + '_' + self.name)
@ -1015,7 +1091,7 @@ class Family(SpecFamily):
# dict space-name -> 'request': set(attrs), 'reply': set(attrs)
self.root_sets = dict()
# dict space-name -> set('request', 'reply')
# dict space-name -> Struct
self.pure_nested_structs = dict()
self._mark_notify()
@ -1235,8 +1311,15 @@ class RenderInfo:
self.op = op
self.fixed_hdr = None
self.fixed_hdr_len = 'ys->family->hdr_len'
if op and op.fixed_header:
self.fixed_hdr = 'struct ' + c_lower(op.fixed_header)
if op.fixed_header != family.fixed_header:
if family.is_classic():
self.fixed_hdr_len = f"sizeof({self.fixed_hdr})"
else:
raise Exception(f"Per-op fixed header not supported, yet")
# 'do' and 'dump' response parsing is identical
self.type_consistent = True
@ -1267,7 +1350,7 @@ class RenderInfo:
self.struct = dict()
if op_mode == 'notify':
op_mode = 'do'
op_mode = 'do' if 'do' in op else 'dump'
for op_dir in ['request', 'reply']:
if op:
type_list = []
@ -1280,6 +1363,9 @@ class RenderInfo:
def type_empty(self, key):
return len(self.struct[key].attr_list) == 0 and self.fixed_hdr is None
def needs_nlflags(self, direction):
return self.op_mode == 'do' and direction == 'request' and self.family.is_classic()
class CodeWriter:
def __init__(self, nlib, out_file=None, overwrite=True):
@ -1685,10 +1771,15 @@ def put_req_nested(ri, struct):
local_vars.append('struct nlattr *nest;')
init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);")
has_anest = False
has_count = False
for _, arg in struct.member_list():
if arg.presence_type() == 'count':
local_vars.append('unsigned int i;')
break
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;')
put_req_nested_prototype(ri, struct, suffix='')
ri.cw.block_start()
@ -1715,13 +1806,18 @@ def _multi_parse(ri, struct, init_lines, local_vars):
if ri.fixed_hdr:
local_vars += ['void *hdr;']
iter_line = "ynl_attr_for_each(attr, nlh, yarg->ys->family->hdr_len)"
if ri.op.fixed_header != ri.family.fixed_header:
if ri.family.is_classic():
iter_line = f"ynl_attr_for_each(attr, nlh, sizeof({ri.fixed_hdr}))"
else:
raise Exception(f"Per-op fixed header not supported, yet")
array_nests = 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"] == 'nest':
if aspec["sub-type"] in {'binary', 'nest'}:
local_vars.append(f'const struct nlattr *attr_{aspec.c_name};')
array_nests.add(arg)
elif aspec['sub-type'] in scalars:
@ -1794,6 +1890,9 @@ def _multi_parse(ri, struct, init_lines, local_vars):
ri.cw.p('return YNL_PARSE_CB_ERROR;')
elif aspec.sub_type in scalars:
ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.sub_type}(attr);")
elif aspec.sub_type == 'binary' and 'exact-len' in aspec.checks:
# Length is validated by typol
ri.cw.p(f'memcpy(dst->{aspec.c_name}[i], ynl_attr_data(attr), {aspec.checks["exact-len"]});')
else:
raise Exception(f"Nest parsing type not supported in {aspec['name']}")
ri.cw.p('i++;')
@ -1817,8 +1916,22 @@ def _multi_parse(ri, struct, init_lines, local_vars):
ri.cw.p('return YNL_PARSE_CB_ERROR;')
elif aspec.type in scalars:
ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.type}(attr);")
elif aspec.type == 'binary' and 'struct' in aspec:
ri.cw.p('size_t len = ynl_attr_data_len(attr);')
ri.cw.nl()
ri.cw.p(f'if (len > sizeof(dst->{aspec.c_name}[0]))')
ri.cw.p(f'len = sizeof(dst->{aspec.c_name}[0]);')
ri.cw.p(f"memcpy(&dst->{aspec.c_name}[i], ynl_attr_data(attr), len);")
elif aspec.type == 'string':
ri.cw.p('unsigned int len;')
ri.cw.nl()
ri.cw.p('len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));')
ri.cw.p(f'dst->{aspec.c_name}[i] = malloc(sizeof(struct ynl_string) + len + 1);')
ri.cw.p(f"dst->{aspec.c_name}[i]->len = len;")
ri.cw.p(f"memcpy(dst->{aspec.c_name}[i]->str, ynl_attr_get_str(attr), len);")
ri.cw.p(f"dst->{aspec.c_name}[i]->str[len] = 0;")
else:
raise Exception('Nest parsing type not supported yet')
raise Exception(f'Nest parsing of type {aspec.type} not supported yet')
ri.cw.p('i++;')
ri.cw.block_end()
ri.cw.block_end()
@ -1910,11 +2023,12 @@ def print_req(ri):
ri.cw.write_func_lvar(local_vars)
if ri.family.is_classic():
ri.cw.p(f"nlh = ynl_msg_start_req(ys, {ri.op.enum_name});")
ri.cw.p(f"nlh = ynl_msg_start_req(ys, {ri.op.enum_name}, req->_nlmsg_flags);")
else:
ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")
ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
ri.cw.p(f"ys->req_hdr_len = {ri.fixed_hdr_len};")
if 'reply' in ri.op[ri.op_mode]:
ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
ri.cw.nl()
@ -1994,6 +2108,7 @@ def print_dump(ri):
if "request" in ri.op[ri.op_mode]:
ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
ri.cw.p(f"ys->req_hdr_len = {ri.fixed_hdr_len};")
ri.cw.nl()
for _, attr in ri.struct["request"].member_list():
attr.attr_put(ri, "req")
@ -2039,6 +2154,16 @@ def print_free_prototype(ri, direction, suffix=';'):
ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix)
def print_nlflags_set(ri, direction):
name = op_prefix(ri, direction)
ri.cw.write_func_prot(f'static inline void', f"{name}_set_nlflags",
[f"struct {name} *req", "__u16 nl_flags"])
ri.cw.block_start()
ri.cw.p('req->_nlmsg_flags = nl_flags;')
ri.cw.block_end()
ri.cw.nl()
def _print_type(ri, direction, struct):
suffix = f'_{ri.type_name}{direction_to_suffix[direction]}'
if not direction and ri.type_name_conflict:
@ -2049,6 +2174,9 @@ def _print_type(ri, direction, struct):
ri.cw.block_start(line=f"struct {ri.family.c_name}{suffix}")
if ri.needs_nlflags(direction):
ri.cw.p('__u16 _nlmsg_flags;')
ri.cw.nl()
if ri.fixed_hdr:
ri.cw.p(ri.fixed_hdr + ' _hdr;')
ri.cw.nl()
@ -2088,6 +2216,9 @@ def print_type_helpers(ri, direction, deref=False):
print_free_prototype(ri, direction)
ri.cw.nl()
if ri.needs_nlflags(direction):
print_nlflags_set(ri, direction)
if ri.ku_space == 'user' and direction == 'request':
for _, attr in ri.struct[direction].member_list():
attr.setter(ri, ri.attr_set, direction, deref=deref)
@ -2156,11 +2287,9 @@ def print_wrapped_type(ri):
def _free_type_members_iter(ri, struct):
for _, attr in struct.member_list():
if attr.free_needs_iter():
ri.cw.p('unsigned int i;')
ri.cw.nl()
break
if struct.free_needs_iter():
ri.cw.p('unsigned int i;')
ri.cw.nl()
def _free_type_members(ri, var, struct, ref=''):
@ -2756,7 +2885,11 @@ def render_uapi(family, cw):
def _render_user_ntf_entry(ri, op):
ri.cw.block_start(line=f"[{op.enum_name}] = ")
if not ri.family.is_classic():
ri.cw.block_start(line=f"[{op.enum_name}] = ")
else:
crud_op = ri.family.req_by_value[op.rsp_value]
ri.cw.block_start(line=f"[{crud_op.enum_name}] = ")
ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),")
ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,")
ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,")
@ -2795,7 +2928,8 @@ def render_user_family(family, cw, prototype):
cw.p(f'.is_classic\t= true,')
cw.p(f'.classic_id\t= {family.get("protonum")},')
if family.is_classic():
cw.p(f'.hdr_len\t= sizeof(struct {c_lower(family.fixed_header)}),')
if family.fixed_header:
cw.p(f'.hdr_len\t= sizeof(struct {c_lower(family.fixed_header)}),')
elif family.fixed_header:
cw.p(f'.hdr_len\t= sizeof(struct genlmsghdr) + sizeof(struct {c_lower(family.fixed_header)}),')
else: