diff options
Diffstat (limited to 'tools/net/ynl/lib')
-rw-r--r-- | tools/net/ynl/lib/.gitignore | 1 | ||||
-rw-r--r-- | tools/net/ynl/lib/Makefile | 1 | ||||
-rw-r--r-- | tools/net/ynl/lib/__init__.py | 8 | ||||
-rw-r--r-- | tools/net/ynl/lib/nlspec.py | 614 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl-priv.h | 19 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl.c | 164 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl.h | 18 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl.py | 1063 |
8 files changed, 162 insertions, 1726 deletions
diff --git a/tools/net/ynl/lib/.gitignore b/tools/net/ynl/lib/.gitignore index 296c4035dbf2..a4383358ec72 100644 --- a/tools/net/ynl/lib/.gitignore +++ b/tools/net/ynl/lib/.gitignore @@ -1,2 +1 @@ -__pycache__/ *.d diff --git a/tools/net/ynl/lib/Makefile b/tools/net/ynl/lib/Makefile index 94c49cca3dca..4b2b98704ff9 100644 --- a/tools/net/ynl/lib/Makefile +++ b/tools/net/ynl/lib/Makefile @@ -19,7 +19,6 @@ ynl.a: $(OBJS) clean: rm -f *.o *.d *~ - rm -rf __pycache__ distclean: clean rm -f *.a diff --git a/tools/net/ynl/lib/__init__.py b/tools/net/ynl/lib/__init__.py deleted file mode 100644 index 9137b83e580a..000000000000 --- a/tools/net/ynl/lib/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ - SpecFamily, SpecOperation -from .ynl import YnlFamily, Netlink, NlError - -__all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", - "SpecFamily", "SpecOperation", "YnlFamily", "Netlink", "NlError"] diff --git a/tools/net/ynl/lib/nlspec.py b/tools/net/ynl/lib/nlspec.py deleted file mode 100644 index a745739655ad..000000000000 --- a/tools/net/ynl/lib/nlspec.py +++ /dev/null @@ -1,614 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -import collections -import importlib -import os -import yaml - - -# To be loaded dynamically as needed -jsonschema = None - - -class SpecElement: - """Netlink spec element. - - Abstract element of the Netlink spec. Implements the dictionary interface - for access to the raw spec. Supports iterative resolution of dependencies - across elements and class inheritance levels. The elements of the spec - may refer to each other, and although loops should be very rare, having - to maintain correct ordering of instantiation is painful, so the resolve() - method should be used to perform parts of init which require access to - other parts of the spec. - - Attributes: - yaml raw spec as loaded from the spec file - family back reference to the full family - - name name of the entity as listed in the spec (optional) - ident_name name which can be safely used as identifier in code (optional) - """ - def __init__(self, family, yaml): - self.yaml = yaml - self.family = family - - if 'name' in self.yaml: - self.name = self.yaml['name'] - self.ident_name = self.name.replace('-', '_') - - self._super_resolved = False - family.add_unresolved(self) - - def __getitem__(self, key): - return self.yaml[key] - - def __contains__(self, key): - return key in self.yaml - - def get(self, key, default=None): - return self.yaml.get(key, default) - - def resolve_up(self, up): - if not self._super_resolved: - up.resolve() - self._super_resolved = True - - def resolve(self): - pass - - -class SpecEnumEntry(SpecElement): - """ Entry within an enum declared in the Netlink spec. - - Attributes: - doc documentation string - enum_set back reference to the enum - value numerical value of this enum (use accessors in most situations!) - - Methods: - raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags - user_value user value, same as raw value for enums, for flags it's the mask - """ - def __init__(self, enum_set, yaml, prev, value_start): - if isinstance(yaml, str): - yaml = {'name': yaml} - super().__init__(enum_set.family, yaml) - - self.doc = yaml.get('doc', '') - self.enum_set = enum_set - - if 'value' in yaml: - self.value = yaml['value'] - elif prev: - self.value = prev.value + 1 - else: - self.value = value_start - - def has_doc(self): - return bool(self.doc) - - def raw_value(self): - return self.value - - def user_value(self, as_flags=None): - if self.enum_set['type'] == 'flags' or as_flags: - return 1 << self.value - else: - return self.value - - -class SpecEnumSet(SpecElement): - """ Enum type - - Represents an enumeration (list of numerical constants) - as declared in the "definitions" section of the spec. - - Attributes: - type enum or flags - entries entries by name - entries_by_val entries by value - Methods: - get_mask for flags compute the mask of all defined values - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.type = yaml['type'] - - prev_entry = None - value_start = self.yaml.get('value-start', 0) - self.entries = dict() - self.entries_by_val = dict() - for entry in self.yaml['entries']: - e = self.new_entry(entry, prev_entry, value_start) - self.entries[e.name] = e - self.entries_by_val[e.raw_value()] = e - prev_entry = e - - def new_entry(self, entry, prev_entry, value_start): - return SpecEnumEntry(self, entry, prev_entry, value_start) - - def has_doc(self): - if 'doc' in self.yaml: - return True - return self.has_entry_doc() - - def has_entry_doc(self): - for entry in self.entries.values(): - if entry.has_doc(): - return True - return False - - def get_mask(self, as_flags=None): - mask = 0 - for e in self.entries.values(): - mask += e.user_value(as_flags) - return mask - - -class SpecAttr(SpecElement): - """ Single Netlink attribute type - - Represents a single attribute type within an attr space. - - Attributes: - type string, attribute type - value numerical ID when serialized - attr_set Attribute Set containing this attr - is_multi bool, attr may repeat multiple times - struct_name string, name of struct definition - sub_type string, name of sub type - len integer, optional byte length of binary types - display_hint string, hint to help choose format specifier - when displaying the value - sub_message string, name of sub message type - selector string, name of attribute used to select - sub-message type - - is_auto_scalar bool, attr is a variable-size scalar - """ - def __init__(self, family, attr_set, yaml, value): - super().__init__(family, yaml) - - self.type = yaml['type'] - self.value = value - self.attr_set = attr_set - self.is_multi = yaml.get('multi-attr', False) - self.struct_name = yaml.get('struct') - self.sub_type = yaml.get('sub-type') - self.byte_order = yaml.get('byte-order') - self.len = yaml.get('len') - self.display_hint = yaml.get('display-hint') - self.sub_message = yaml.get('sub-message') - self.selector = yaml.get('selector') - - self.is_auto_scalar = self.type == "sint" or self.type == "uint" - - -class SpecAttrSet(SpecElement): - """ Netlink Attribute Set class. - - Represents a ID space of attributes within Netlink. - - Note that unlike other elements, which expose contents of the raw spec - via the dictionary interface Attribute Set exposes attributes by name. - - Attributes: - attrs ordered dict of all attributes (indexed by name) - attrs_by_val ordered dict of all attributes (indexed by value) - subset_of parent set if this is a subset, otherwise None - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.subset_of = self.yaml.get('subset-of', None) - - self.attrs = collections.OrderedDict() - self.attrs_by_val = collections.OrderedDict() - - if self.subset_of is None: - val = 1 - for elem in self.yaml['attributes']: - if 'value' in elem: - val = elem['value'] - - attr = self.new_attr(elem, val) - self.attrs[attr.name] = attr - self.attrs_by_val[attr.value] = attr - val += 1 - else: - real_set = family.attr_sets[self.subset_of] - for elem in self.yaml['attributes']: - attr = real_set[elem['name']] - self.attrs[attr.name] = attr - self.attrs_by_val[attr.value] = attr - - def new_attr(self, elem, value): - return SpecAttr(self.family, self, elem, value) - - def __getitem__(self, key): - return self.attrs[key] - - def __contains__(self, key): - return key in self.attrs - - def __iter__(self): - yield from self.attrs - - def items(self): - return self.attrs.items() - - -class SpecStructMember(SpecElement): - """Struct member attribute - - Represents a single struct member attribute. - - Attributes: - type string, type of the member attribute - byte_order string or None for native byte order - enum string, name of the enum definition - len integer, optional byte length of binary types - display_hint string, hint to help choose format specifier - when displaying the value - struct string, name of nested struct type - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - self.type = yaml['type'] - self.byte_order = yaml.get('byte-order') - self.enum = yaml.get('enum') - self.len = yaml.get('len') - self.display_hint = yaml.get('display-hint') - self.struct = yaml.get('struct') - - -class SpecStruct(SpecElement): - """Netlink struct type - - Represents a C struct definition. - - Attributes: - members ordered list of struct members - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.members = [] - for member in yaml.get('members', []): - self.members.append(self.new_member(family, member)) - - def new_member(self, family, elem): - return SpecStructMember(family, elem) - - def __iter__(self): - yield from self.members - - def items(self): - return self.members.items() - - -class SpecSubMessage(SpecElement): - """ Netlink sub-message definition - - Represents a set of sub-message formats for polymorphic nlattrs - that contain type-specific sub messages. - - Attributes: - name string, name of sub-message definition - formats dict of sub-message formats indexed by match value - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.formats = collections.OrderedDict() - for elem in self.yaml['formats']: - format = self.new_format(family, elem) - self.formats[format.value] = format - - def new_format(self, family, format): - return SpecSubMessageFormat(family, format) - - -class SpecSubMessageFormat(SpecElement): - """ Netlink sub-message format definition - - Represents a single format for a sub-message. - - Attributes: - value attribute value to match against type selector - fixed_header string, name of fixed header, or None - attr_set string, name of attribute set, or None - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.value = yaml.get('value') - self.fixed_header = yaml.get('fixed-header') - self.attr_set = yaml.get('attribute-set') - - -class SpecOperation(SpecElement): - """Netlink Operation - - Information about a single Netlink operation. - - Attributes: - value numerical ID when serialized, None if req/rsp values differ - - req_value numerical ID when serialized, user -> kernel - rsp_value numerical ID when serialized, user <- kernel - modes supported operation modes (do, dump, event etc.) - is_call bool, whether the operation is a call - is_async bool, whether the operation is a notification - is_resv bool, whether the operation does not exist (it's just a reserved ID) - attr_set attribute set name - fixed_header string, optional name of fixed header struct - - yaml raw spec as loaded from the spec file - """ - def __init__(self, family, yaml, req_value, rsp_value): - super().__init__(family, yaml) - - self.value = req_value if req_value == rsp_value else None - self.req_value = req_value - self.rsp_value = rsp_value - - self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'} - self.is_call = 'do' in yaml or 'dump' in yaml - self.is_async = 'notify' in yaml or 'event' in yaml - self.is_resv = not self.is_async and not self.is_call - self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) - - # Added by resolve: - self.attr_set = None - delattr(self, "attr_set") - - def resolve(self): - self.resolve_up(super()) - - if 'attribute-set' in self.yaml: - attr_set_name = self.yaml['attribute-set'] - elif 'notify' in self.yaml: - msg = self.family.msgs[self.yaml['notify']] - attr_set_name = msg['attribute-set'] - elif self.is_resv: - attr_set_name = '' - else: - raise Exception(f"Can't resolve attribute set for op '{self.name}'") - if attr_set_name: - self.attr_set = self.family.attr_sets[attr_set_name] - - -class SpecMcastGroup(SpecElement): - """Netlink Multicast Group - - Information about a multicast group. - - Value is only used for classic netlink families that use the - netlink-raw schema. Genetlink families use dynamic ID allocation - where the ids of multicast groups get resolved at runtime. Value - will be None for genetlink families. - - Attributes: - name name of the mulitcast group - value integer id of this multicast group for netlink-raw or None - yaml raw spec as loaded from the spec file - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - self.value = self.yaml.get('value') - - -class SpecFamily(SpecElement): - """ Netlink Family Spec class. - - Netlink family information loaded from a spec (e.g. in YAML). - Takes care of unfolding implicit information which can be skipped - in the spec itself for brevity. - - The class can be used like a dictionary to access the raw spec - elements but that's usually a bad idea. - - Attributes: - proto protocol type (e.g. genetlink) - msg_id_model enum-model for operations (unified, directional etc.) - license spec license (loaded from an SPDX tag on the spec) - - attr_sets dict of attribute sets - msgs dict of all messages (index by name) - sub_msgs dict of all sub messages (index by name) - ops dict of all valid requests / responses - ntfs dict of all async events - consts dict of all constants/enums - fixed_header string, optional name of family default fixed header struct - mcast_groups dict of all multicast groups (index by name) - kernel_family dict of kernel family attributes - """ - def __init__(self, spec_path, schema_path=None, exclude_ops=None): - with open(spec_path, "r") as stream: - prefix = '# SPDX-License-Identifier: ' - first = stream.readline().strip() - if not first.startswith(prefix): - raise Exception('SPDX license tag required in the spec') - self.license = first[len(prefix):] - - stream.seek(0) - spec = yaml.safe_load(stream) - - self._resolution_list = [] - - super().__init__(self, spec) - - self._exclude_ops = exclude_ops if exclude_ops else [] - - self.proto = self.yaml.get('protocol', 'genetlink') - self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - - if schema_path is None: - schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' - if schema_path: - global jsonschema - - with open(schema_path, "r") as stream: - schema = yaml.safe_load(stream) - - if jsonschema is None: - jsonschema = importlib.import_module("jsonschema") - - jsonschema.validate(self.yaml, schema) - - self.attr_sets = collections.OrderedDict() - self.sub_msgs = collections.OrderedDict() - self.msgs = collections.OrderedDict() - self.req_by_value = collections.OrderedDict() - self.rsp_by_value = collections.OrderedDict() - self.ops = collections.OrderedDict() - self.ntfs = collections.OrderedDict() - self.consts = collections.OrderedDict() - self.mcast_groups = collections.OrderedDict() - self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {})) - - last_exception = None - while len(self._resolution_list) > 0: - resolved = [] - unresolved = self._resolution_list - self._resolution_list = [] - - for elem in unresolved: - try: - elem.resolve() - except (KeyError, AttributeError) as e: - self._resolution_list.append(elem) - last_exception = e - continue - - resolved.append(elem) - - if len(resolved) == 0: - raise last_exception - - def new_enum(self, elem): - return SpecEnumSet(self, elem) - - def new_attr_set(self, elem): - return SpecAttrSet(self, elem) - - def new_struct(self, elem): - return SpecStruct(self, elem) - - def new_sub_message(self, elem): - return SpecSubMessage(self, elem); - - def new_operation(self, elem, req_val, rsp_val): - return SpecOperation(self, elem, req_val, rsp_val) - - def new_mcast_group(self, elem): - return SpecMcastGroup(self, elem) - - def add_unresolved(self, elem): - self._resolution_list.append(elem) - - def _dictify_ops_unified(self): - self.fixed_header = self.yaml['operations'].get('fixed-header') - val = 1 - for elem in self.yaml['operations']['list']: - if 'value' in elem: - val = elem['value'] - - op = self.new_operation(elem, val, val) - val += 1 - - self.msgs[op.name] = op - - def _dictify_ops_directional(self): - self.fixed_header = self.yaml['operations'].get('fixed-header') - req_val = rsp_val = 1 - for elem in self.yaml['operations']['list']: - if 'notify' in elem or 'event' in elem: - if 'value' in elem: - rsp_val = elem['value'] - req_val_next = req_val - rsp_val_next = rsp_val + 1 - req_val = None - elif 'do' in elem or 'dump' in elem: - mode = elem['do'] if 'do' in elem else elem['dump'] - - v = mode.get('request', {}).get('value', None) - if v: - req_val = v - v = mode.get('reply', {}).get('value', None) - if v: - rsp_val = v - - rsp_inc = 1 if 'reply' in mode else 0 - req_val_next = req_val + 1 - rsp_val_next = rsp_val + rsp_inc - else: - raise Exception("Can't parse directional ops") - - if req_val == req_val_next: - req_val = None - if rsp_val == rsp_val_next: - rsp_val = None - - skip = False - for exclude in self._exclude_ops: - skip |= bool(exclude.match(elem['name'])) - if not skip: - op = self.new_operation(elem, req_val, rsp_val) - - req_val = req_val_next - rsp_val = rsp_val_next - - self.msgs[op.name] = op - - def find_operation(self, name): - """ - For a given operation name, find and return operation spec. - """ - for op in self.yaml['operations']['list']: - if name == op['name']: - return op - return None - - def resolve(self): - self.resolve_up(super()) - - definitions = self.yaml.get('definitions', []) - for elem in definitions: - if elem['type'] == 'enum' or elem['type'] == 'flags': - self.consts[elem['name']] = self.new_enum(elem) - elif elem['type'] == 'struct': - self.consts[elem['name']] = self.new_struct(elem) - else: - self.consts[elem['name']] = elem - - for elem in self.yaml['attribute-sets']: - attr_set = self.new_attr_set(elem) - self.attr_sets[elem['name']] = attr_set - - for elem in self.yaml.get('sub-messages', []): - sub_message = self.new_sub_message(elem) - self.sub_msgs[sub_message.name] = sub_message - - if self.msg_id_model == 'unified': - self._dictify_ops_unified() - elif self.msg_id_model == 'directional': - self._dictify_ops_directional() - - for op in self.msgs.values(): - if op.req_value is not None: - self.req_by_value[op.req_value] = op - if op.rsp_value is not None: - self.rsp_by_value[op.rsp_value] = op - if not op.is_async and 'attribute-set' in op: - self.ops[op.name] = op - elif op.is_async: - self.ntfs[op.name] = op - - mcgs = self.yaml.get('mcast-groups') - if mcgs: - for elem in mcgs['list']: - mcg = self.new_mcast_group(elem) - self.mcast_groups[elem['name']] = mcg diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h index 3c09a7bbfba5..824777d7e05e 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 { @@ -42,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; @@ -94,12 +98,17 @@ struct ynl_ntf_base_type { unsigned char data[] __attribute__((aligned(8))); }; +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 * ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version); 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 */ @@ -204,11 +213,15 @@ static inline void *ynl_attr_data_end(const struct nlattr *attr) NLMSG_HDRLEN + fixed_hdr_sz); attr; \ (attr) = ynl_attr_next(ynl_nlmsg_end_addr(nlh), attr)) -#define ynl_attr_for_each_nested(attr, outer) \ +#define ynl_attr_for_each_nested_off(attr, outer, offset) \ for ((attr) = ynl_attr_first(outer, outer->nla_len, \ - sizeof(struct nlattr)); attr; \ + sizeof(struct nlattr) + offset); \ + attr; \ (attr) = ynl_attr_next(ynl_attr_data_end(outer), attr)) +#define ynl_attr_for_each_nested(attr, outer) \ + ynl_attr_for_each_nested_off(attr, outer, 0) + #define ynl_attr_for_each_payload(start, len, attr) \ for ((attr) = ynl_attr_first(start, len, 0); attr; \ (attr) = ynl_attr_next(start + len, attr)) diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c index e16cef160bc2..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; @@ -95,7 +153,11 @@ 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 = astart_off + ynl_attr_data_len(attr); + 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); } @@ -191,12 +257,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 +282,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, @@ -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; @@ -364,7 +430,7 @@ int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr) "Invalid attribute (binary %s)", policy->name); return -1; case YNL_PT_NUL_STR: - if ((!policy->len || len <= policy->len) && !data[len - 1]) + if (len && (!policy->len || len <= policy->len) && !data[len - 1]) break; yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, "Invalid attribute (string %s)", policy->name); @@ -384,6 +450,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) @@ -451,14 +526,14 @@ ynl_gemsg_start(struct ynl_sock *ys, __u32 id, __u16 flags, return nlh; } -void ynl_msg_start_req(struct ynl_sock *ys, __u32 id) +struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id, __u16 flags) { - ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK); + return ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | flags); } -void ynl_msg_start_dump(struct ynl_sock *ys, __u32 id) +struct nlmsghdr *ynl_msg_start_dump(struct ynl_sock *ys, __u32 id) { - ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); + return ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); } struct nlmsghdr * @@ -663,6 +738,7 @@ ynl_sock_create(const struct ynl_family *yf, struct ynl_error *yse) struct sockaddr_nl addr; struct ynl_sock *ys; socklen_t addrlen; + int sock_type; int one = 1; ys = malloc(sizeof(*ys) + 2 * YNL_SOCKET_BUFFER_SIZE); @@ -675,7 +751,9 @@ ynl_sock_create(const struct ynl_family *yf, struct ynl_error *yse) ys->rx_buf = &ys->raw_buf[YNL_SOCKET_BUFFER_SIZE]; ys->ntf_last_next = &ys->ntf_first; - ys->socket = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); + sock_type = yf->is_classic ? yf->classic_id : NETLINK_GENERIC; + + ys->socket = socket(AF_NETLINK, SOCK_RAW, sock_type); if (ys->socket < 0) { __perr(yse, "failed to create a netlink socket"); goto err_free_sock; @@ -708,8 +786,9 @@ ynl_sock_create(const struct ynl_family *yf, struct ynl_error *yse) ys->portid = addr.nl_pid; ys->seq = random(); - - if (ynl_sock_read_family(ys, yf->name)) { + if (yf->is_classic) { + ys->family_id = yf->classic_id; + } else if (ynl_sock_read_family(ys, yf->name)) { if (yse) memcpy(yse, &ys->err, sizeof(*yse)); goto err_close_sock; @@ -791,13 +870,21 @@ static int ynl_ntf_parse(struct ynl_sock *ys, const struct nlmsghdr *nlh) struct ynl_parse_arg yarg = { .ys = ys, }; const struct ynl_ntf_info *info; struct ynl_ntf_base_type *rsp; - struct genlmsghdr *gehdr; + __u32 cmd; int ret; - gehdr = ynl_nlmsg_data(nlh); - if (gehdr->cmd >= ys->family->ntf_info_size) + if (ys->family->is_classic) { + cmd = nlh->nlmsg_type; + } else { + struct genlmsghdr *gehdr; + + gehdr = ynl_nlmsg_data(nlh); + cmd = gehdr->cmd; + } + + if (cmd >= ys->family->ntf_info_size) return YNL_PARSE_CB_ERROR; - info = &ys->family->ntf_info[gehdr->cmd]; + info = &ys->family->ntf_info[cmd]; if (!info->cb) return YNL_PARSE_CB_ERROR; @@ -811,7 +898,7 @@ static int ynl_ntf_parse(struct ynl_sock *ys, const struct nlmsghdr *nlh) goto err_free; rsp->family = nlh->nlmsg_type; - rsp->cmd = gehdr->cmd; + rsp->cmd = cmd; *ys->ntf_last_next = rsp; ys->ntf_last_next = &rsp->next; @@ -863,18 +950,23 @@ int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg) static int ynl_check_alien(struct ynl_sock *ys, const struct nlmsghdr *nlh, __u32 rsp_cmd) { - struct genlmsghdr *gehdr; + if (ys->family->is_classic) { + if (nlh->nlmsg_type != rsp_cmd) + return ynl_ntf_parse(ys, nlh); + } else { + struct genlmsghdr *gehdr; + + if (ynl_nlmsg_data_len(nlh) < sizeof(*gehdr)) { + yerr(ys, YNL_ERROR_INV_RESP, + "Kernel responded with truncated message"); + return -1; + } - if (ynl_nlmsg_data_len(nlh) < sizeof(*gehdr)) { - yerr(ys, YNL_ERROR_INV_RESP, - "Kernel responded with truncated message"); - return -1; + gehdr = ynl_nlmsg_data(nlh); + if (gehdr->cmd != rsp_cmd) + return ynl_ntf_parse(ys, nlh); } - gehdr = ynl_nlmsg_data(nlh); - if (gehdr->cmd != rsp_cmd) - return ynl_ntf_parse(ys, nlh); - return 0; } diff --git a/tools/net/ynl/lib/ynl.h b/tools/net/ynl/lib/ynl.h index 6cd570b283ea..db7c0591a63f 100644 --- a/tools/net/ynl/lib/ynl.h +++ b/tools/net/ynl/lib/ynl.h @@ -2,6 +2,7 @@ #ifndef __YNL_C_H #define __YNL_C_H 1 +#include <stdbool.h> #include <stddef.h> #include <linux/genetlink.h> #include <linux/types.h> @@ -22,6 +23,7 @@ enum ynl_error_code { YNL_ERROR_INV_RESP, YNL_ERROR_INPUT_INVALID, YNL_ERROR_INPUT_TOO_BIG, + YNL_ERROR_SUBMSG_KEY, }; /** @@ -48,6 +50,8 @@ struct ynl_family { /* private: */ const char *name; size_t hdr_len; + bool is_classic; + __u16 classic_id; const struct ynl_ntf_info *ntf_info; unsigned int ntf_info_size; }; @@ -77,11 +81,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); diff --git a/tools/net/ynl/lib/ynl.py b/tools/net/ynl/lib/ynl.py deleted file mode 100644 index eea29359a899..000000000000 --- a/tools/net/ynl/lib/ynl.py +++ /dev/null @@ -1,1063 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -from collections import namedtuple -from enum import Enum -import functools -import os -import random -import socket -import struct -from struct import Struct -import sys -import yaml -import ipaddress -import uuid -import queue -import selectors -import time - -from .nlspec import SpecFamily - -# -# Generic Netlink code which should really be in some library, but I can't quickly find one. -# - - -class Netlink: - # Netlink socket - SOL_NETLINK = 270 - - NETLINK_ADD_MEMBERSHIP = 1 - NETLINK_CAP_ACK = 10 - NETLINK_EXT_ACK = 11 - NETLINK_GET_STRICT_CHK = 12 - - # Netlink message - NLMSG_ERROR = 2 - NLMSG_DONE = 3 - - NLM_F_REQUEST = 1 - NLM_F_ACK = 4 - NLM_F_ROOT = 0x100 - NLM_F_MATCH = 0x200 - - NLM_F_REPLACE = 0x100 - NLM_F_EXCL = 0x200 - NLM_F_CREATE = 0x400 - NLM_F_APPEND = 0x800 - - NLM_F_CAPPED = 0x100 - NLM_F_ACK_TLVS = 0x200 - - NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH - - NLA_F_NESTED = 0x8000 - NLA_F_NET_BYTEORDER = 0x4000 - - NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER - - # Genetlink defines - NETLINK_GENERIC = 16 - - GENL_ID_CTRL = 0x10 - - # nlctrl - CTRL_CMD_GETFAMILY = 3 - - CTRL_ATTR_FAMILY_ID = 1 - CTRL_ATTR_FAMILY_NAME = 2 - CTRL_ATTR_MAXATTR = 5 - CTRL_ATTR_MCAST_GROUPS = 7 - - CTRL_ATTR_MCAST_GRP_NAME = 1 - CTRL_ATTR_MCAST_GRP_ID = 2 - - # Extack types - NLMSGERR_ATTR_MSG = 1 - NLMSGERR_ATTR_OFFS = 2 - NLMSGERR_ATTR_COOKIE = 3 - NLMSGERR_ATTR_POLICY = 4 - NLMSGERR_ATTR_MISS_TYPE = 5 - NLMSGERR_ATTR_MISS_NEST = 6 - - # Policy types - NL_POLICY_TYPE_ATTR_TYPE = 1 - NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 2 - NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 3 - NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 4 - NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 5 - NL_POLICY_TYPE_ATTR_MIN_LENGTH = 6 - NL_POLICY_TYPE_ATTR_MAX_LENGTH = 7 - NL_POLICY_TYPE_ATTR_POLICY_IDX = 8 - NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 9 - NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 10 - NL_POLICY_TYPE_ATTR_PAD = 11 - NL_POLICY_TYPE_ATTR_MASK = 12 - - AttrType = Enum('AttrType', ['flag', 'u8', 'u16', 'u32', 'u64', - 's8', 's16', 's32', 's64', - 'binary', 'string', 'nul-string', - 'nested', 'nested-array', - 'bitfield32', 'sint', 'uint']) - -class NlError(Exception): - def __init__(self, nl_msg): - self.nl_msg = nl_msg - self.error = -nl_msg.error - - def __str__(self): - return f"Netlink error: {os.strerror(self.error)}\n{self.nl_msg}" - - -class ConfigError(Exception): - pass - - -class NlAttr: - ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) - type_formats = { - 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), - 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), - 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")), - 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")), - 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")), - 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")), - 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")), - 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q")) - } - - def __init__(self, raw, offset): - self._len, self._type = struct.unpack("HH", raw[offset : offset + 4]) - self.type = self._type & ~Netlink.NLA_TYPE_MASK - self.is_nest = self._type & Netlink.NLA_F_NESTED - self.payload_len = self._len - self.full_len = (self.payload_len + 3) & ~3 - self.raw = raw[offset + 4 : offset + self.payload_len] - - @classmethod - def get_format(cls, attr_type, byte_order=None): - format = cls.type_formats[attr_type] - if byte_order: - return format.big if byte_order == "big-endian" \ - else format.little - return format.native - - def as_scalar(self, attr_type, byte_order=None): - format = self.get_format(attr_type, byte_order) - return format.unpack(self.raw)[0] - - def as_auto_scalar(self, attr_type, byte_order=None): - if len(self.raw) != 4 and len(self.raw) != 8: - raise Exception(f"Auto-scalar len payload be 4 or 8 bytes, got {len(self.raw)}") - real_type = attr_type[0] + str(len(self.raw) * 8) - format = self.get_format(real_type, byte_order) - return format.unpack(self.raw)[0] - - def as_strz(self): - return self.raw.decode('ascii')[:-1] - - def as_bin(self): - return self.raw - - def as_c_array(self, type): - format = self.get_format(type) - return [ x[0] for x in format.iter_unpack(self.raw) ] - - def __repr__(self): - return f"[type:{self.type} len:{self._len}] {self.raw}" - - -class NlAttrs: - def __init__(self, msg, offset=0): - self.attrs = [] - - while offset < len(msg): - attr = NlAttr(msg, offset) - offset += attr.full_len - self.attrs.append(attr) - - def __iter__(self): - yield from self.attrs - - def __repr__(self): - msg = '' - for a in self.attrs: - if msg: - msg += '\n' - msg += repr(a) - return msg - - -class NlMsg: - def __init__(self, msg, offset, attr_space=None): - self.hdr = msg[offset : offset + 16] - - self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ - struct.unpack("IHHII", self.hdr) - - self.raw = msg[offset + 16 : offset + self.nl_len] - - self.error = 0 - self.done = 0 - - extack_off = None - if self.nl_type == Netlink.NLMSG_ERROR: - self.error = struct.unpack("i", self.raw[0:4])[0] - self.done = 1 - extack_off = 20 - elif self.nl_type == Netlink.NLMSG_DONE: - self.error = struct.unpack("i", self.raw[0:4])[0] - self.done = 1 - extack_off = 4 - - self.extack = None - if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: - self.extack = dict() - extack_attrs = NlAttrs(self.raw[extack_off:]) - for extack in extack_attrs: - if extack.type == Netlink.NLMSGERR_ATTR_MSG: - self.extack['msg'] = extack.as_strz() - elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: - self.extack['miss-type'] = extack.as_scalar('u32') - elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: - self.extack['miss-nest'] = extack.as_scalar('u32') - elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: - self.extack['bad-attr-offs'] = extack.as_scalar('u32') - elif extack.type == Netlink.NLMSGERR_ATTR_POLICY: - self.extack['policy'] = self._decode_policy(extack.raw) - else: - if 'unknown' not in self.extack: - self.extack['unknown'] = [] - self.extack['unknown'].append(extack) - - if attr_space: - # We don't have the ability to parse nests yet, so only do global - if 'miss-type' in self.extack and 'miss-nest' not in self.extack: - miss_type = self.extack['miss-type'] - if miss_type in attr_space.attrs_by_val: - spec = attr_space.attrs_by_val[miss_type] - self.extack['miss-type'] = spec['name'] - if 'doc' in spec: - self.extack['miss-type-doc'] = spec['doc'] - - def _decode_policy(self, raw): - policy = {} - for attr in NlAttrs(raw): - if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE: - type = attr.as_scalar('u32') - policy['type'] = Netlink.AttrType(type).name - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S: - policy['min-value'] = attr.as_scalar('s64') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S: - policy['max-value'] = attr.as_scalar('s64') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U: - policy['min-value'] = attr.as_scalar('u64') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U: - policy['max-value'] = attr.as_scalar('u64') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH: - policy['min-length'] = attr.as_scalar('u32') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH: - policy['max-length'] = attr.as_scalar('u32') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: - policy['bitfield32-mask'] = attr.as_scalar('u32') - elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK: - policy['mask'] = attr.as_scalar('u64') - return policy - - def cmd(self): - return self.nl_type - - def __repr__(self): - msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}" - if self.error: - msg += '\n\terror: ' + str(self.error) - if self.extack: - msg += '\n\textack: ' + repr(self.extack) - return msg - - -class NlMsgs: - def __init__(self, data, attr_space=None): - self.msgs = [] - - offset = 0 - while offset < len(data): - msg = NlMsg(data, offset, attr_space=attr_space) - offset += msg.nl_len - self.msgs.append(msg) - - def __iter__(self): - yield from self.msgs - - -genl_family_name_to_id = None - - -def _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): - # we prepend length in _genl_msg_finalize() - if seq is None: - seq = random.randint(1, 1024) - nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) - genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0) - return nlmsg + genlmsg - - -def _genl_msg_finalize(msg): - return struct.pack("I", len(msg) + 4) + msg - - -def _genl_load_families(): - with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: - sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) - - msg = _genl_msg(Netlink.GENL_ID_CTRL, - Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, - Netlink.CTRL_CMD_GETFAMILY, 1) - msg = _genl_msg_finalize(msg) - - sock.send(msg, 0) - - global genl_family_name_to_id - genl_family_name_to_id = dict() - - while True: - reply = sock.recv(128 * 1024) - nms = NlMsgs(reply) - for nl_msg in nms: - if nl_msg.error: - print("Netlink error:", nl_msg.error) - return - if nl_msg.done: - return - - gm = GenlMsg(nl_msg) - fam = dict() - for attr in NlAttrs(gm.raw): - if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: - fam['id'] = attr.as_scalar('u16') - elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: - fam['name'] = attr.as_strz() - elif attr.type == Netlink.CTRL_ATTR_MAXATTR: - fam['maxattr'] = attr.as_scalar('u32') - elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: - fam['mcast'] = dict() - for entry in NlAttrs(attr.raw): - mcast_name = None - mcast_id = None - for entry_attr in NlAttrs(entry.raw): - if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: - mcast_name = entry_attr.as_strz() - elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: - mcast_id = entry_attr.as_scalar('u32') - if mcast_name and mcast_id is not None: - fam['mcast'][mcast_name] = mcast_id - if 'name' in fam and 'id' in fam: - genl_family_name_to_id[fam['name']] = fam - - -class GenlMsg: - def __init__(self, nl_msg): - self.nl = nl_msg - self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0) - self.raw = nl_msg.raw[4:] - - def cmd(self): - return self.genl_cmd - - def __repr__(self): - msg = repr(self.nl) - msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" - for a in self.raw_attrs: - msg += '\t\t' + repr(a) + '\n' - return msg - - -class NetlinkProtocol: - def __init__(self, family_name, proto_num): - self.family_name = family_name - self.proto_num = proto_num - - def _message(self, nl_type, nl_flags, seq=None): - if seq is None: - seq = random.randint(1, 1024) - nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) - return nlmsg - - def message(self, flags, command, version, seq=None): - return self._message(command, flags, seq) - - def _decode(self, nl_msg): - return nl_msg - - def decode(self, ynl, nl_msg, op): - msg = self._decode(nl_msg) - if op is None: - op = ynl.rsp_by_value[msg.cmd()] - fixed_header_size = ynl._struct_size(op.fixed_header) - msg.raw_attrs = NlAttrs(msg.raw, fixed_header_size) - return msg - - def get_mcast_id(self, mcast_name, mcast_groups): - if mcast_name not in mcast_groups: - raise Exception(f'Multicast group "{mcast_name}" not present in the spec') - return mcast_groups[mcast_name].value - - def msghdr_size(self): - return 16 - - -class GenlProtocol(NetlinkProtocol): - def __init__(self, family_name): - super().__init__(family_name, Netlink.NETLINK_GENERIC) - - global genl_family_name_to_id - if genl_family_name_to_id is None: - _genl_load_families() - - self.genl_family = genl_family_name_to_id[family_name] - self.family_id = genl_family_name_to_id[family_name]['id'] - - def message(self, flags, command, version, seq=None): - nlmsg = self._message(self.family_id, flags, seq) - genlmsg = struct.pack("BBH", command, version, 0) - return nlmsg + genlmsg - - def _decode(self, nl_msg): - return GenlMsg(nl_msg) - - def get_mcast_id(self, mcast_name, mcast_groups): - if mcast_name not in self.genl_family['mcast']: - raise Exception(f'Multicast group "{mcast_name}" not present in the family') - return self.genl_family['mcast'][mcast_name] - - def msghdr_size(self): - return super().msghdr_size() + 4 - - -class SpaceAttrs: - SpecValuesPair = namedtuple('SpecValuesPair', ['spec', 'values']) - - def __init__(self, attr_space, attrs, outer = None): - outer_scopes = outer.scopes if outer else [] - inner_scope = self.SpecValuesPair(attr_space, attrs) - self.scopes = [inner_scope] + outer_scopes - - def lookup(self, name): - for scope in self.scopes: - if name in scope.spec: - if name in scope.values: - return scope.values[name] - spec_name = scope.spec.yaml['name'] - raise Exception( - f"No value for '{name}' in attribute space '{spec_name}'") - raise Exception(f"Attribute '{name}' not defined in any attribute-set") - - -# -# YNL implementation details. -# - - -class YnlFamily(SpecFamily): - def __init__(self, def_path, schema=None, process_unknown=False, - recv_size=0): - super().__init__(def_path, schema) - - self.include_raw = False - self.process_unknown = process_unknown - - try: - if self.proto == "netlink-raw": - self.nlproto = NetlinkProtocol(self.yaml['name'], - self.yaml['protonum']) - else: - self.nlproto = GenlProtocol(self.yaml['name']) - except KeyError: - raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel") - - self._recv_dbg = False - # Note that netlink will use conservative (min) message size for - # the first dump recv() on the socket, our setting will only matter - # from the second recv() on. - self._recv_size = recv_size if recv_size else 131072 - # Netlink will always allocate at least PAGE_SIZE - sizeof(skb_shinfo) - # for a message, so smaller receive sizes will lead to truncation. - # Note that the min size for other families may be larger than 4k! - if self._recv_size < 4000: - raise ConfigError() - - self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num) - self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) - self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) - self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1) - - self.async_msg_ids = set() - self.async_msg_queue = queue.Queue() - - for msg in self.msgs.values(): - if msg.is_async: - self.async_msg_ids.add(msg.rsp_value) - - for op_name, op in self.ops.items(): - bound_f = functools.partial(self._op, op_name) - setattr(self, op.ident_name, bound_f) - - - def ntf_subscribe(self, mcast_name): - mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups) - self.sock.bind((0, 0)) - self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, - mcast_id) - - def set_recv_dbg(self, enabled): - self._recv_dbg = enabled - - def _recv_dbg_print(self, reply, nl_msgs): - if not self._recv_dbg: - return - print("Recv: read", len(reply), "bytes,", - len(nl_msgs.msgs), "messages", file=sys.stderr) - for nl_msg in nl_msgs: - print(" ", nl_msg, file=sys.stderr) - - def _encode_enum(self, attr_spec, value): - enum = self.consts[attr_spec['enum']] - if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): - scalar = 0 - if isinstance(value, str): - value = [value] - for single_value in value: - scalar += enum.entries[single_value].user_value(as_flags = True) - return scalar - else: - return enum.entries[value].user_value() - - def _get_scalar(self, attr_spec, value): - try: - return int(value) - except (ValueError, TypeError) as e: - if 'enum' not in attr_spec: - raise e - return self._encode_enum(attr_spec, value) - - def _add_attr(self, space, name, value, search_attrs): - try: - attr = self.attr_sets[space][name] - except KeyError: - raise Exception(f"Space '{space}' has no attribute '{name}'") - nl_type = attr.value - - if attr.is_multi and isinstance(value, list): - attr_payload = b'' - for subvalue in value: - attr_payload += self._add_attr(space, name, subvalue, search_attrs) - return attr_payload - - 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) - elif attr["type"] == 'flag': - if not value: - # If value is absent or false then skip attribute creation. - return b'' - attr_payload = b'' - elif attr["type"] == 'string': - attr_payload = str(value).encode('ascii') + b'\x00' - elif attr["type"] == 'binary': - if isinstance(value, bytes): - attr_payload = value - elif isinstance(value, str): - attr_payload = bytes.fromhex(value) - elif isinstance(value, dict) and attr.struct_name: - attr_payload = self._encode_struct(attr.struct_name, value) - else: - raise Exception(f'Unknown type for binary attribute, value: {value}') - elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar: - scalar = self._get_scalar(attr, value) - if attr.is_auto_scalar: - attr_type = attr["type"][0] + ('32' if scalar.bit_length() <= 32 else '64') - else: - attr_type = attr["type"] - format = NlAttr.get_format(attr_type, attr.byte_order) - attr_payload = format.pack(scalar) - elif attr['type'] in "bitfield32": - scalar_value = self._get_scalar(attr, value["value"]) - scalar_selector = self._get_scalar(attr, value["selector"]) - attr_payload = struct.pack("II", scalar_value, scalar_selector) - elif attr['type'] == 'sub-message': - msg_format = self._resolve_selector(attr, search_attrs) - attr_payload = b'' - if msg_format.fixed_header: - attr_payload += self._encode_struct(msg_format.fixed_header, value) - if msg_format.attr_set: - if msg_format.attr_set in self.attr_sets: - nl_type |= Netlink.NLA_F_NESTED - sub_attrs = SpaceAttrs(msg_format.attr_set, value, search_attrs) - for subname, subvalue in value.items(): - attr_payload += self._add_attr(msg_format.attr_set, - subname, subvalue, sub_attrs) - else: - raise Exception(f"Unknown attribute-set '{msg_format.attr_set}'") - else: - raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') - - pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) - return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad - - def _decode_enum(self, raw, attr_spec): - enum = self.consts[attr_spec['enum']] - if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): - i = 0 - value = set() - while raw: - if raw & 1: - value.add(enum.entries_by_val[i].name) - raw >>= 1 - i += 1 - else: - value = enum.entries_by_val[raw].name - return value - - def _decode_binary(self, attr, attr_spec): - if attr_spec.struct_name: - decoded = self._decode_struct(attr.raw, attr_spec.struct_name) - elif attr_spec.sub_type: - decoded = attr.as_c_array(attr_spec.sub_type) - else: - decoded = attr.as_bin() - if attr_spec.display_hint: - decoded = self._formatted_string(decoded, attr_spec.display_hint) - return decoded - - def _decode_array_attr(self, attr, attr_spec): - decoded = [] - offset = 0 - while offset < len(attr.raw): - item = NlAttr(attr.raw, offset) - offset += item.full_len - - if attr_spec["sub-type"] == 'nest': - subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes']) - decoded.append({ item.type: subattrs }) - elif attr_spec["sub-type"] == 'binary': - subattrs = item.as_bin() - if attr_spec.display_hint: - subattrs = self._formatted_string(subattrs, attr_spec.display_hint) - decoded.append(subattrs) - elif attr_spec["sub-type"] in NlAttr.type_formats: - subattrs = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order) - if attr_spec.display_hint: - subattrs = self._formatted_string(subattrs, attr_spec.display_hint) - decoded.append(subattrs) - else: - raise Exception(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}') - return decoded - - def _decode_nest_type_value(self, attr, attr_spec): - decoded = {} - value = attr - for name in attr_spec['type-value']: - value = NlAttr(value.raw, 0) - decoded[name] = value.type - subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes']) - decoded.update(subattrs) - return decoded - - def _decode_unknown(self, attr): - if attr.is_nest: - return self._decode(NlAttrs(attr.raw), None) - else: - return attr.as_bin() - - def _rsp_add(self, rsp, name, is_multi, decoded): - if is_multi == None: - if name in rsp and type(rsp[name]) is not list: - rsp[name] = [rsp[name]] - is_multi = True - else: - is_multi = False - - if not is_multi: - rsp[name] = decoded - elif name in rsp: - rsp[name].append(decoded) - else: - rsp[name] = [decoded] - - def _resolve_selector(self, attr_spec, search_attrs): - sub_msg = attr_spec.sub_message - if sub_msg not in self.sub_msgs: - raise Exception(f"No sub-message spec named {sub_msg} for {attr_spec.name}") - sub_msg_spec = self.sub_msgs[sub_msg] - - selector = attr_spec.selector - value = search_attrs.lookup(selector) - if value not in sub_msg_spec.formats: - raise Exception(f"No message format for '{value}' in sub-message spec '{sub_msg}'") - - spec = sub_msg_spec.formats[value] - return spec - - def _decode_sub_msg(self, attr, attr_spec, search_attrs): - msg_format = self._resolve_selector(attr_spec, search_attrs) - decoded = {} - offset = 0 - if msg_format.fixed_header: - decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header)); - offset = self._struct_size(msg_format.fixed_header) - if msg_format.attr_set: - if msg_format.attr_set in self.attr_sets: - subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set) - decoded.update(subdict) - else: - raise Exception(f"Unknown attribute-set '{attr_space}' when decoding '{attr_spec.name}'") - return decoded - - def _decode(self, attrs, space, outer_attrs = None): - rsp = dict() - if space: - attr_space = self.attr_sets[space] - search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs) - - for attr in attrs: - try: - attr_spec = attr_space.attrs_by_val[attr.type] - except (KeyError, UnboundLocalError): - if not self.process_unknown: - raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'") - attr_name = f"UnknownAttr({attr.type})" - self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr)) - continue - - if attr_spec["type"] == 'nest': - subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'], search_attrs) - decoded = subdict - elif attr_spec["type"] == 'string': - decoded = attr.as_strz() - elif attr_spec["type"] == 'binary': - decoded = self._decode_binary(attr, attr_spec) - elif attr_spec["type"] == 'flag': - decoded = True - elif attr_spec.is_auto_scalar: - decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order) - elif attr_spec["type"] in NlAttr.type_formats: - decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) - if 'enum' in attr_spec: - decoded = self._decode_enum(decoded, attr_spec) - elif attr_spec.display_hint: - decoded = self._formatted_string(decoded, attr_spec.display_hint) - elif attr_spec["type"] == 'indexed-array': - decoded = self._decode_array_attr(attr, attr_spec) - elif attr_spec["type"] == 'bitfield32': - value, selector = struct.unpack("II", attr.raw) - if 'enum' in attr_spec: - value = self._decode_enum(value, attr_spec) - selector = self._decode_enum(selector, attr_spec) - decoded = {"value": value, "selector": selector} - elif attr_spec["type"] == 'sub-message': - decoded = self._decode_sub_msg(attr, attr_spec, search_attrs) - elif attr_spec["type"] == 'nest-type-value': - decoded = self._decode_nest_type_value(attr, attr_spec) - else: - if not self.process_unknown: - raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') - decoded = self._decode_unknown(attr) - - self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded) - - return rsp - - def _decode_extack_path(self, attrs, attr_set, offset, target): - for attr in attrs: - try: - attr_spec = attr_set.attrs_by_val[attr.type] - except KeyError: - raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'") - if offset > target: - break - if offset == target: - return '.' + attr_spec.name - - if offset + attr.full_len <= target: - offset += attr.full_len - continue - if attr_spec['type'] != 'nest': - raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") - offset += 4 - subpath = self._decode_extack_path(NlAttrs(attr.raw), - self.attr_sets[attr_spec['nested-attributes']], - offset, target) - if subpath is None: - return None - return '.' + attr_spec.name + subpath - - return None - - def _decode_extack(self, request, op, extack): - if 'bad-attr-offs' not in extack: - return - - msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op) - offset = self.nlproto.msghdr_size() + self._struct_size(op.fixed_header) - path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, - extack['bad-attr-offs']) - if path: - del extack['bad-attr-offs'] - extack['bad-attr'] = path - - def _struct_size(self, name): - if name: - members = self.consts[name].members - size = 0 - for m in members: - if m.type in ['pad', 'binary']: - if m.struct: - size += self._struct_size(m.struct) - else: - size += m.len - else: - format = NlAttr.get_format(m.type, m.byte_order) - size += format.size - return size - else: - return 0 - - def _decode_struct(self, data, name): - members = self.consts[name].members - attrs = dict() - offset = 0 - for m in members: - value = None - if m.type == 'pad': - offset += m.len - elif m.type == 'binary': - if m.struct: - len = self._struct_size(m.struct) - value = self._decode_struct(data[offset : offset + len], - m.struct) - offset += len - else: - value = data[offset : offset + m.len] - offset += m.len - else: - format = NlAttr.get_format(m.type, m.byte_order) - [ value ] = format.unpack_from(data, offset) - offset += format.size - if value is not None: - if m.enum: - value = self._decode_enum(value, m) - elif m.display_hint: - value = self._formatted_string(value, m.display_hint) - attrs[m.name] = value - return attrs - - def _encode_struct(self, name, vals): - members = self.consts[name].members - attr_payload = b'' - for m in members: - value = vals.pop(m.name) if m.name in vals else None - if m.type == 'pad': - attr_payload += bytearray(m.len) - elif m.type == 'binary': - if m.struct: - if value is None: - value = dict() - attr_payload += self._encode_struct(m.struct, value) - else: - if value is None: - attr_payload += bytearray(m.len) - else: - attr_payload += bytes.fromhex(value) - else: - if value is None: - value = 0 - format = NlAttr.get_format(m.type, m.byte_order) - attr_payload += format.pack(value) - return attr_payload - - def _formatted_string(self, raw, display_hint): - if display_hint == 'mac': - formatted = ':'.join('%02x' % b for b in raw) - elif display_hint == 'hex': - if isinstance(raw, int): - formatted = hex(raw) - else: - formatted = bytes.hex(raw, ' ') - elif display_hint in [ 'ipv4', 'ipv6' ]: - formatted = format(ipaddress.ip_address(raw)) - elif display_hint == 'uuid': - formatted = str(uuid.UUID(bytes=raw)) - else: - formatted = raw - return formatted - - def handle_ntf(self, decoded): - msg = dict() - if self.include_raw: - msg['raw'] = decoded - op = self.rsp_by_value[decoded.cmd()] - attrs = self._decode(decoded.raw_attrs, op.attr_set.name) - if op.fixed_header: - attrs.update(self._decode_struct(decoded.raw, op.fixed_header)) - - msg['name'] = op['name'] - msg['msg'] = attrs - self.async_msg_queue.put(msg) - - def check_ntf(self): - while True: - try: - reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT) - except BlockingIOError: - return - - nms = NlMsgs(reply) - self._recv_dbg_print(reply, nms) - for nl_msg in nms: - if nl_msg.error: - print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) - print(nl_msg) - continue - if nl_msg.done: - print("Netlink done while checking for ntf!?") - continue - - decoded = self.nlproto.decode(self, nl_msg, None) - if decoded.cmd() not in self.async_msg_ids: - print("Unexpected msg id while checking for ntf", decoded) - continue - - self.handle_ntf(decoded) - - def poll_ntf(self, duration=None): - start_time = time.time() - selector = selectors.DefaultSelector() - selector.register(self.sock, selectors.EVENT_READ) - - while True: - try: - yield self.async_msg_queue.get_nowait() - except queue.Empty: - if duration is not None: - timeout = start_time + duration - time.time() - if timeout <= 0: - return - else: - timeout = None - events = selector.select(timeout) - if events: - self.check_ntf() - - def operation_do_attributes(self, name): - """ - For a given operation name, find and return a supported - set of attributes (as a dict). - """ - op = self.find_operation(name) - if not op: - return None - - return op['do']['request']['attributes'].copy() - - def _encode_message(self, op, vals, flags, req_seq): - nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK - for flag in flags or []: - nl_flags |= flag - - msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq) - if op.fixed_header: - msg += self._encode_struct(op.fixed_header, vals) - search_attrs = SpaceAttrs(op.attr_set, vals) - for name, value in vals.items(): - msg += self._add_attr(op.attr_set.name, name, value, search_attrs) - msg = _genl_msg_finalize(msg) - return msg - - def _ops(self, ops): - reqs_by_seq = {} - req_seq = random.randint(1024, 65535) - payload = b'' - for (method, vals, flags) in ops: - op = self.ops[method] - msg = self._encode_message(op, vals, flags, req_seq) - reqs_by_seq[req_seq] = (op, msg, flags) - payload += msg - req_seq += 1 - - self.sock.send(payload, 0) - - done = False - rsp = [] - op_rsp = [] - while not done: - reply = self.sock.recv(self._recv_size) - nms = NlMsgs(reply, attr_space=op.attr_set) - self._recv_dbg_print(reply, nms) - for nl_msg in nms: - if nl_msg.nl_seq in reqs_by_seq: - (op, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq] - if nl_msg.extack: - self._decode_extack(req_msg, op, nl_msg.extack) - else: - op = None - req_flags = [] - - if nl_msg.error: - raise NlError(nl_msg) - if nl_msg.done: - if nl_msg.extack: - print("Netlink warning:") - print(nl_msg) - - if Netlink.NLM_F_DUMP in req_flags: - rsp.append(op_rsp) - elif not op_rsp: - rsp.append(None) - elif len(op_rsp) == 1: - rsp.append(op_rsp[0]) - else: - rsp.append(op_rsp) - op_rsp = [] - - del reqs_by_seq[nl_msg.nl_seq] - done = len(reqs_by_seq) == 0 - break - - decoded = self.nlproto.decode(self, nl_msg, op) - - # Check if this is a reply to our request - if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value: - if decoded.cmd() in self.async_msg_ids: - self.handle_ntf(decoded) - continue - else: - print('Unexpected message: ' + repr(decoded)) - continue - - rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name) - if op.fixed_header: - rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header)) - op_rsp.append(rsp_msg) - - return rsp - - def _op(self, method, vals, flags=None, dump=False): - req_flags = flags or [] - if dump: - req_flags.append(Netlink.NLM_F_DUMP) - - ops = [(method, vals, req_flags)] - return self._ops(ops)[0] - - def do(self, method, vals, flags=None): - return self._op(method, vals, flags) - - def dump(self, method, vals): - return self._op(method, vals, dump=True) - - def do_multi(self, ops): - return self._ops(ops) |