Commit 35fae44e authored by Jakub Kicinski's avatar Jakub Kicinski

Merge branch 'ynl-add-support-for-user-headers-and-struct-attrs'

Donald Hunter says:

====================
ynl: add support for user headers and struct attrs

Add support for user headers and struct attrs to YNL. This patchset adds
features to ynl and add a partial spec for openvswitch that demonstrates
use of the features.

Patch 1-4 add features to ynl
Patch 5 adds partial openvswitch specs that demonstrate the new features
Patch 6-7 add documentation for legacy structs and for sub-type
====================

Link: https://lore.kernel.org/r/20230327083138.96044-1-donald.hunter@gmail.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents de749452 04eac393
...@@ -218,6 +218,11 @@ properties: ...@@ -218,6 +218,11 @@ properties:
description: Max length for a string or a binary attribute. description: Max length for a string or a binary attribute.
$ref: '#/$defs/len-or-define' $ref: '#/$defs/len-or-define'
sub-type: *attr-type sub-type: *attr-type
# Start genetlink-legacy
struct:
description: Name of the struct type used for the attribute.
type: string
# End genetlink-legacy
# Make sure name-prefix does not appear in subsets (subsets inherit naming) # Make sure name-prefix does not appear in subsets (subsets inherit naming)
dependencies: dependencies:
...@@ -256,6 +261,14 @@ properties: ...@@ -256,6 +261,14 @@ properties:
async-enum: async-enum:
description: Name for the enum type with notifications/events. description: Name for the enum type with notifications/events.
type: string type: string
# Start genetlink-legacy
fixed-header: &fixed-header
description: |
Name of the structure defining the optional fixed-length protocol
header. This header is placed in a message after the netlink and
genetlink headers and before any attributes.
type: string
# End genetlink-legacy
list: list:
description: List of commands description: List of commands
type: array type: array
...@@ -288,6 +301,9 @@ properties: ...@@ -288,6 +301,9 @@ properties:
type: array type: array
items: items:
enum: [ strict, dump ] enum: [ strict, dump ]
# Start genetlink-legacy
fixed-header: *fixed-header
# End genetlink-legacy
do: &subop-type do: &subop-type
description: Main command handler. description: Main command handler.
type: object type: object
......
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
name: ovs_datapath
version: 2
protocol: genetlink-legacy
doc:
OVS datapath configuration over generic netlink.
definitions:
-
name: ovs-header
type: struct
members:
-
name: dp-ifindex
type: u32
-
name: user-features
type: flags
entries:
-
name: unaligned
doc: Allow last Netlink attribute to be unaligned
-
name: vport-pids
doc: Allow datapath to associate multiple Netlink PIDs to each vport
-
name: tc-recirc-sharing
doc: Allow tc offload recirc sharing
-
name: dispatch-upcall-per-cpu
doc: Allow per-cpu dispatch of upcalls
-
name: datapath-stats
type: struct
members:
-
name: hit
type: u64
-
name: missed
type: u64
-
name: lost
type: u64
-
name: flows
type: u64
-
name: megaflow-stats
type: struct
members:
-
name: mask-hit
type: u64
-
name: masks
type: u32
-
name: padding
type: u32
-
name: cache-hits
type: u64
-
name: pad1
type: u64
attribute-sets:
-
name: datapath
attributes:
-
name: name
type: string
-
name: upcall-pid
doc: upcall pid
type: u32
-
name: stats
type: binary
struct: datapath-stats
-
name: megaflow-stats
type: binary
struct: megaflow-stats
-
name: user-features
type: u32
enum: user-features
enum-as-flags: true
-
name: pad
type: unused
-
name: masks-cache-size
type: u32
-
name: per-cpu-pids
type: binary
sub-type: u32
operations:
fixed-header: ovs-header
list:
-
name: dp-get
doc: Get / dump OVS data path configuration and state
value: 3
attribute-set: datapath
do: &dp-get-op
request:
attributes:
- name
reply:
attributes:
- name
- upcall-pid
- stats
- megaflow-stats
- user-features
- masks-cache-size
- per-cpu-pids
dump: *dp-get-op
-
name: dp-new
doc: Create new OVS data path
value: 1
attribute-set: datapath
do:
request:
attributes:
- dp-ifindex
- name
- upcall-pid
- user-features
-
name: dp-del
doc: Delete existing OVS data path
value: 2
attribute-set: datapath
do:
request:
attributes:
- dp-ifindex
- name
mcast-groups:
list:
-
name: ovs_datapath
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
name: ovs_vport
version: 2
protocol: genetlink-legacy
doc:
OVS vport configuration over generic netlink.
definitions:
-
name: ovs-header
type: struct
members:
-
name: dp-ifindex
type: u32
-
name: vport-type
type: enum
entries: [ unspec, netdev, internal, gre, vxlan, geneve ]
-
name: vport-stats
type: struct
members:
-
name: rx-packets
type: u64
-
name: tx-packets
type: u64
-
name: rx-bytes
type: u64
-
name: tx-bytes
type: u64
-
name: rx-errors
type: u64
-
name: tx-errors
type: u64
-
name: rx-dropped
type: u64
-
name: tx-dropped
type: u64
attribute-sets:
-
name: vport-options
attributes:
-
name: dst-port
type: u32
-
name: extension
type: u32
-
name: upcall-stats
attributes:
-
name: success
type: u64
value: 0
-
name: fail
type: u64
-
name: vport
attributes:
-
name: port-no
type: u32
-
name: type
type: u32
enum: vport-type
-
name: name
type: string
-
name: options
type: nest
nested-attributes: vport-options
-
name: upcall-pid
type: binary
sub-type: u32
-
name: stats
type: binary
struct: vport-stats
-
name: pad
type: unused
-
name: ifindex
type: u32
-
name: netnsid
type: u32
-
name: upcall-stats
type: nest
nested-attributes: upcall-stats
operations:
list:
-
name: vport-get
doc: Get / dump OVS vport configuration and state
value: 3
attribute-set: vport
fixed-header: ovs-header
do: &vport-get-op
request:
attributes:
- dp-ifindex
- name
reply: &dev-all
attributes:
- dp-ifindex
- port-no
- type
- name
- upcall-pid
- stats
- ifindex
- netnsid
- upcall-stats
dump: *vport-get-op
mcast-groups:
list:
-
name: ovs_vport
...@@ -162,9 +162,91 @@ Other quirks (todo) ...@@ -162,9 +162,91 @@ Other quirks (todo)
Structures Structures
---------- ----------
Legacy families can define C structures both to be used as the contents Legacy families can define C structures both to be used as the contents of
of an attribute and as a fixed message header. The plan is to define an attribute and as a fixed message header. Structures are defined in
the structs in ``definitions`` and link the appropriate attrs. ``definitions`` and referenced in operations or attributes. Note that
structures defined in YAML are implicitly packed according to C
conventions. For example, the following struct is 4 bytes, not 6 bytes:
.. code-block:: c
struct {
u8 a;
u16 b;
u8 c;
}
Any padding must be explicitly added and C-like languages should infer the
need for explicit padding from whether the members are naturally aligned.
Here is the struct definition from above, declared in YAML:
.. code-block:: yaml
definitions:
-
name: message-header
type: struct
members:
-
name: a
type: u8
-
name: b
type: u16
-
name: c
type: u8
Fixed Headers
~~~~~~~~~~~~~
Fixed message headers can be added to operations using ``fixed-header``.
The default ``fixed-header`` can be set in ``operations`` and it can be set
or overridden for each operation.
.. code-block:: yaml
operations:
fixed-header: message-header
list:
-
name: get
fixed-header: custom-header
attribute-set: message-attrs
Attributes
~~~~~~~~~~
A ``binary`` attribute can be interpreted as a C structure using a
``struct`` property with the name of the structure definition. The
``struct`` property implies ``sub-type: struct`` so it is not necessary to
specify a sub-type.
.. code-block:: yaml
attribute-sets:
-
name: stats-attrs
attributes:
-
name: stats
type: binary
struct: vport-stats
C Arrays
--------
Legacy families also use ``binary`` attributes to encapsulate C arrays. The
``sub-type`` is used to identify the type of scalar to extract.
.. code-block:: yaml
attributes:
-
name: ports
type: binary
sub-type: u32
Multi-message DO Multi-message DO
---------------- ----------------
......
...@@ -254,6 +254,16 @@ rather than depend on what is specified in the spec file. ...@@ -254,6 +254,16 @@ rather than depend on what is specified in the spec file.
The validation policy in the kernel is formed by combining the type The validation policy in the kernel is formed by combining the type
definition (``type`` and ``nested-attributes``) and the ``checks``. definition (``type`` and ``nested-attributes``) and the ``checks``.
sub-type
~~~~~~~~
Legacy families have special ways of expressing arrays. ``sub-type`` can be
used to define the type of array members in case array members are not
fully defined as attributes (in a bona fide attribute space). For instance
a C array of u32 values can be specified with ``type: binary`` and
``sub-type: u32``. Binary types and legacy array formats are described in
more detail in :doc:`genetlink-legacy`.
operations operations
---------- ----------
......
...@@ -149,8 +149,11 @@ class SpecAttr(SpecElement): ...@@ -149,8 +149,11 @@ class SpecAttr(SpecElement):
Represents a single attribute type within an attr space. Represents a single attribute type within an attr space.
Attributes: Attributes:
value numerical ID when serialized value numerical ID when serialized
attr_set Attribute Set containing this attr 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
""" """
def __init__(self, family, attr_set, yaml, value): def __init__(self, family, attr_set, yaml, value):
super().__init__(family, yaml) super().__init__(family, yaml)
...@@ -158,6 +161,8 @@ class SpecAttr(SpecElement): ...@@ -158,6 +161,8 @@ class SpecAttr(SpecElement):
self.value = value self.value = value
self.attr_set = attr_set self.attr_set = attr_set
self.is_multi = yaml.get('multi-attr', False) self.is_multi = yaml.get('multi-attr', False)
self.struct_name = yaml.get('struct')
self.sub_type = yaml.get('sub-type')
class SpecAttrSet(SpecElement): class SpecAttrSet(SpecElement):
...@@ -214,22 +219,61 @@ class SpecAttrSet(SpecElement): ...@@ -214,22 +219,61 @@ class SpecAttrSet(SpecElement):
return self.attrs.items() return self.attrs.items()
class SpecStructMember(SpecElement):
"""Struct member attribute
Represents a single struct member attribute.
Attributes:
type string, type of the member attribute
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.type = yaml['type']
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 SpecOperation(SpecElement): class SpecOperation(SpecElement):
"""Netlink Operation """Netlink Operation
Information about a single Netlink operation. Information about a single Netlink operation.
Attributes: Attributes:
value numerical ID when serialized, None if req/rsp values differ value numerical ID when serialized, None if req/rsp values differ
req_value numerical ID when serialized, user -> kernel req_value numerical ID when serialized, user -> kernel
rsp_value numerical ID when serialized, user <- kernel rsp_value numerical ID when serialized, user <- kernel
is_call bool, whether the operation is a call is_call bool, whether the operation is a call
is_async bool, whether the operation is a notification is_async bool, whether the operation is a notification
is_resv bool, whether the operation does not exist (it's just a reserved ID) is_resv bool, whether the operation does not exist (it's just a reserved ID)
attr_set attribute set name attr_set attribute set name
fixed_header string, optional name of fixed header struct
yaml raw spec as loaded from the spec file yaml raw spec as loaded from the spec file
""" """
def __init__(self, family, yaml, req_value, rsp_value): def __init__(self, family, yaml, req_value, rsp_value):
super().__init__(family, yaml) super().__init__(family, yaml)
...@@ -241,6 +285,7 @@ class SpecOperation(SpecElement): ...@@ -241,6 +285,7 @@ class SpecOperation(SpecElement):
self.is_call = 'do' in yaml or 'dump' in yaml self.is_call = 'do' in yaml or 'dump' in yaml
self.is_async = 'notify' in yaml or 'event' 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.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: # Added by resolve:
self.attr_set = None self.attr_set = None
...@@ -281,6 +326,7 @@ class SpecFamily(SpecElement): ...@@ -281,6 +326,7 @@ class SpecFamily(SpecElement):
msgs_by_value dict of all messages (indexed by name) msgs_by_value dict of all messages (indexed by name)
ops dict of all valid requests / responses ops dict of all valid requests / responses
consts dict of all constants/enums consts dict of all constants/enums
fixed_header string, optional name of family default fixed header struct
""" """
def __init__(self, spec_path, schema_path=None): def __init__(self, spec_path, schema_path=None):
with open(spec_path, "r") as stream: with open(spec_path, "r") as stream:
...@@ -344,6 +390,9 @@ class SpecFamily(SpecElement): ...@@ -344,6 +390,9 @@ class SpecFamily(SpecElement):
def new_attr_set(self, elem): def new_attr_set(self, elem):
return SpecAttrSet(self, elem) return SpecAttrSet(self, elem)
def new_struct(self, elem):
return SpecStruct(self, elem)
def new_operation(self, elem, req_val, rsp_val): def new_operation(self, elem, req_val, rsp_val):
return SpecOperation(self, elem, req_val, rsp_val) return SpecOperation(self, elem, req_val, rsp_val)
...@@ -351,6 +400,7 @@ class SpecFamily(SpecElement): ...@@ -351,6 +400,7 @@ class SpecFamily(SpecElement):
self._resolution_list.append(elem) self._resolution_list.append(elem)
def _dictify_ops_unified(self): def _dictify_ops_unified(self):
self.fixed_header = self.yaml['operations'].get('fixed-header')
val = 1 val = 1
for elem in self.yaml['operations']['list']: for elem in self.yaml['operations']['list']:
if 'value' in elem: if 'value' in elem:
...@@ -362,6 +412,7 @@ class SpecFamily(SpecElement): ...@@ -362,6 +412,7 @@ class SpecFamily(SpecElement):
self.msgs[op.name] = op self.msgs[op.name] = op
def _dictify_ops_directional(self): def _dictify_ops_directional(self):
self.fixed_header = self.yaml['operations'].get('fixed-header')
req_val = rsp_val = 1 req_val = rsp_val = 1
for elem in self.yaml['operations']['list']: for elem in self.yaml['operations']['list']:
if 'notify' in elem: if 'notify' in elem:
...@@ -399,6 +450,8 @@ class SpecFamily(SpecElement): ...@@ -399,6 +450,8 @@ class SpecFamily(SpecElement):
for elem in definitions: for elem in definitions:
if elem['type'] == 'enum' or elem['type'] == 'flags': if elem['type'] == 'enum' or elem['type'] == 'flags':
self.consts[elem['name']] = self.new_enum(elem) self.consts[elem['name']] = self.new_enum(elem)
elif elem['type'] == 'struct':
self.consts[elem['name']] = self.new_struct(elem)
else: else:
self.consts[elem['name']] = elem self.consts[elem['name']] = elem
......
...@@ -68,6 +68,11 @@ class Netlink: ...@@ -68,6 +68,11 @@ class Netlink:
class NlAttr: class NlAttr:
type_formats = { 'u8' : ('B', 1), 's8' : ('b', 1),
'u16': ('H', 2), 's16': ('h', 2),
'u32': ('I', 4), 's32': ('i', 4),
'u64': ('Q', 8), 's64': ('q', 8) }
def __init__(self, raw, offset): def __init__(self, raw, offset):
self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) self._len, self._type = struct.unpack("HH", raw[offset:offset + 4])
self.type = self._type & ~Netlink.NLA_TYPE_MASK self.type = self._type & ~Netlink.NLA_TYPE_MASK
...@@ -93,6 +98,21 @@ class NlAttr: ...@@ -93,6 +98,21 @@ class NlAttr:
def as_bin(self): def as_bin(self):
return self.raw return self.raw
def as_c_array(self, type):
format, _ = self.type_formats[type]
return list({ x[0] for x in struct.iter_unpack(format, self.raw) })
def as_struct(self, members):
value = dict()
offset = 0
for m in members:
# TODO: handle non-scalar members
format, size = self.type_formats[m.type]
decoded = struct.unpack_from(format, self.raw, offset)
offset += size
value[m.name] = decoded[0]
return value
def __repr__(self): def __repr__(self):
return f"[type:{self.type} len:{self._len}] {self.raw}" return f"[type:{self.type} len:{self._len}] {self.raw}"
...@@ -258,14 +278,22 @@ def _genl_load_families(): ...@@ -258,14 +278,22 @@ def _genl_load_families():
class GenlMsg: class GenlMsg:
def __init__(self, nl_msg): def __init__(self, nl_msg, fixed_header_members=[]):
self.nl = nl_msg self.nl = nl_msg
self.hdr = nl_msg.raw[0:4] self.hdr = nl_msg.raw[0:4]
self.raw = nl_msg.raw[4:] offset = 4
self.genl_cmd, self.genl_version, _ = struct.unpack("BBH", self.hdr) self.genl_cmd, self.genl_version, _ = struct.unpack("BBH", self.hdr)
self.fixed_header_attrs = dict()
for m in fixed_header_members:
format, size = NlAttr.type_formats[m.type]
decoded = struct.unpack_from(format, nl_msg.raw, offset)
offset += size
self.fixed_header_attrs[m.name] = decoded[0]
self.raw = nl_msg.raw[offset:]
self.raw_attrs = NlAttrs(self.raw) self.raw_attrs = NlAttrs(self.raw)
def __repr__(self): def __repr__(self):
...@@ -367,6 +395,15 @@ class YnlFamily(SpecFamily): ...@@ -367,6 +395,15 @@ class YnlFamily(SpecFamily):
value = enum.entries_by_val[raw - i].name value = enum.entries_by_val[raw - i].name
rsp[attr_spec['name']] = value rsp[attr_spec['name']] = value
def _decode_binary(self, attr, attr_spec):
if attr_spec.struct_name:
decoded = attr.as_struct(self.consts[attr_spec.struct_name])
elif attr_spec.sub_type:
decoded = attr.as_c_array(attr_spec.sub_type)
else:
decoded = attr.as_bin()
return decoded
def _decode(self, attrs, space): def _decode(self, attrs, space):
attr_space = self.attr_sets[space] attr_space = self.attr_sets[space]
rsp = dict() rsp = dict()
...@@ -386,7 +423,7 @@ class YnlFamily(SpecFamily): ...@@ -386,7 +423,7 @@ class YnlFamily(SpecFamily):
elif attr_spec["type"] == 'string': elif attr_spec["type"] == 'string':
decoded = attr.as_strz() decoded = attr.as_strz()
elif attr_spec["type"] == 'binary': elif attr_spec["type"] == 'binary':
decoded = attr.as_bin() decoded = self._decode_binary(attr, attr_spec)
elif attr_spec["type"] == 'flag': elif attr_spec["type"] == 'flag':
decoded = True decoded = True
else: else:
...@@ -480,6 +517,13 @@ class YnlFamily(SpecFamily): ...@@ -480,6 +517,13 @@ class YnlFamily(SpecFamily):
req_seq = random.randint(1024, 65535) req_seq = random.randint(1024, 65535)
msg = _genl_msg(self.family.family_id, nl_flags, op.req_value, 1, req_seq) msg = _genl_msg(self.family.family_id, nl_flags, op.req_value, 1, req_seq)
fixed_header_members = []
if op.fixed_header:
fixed_header_members = self.consts[op.fixed_header].members
for m in fixed_header_members:
value = vals.pop(m.name)
format, _ = NlAttr.type_formats[m.type]
msg += struct.pack(format, value)
for name, value in vals.items(): for name, value in vals.items():
msg += self._add_attr(op.attr_set.name, name, value) msg += self._add_attr(op.attr_set.name, name, value)
msg = _genl_msg_finalize(msg) msg = _genl_msg_finalize(msg)
...@@ -506,7 +550,7 @@ class YnlFamily(SpecFamily): ...@@ -506,7 +550,7 @@ class YnlFamily(SpecFamily):
done = True done = True
break break
gm = GenlMsg(nl_msg) gm = GenlMsg(nl_msg, fixed_header_members)
# Check if this is a reply to our request # Check if this is a reply to our request
if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.rsp_value: if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.rsp_value:
if gm.genl_cmd in self.async_msg_ids: if gm.genl_cmd in self.async_msg_ids:
...@@ -516,7 +560,8 @@ class YnlFamily(SpecFamily): ...@@ -516,7 +560,8 @@ class YnlFamily(SpecFamily):
print('Unexpected message: ' + repr(gm)) print('Unexpected message: ' + repr(gm))
continue continue
rsp.append(self._decode(gm.raw_attrs, op.attr_set.name)) rsp.append(self._decode(gm.raw_attrs, op.attr_set.name)
| gm.fixed_header_attrs)
if not rsp: if not rsp:
return None return None
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment