CTLV compared with the Netlink YAML specs

The kernel already describes Netlink protocols in YAML (Documentation/netlink/specs/, schemas in Documentation/netlink/). CTLV specs look similar on purpose, and differ in ways that follow from CTLV being a character-device transport. This page lines them up. "Netlink" below means the modern genetlink schema, Documentation/netlink/genetlink.yaml; the older genetlink-c, genetlink-legacy and netlink-raw levels add keys on top of it and are mentioned only where they matter.

Common ground

If you know Netlink specs, these will feel familiar.

Same interface, two specs

The Netlink handshake family (Documentation/netlink/specs/handshake.yaml) lets a TLS handler daemon accept a pending handshake request. Here is its accept command, abridged, and a CTLV spec for an equivalent invented interface.

Netlink

name: handshake
protocol: genetlink
doc: Netlink protocol to request a transport layer security handshake.

definitions:
  -
    type: enum
    name: handler-class
    value-start: 0
    entries: [none, tlshd, max]
  -
    type: enum
    name: auth
    value-start: 0
    entries: [unspec, unauth, psk, x509]

attribute-sets:
  -
    name: x509
    attributes:
      - { name: cert,    type: s32 }
      - { name: privkey, type: s32 }
  -
    name: accept
    attributes:
      - { name: sockfd,        type: s32 }
      - { name: handler-class, type: u32, enum: handler-class }
      - { name: timeout,       type: u32 }
      - { name: auth-mode,     type: u32, enum: auth }
      - { name: peer-identity, type: u32, multi-attr: true }
      - { name: certificate,   type: nest, nested-attributes: x509,
          multi-attr: true }
  -
    name: done
    attributes:
      - name: status
        type: u32
        checks: { max: max-errno }

operations:
  list:
    -
      name: accept
      doc: Handler retrieves next queued handshake request
      attribute-set: accept
      flags: [admin-perm]
      do:
        request:
          attributes: [handler-class]
        reply:
          attributes: [sockfd, timeout, auth-mode,
                       peer-identity, certificate]

CTLV

family:
  name: ctlv-handshake
  inherits: ctlv

enums:
  - name: handler-class
    values: [ tlshd ]
  - name: auth
    values: [ unauth, psk, x509 ]

attr-groups:
  - name: handshake
    attrs:
      - name: sockfd
        type: sint
      - name: handler-class
        enum: handler-class
      - name: timeout
        type: uint
      - name: auth-mode
        enum: auth
      - name: peer-identity
        type: uint
        max-occurrences: 8
      - name: certificate
        attrs: x509
        max-occurrences: 4
      - name: cert
        type: sint
      - name: privkey
        type: sint
      - name: status
        type: uint
        max: 4095

attr-sets:
  - name: x509
    attrs: [ handshake.cert, handshake.privkey ]
  - name: accept-request
    attrs: handshake.handler-class
  - name: accept-reply
    attrs: [ handshake.sockfd, handshake.timeout, handshake.auth-mode ]
    optional-attrs: [ handshake.peer-identity, handshake.certificate ]

ops:
  - name: accept
    type: action
    request:
      attrs: accept-request
    reply:
      attrs: accept-reply

The CTLV spec passes the CTLV validator once registered. Points worth noticing:

At a glance

Netlink YAMLCTLV YAML
TransportNetlink socketsCharacter device per instance, one ioctl, read() for events
Interface identityFamily name; numeric ID allocated at runtimeFamily name plus a fixed numeric ID from an append-only registry
Reuse across interfacesAttribute sets, subset-of; no inheritance between familiesSingle inheritance between families; stubs as extension points
Required vs optional attributesNot expressed in the specExpressed per placement (attrs / optional-attrs) and enforced
Integer widths8, 16, 32, 64 bit, signed and unsigned, byte order selectableAlways 64 bit
NumberingSequential, overridable with valueStrictly by position; no override
Max message lengthNot computedComputed and bounded at generation time
Compatibility checkingNo snapshot mechanism in the schemasChecked-in ABI snapshots, checked by the tool
Schema variantsFour levels (genetlink, -c, -legacy, netlink-raw)One
Unsolicited messagesMulticast groupsPer-file-description subscriptions and a bounded queue

Identity and structure

TopicNetlinkCTLV
Top-level keysname, doc, protocol, uapi-header, definitions, attribute-sets, operations, mcast-groups, kernel-familyfamily, enums, flags, attr-groups, attr-sets, ops
Required keysname, doc, attribute-sets, operationsfamily only
Family IDAllocated dynamically; the name identifies the familyIndex in families.yaml; part of every attr and op ID
File nameFreeMust equal the family name
InheritanceNone. A spec is complete and independent of other specsOne parent. A child name is parent-suffix. The root family ctlv supplies discovery, introspection and event operations to every family
Partial reusesubset-of declares a restricted view of another attribute set in the same specSelectors pick any mix of single attrs, whole groups and other sets
Extension by othersNot supported by the languageA parent leaves a stub set; one descendant fills it
Constantsdefinitions may declare const (and struct in the legacy level), and may point at an existing C headerEnums and flags only; no constants, no external headers
Naming controlname-prefix, enum-name, async-prefix, uapi-headerNone; C names are derived from the YAML names, and a collision is an error
YAML dialectStandard YAML validated by the schemaStrict subset: no anchors, aliases, merge keys, explicit tags or duplicate keys

Attributes

Declaring and using

Both languages separate declaring an attribute from deciding where it appears, but at different granularity.

NetlinkCTLV
Containerattribute-sets: a named space of attributesattr-groups: a named namespace of attributes (declaration)
Message levelAn operation names one attribute-set; its request and reply list attribute names from that setAn attr-set lists selectors; an operation names a set (or selectors) for its request and for its reply
Nestingtype: nest with nested-attributes naming another setA nest attr with attrs (and optional-attrs) selectors
Attributes from different spaces in one messageNot within one operation, which names one setYes; a set can select from any groups
RecursionNo dedicated keywordExplicit recursive: true, query replies only
Required attributeNo such concept in the specListed under attrs; absence is a validation error naming the attribute
Repetitionmulti-attr: true; unboundedmax-occurrences: N or unlimited (query replies only); repeated occurrences must be adjacent in a request
Arraysindexed-array, sub-typeRepeat the attribute
Documentationdoc on nearly everything, required on the family and each operationYAML comments

Numbering

NetlinkCTLV
Attribute numberPosition within the set, overridable with value; the number is local to the attribute setPosition within the group; globally unique 64-bit ID combining family, group and attribute
Operation numberPosition, overridable with valuePosition; globally unique ID combining family and operation
Enum startvalue-start, default 0Always 1
Flag bitsFrom the starting bit given by value-startFrom bit 0, at most 64
Reserved slotsunused and pad attribute typesNone

CTLV's absence of value overrides means a CTLV spec file is its own numbering: moving or deleting a declaration changes IDs, which the ABI check reports.

Type mapping

NetlinkCTLVNotes
u8, u16, u32, u64uintCTLV is 64-bit on the wire; use max to state the real range.
s8 … s64sintSame.
uint, sintuint, sintNetlink's variable-width forms; CTLV's are fixed 64-bit.
flag (presence means true)bool (one byte, 0 or 1)A CTLV bool is always carried with a value.
stringstringCTLV requires max-length.
binarybinaryCTLV requires length or max-length.
nestattr with attrs
type: u32 + enum: nameenum: nameEnum is a primary form in CTLV; there is no separate integer width.
enum-as-flags, or an enum of flags typeflags: nameFlags are their own definition kind in CTLV.
byte-ordernoneCTLV integers are not byte-order selectable.
display-hintnoneCTLV has unit and scale for integers instead.
indexed-array, nest-type-valuenone
unused, padnoneCTLV pads implicitly to 8 bytes.
noneblobDescribes a buffer in caller memory (direction, backing, maximum length, alignment). Netlink conveys such things as plain integers.
noneunit (seconds, hertz, bytes, celsius), scaleMachine-readable meaning of an integer.

Validation constraints

Netlink checksCTLV
min, max (integer, constant name, or limit such as u8-max)min, max on the attribute; integer literals only
min-len, max-len, exact-len (integer or defined constant)min-length, max-length, length on the attribute; integer literals only
flags-mask (names a flags constant)Implied by flags: name
Kernel policy generated from the specKernel schema generated from the spec
dont-validate: [strict, dump, dump-strict]None; validation is not switchable per operation
Unknown attributes tolerated in non-strict modeRejected, unless the sender set CTLV_ATTR_F_IGNORE_IF_UNKNOWN on the attribute
No ordering rule between repeated attributesOccurrences of one attribute must be contiguous
Message size left to the handlerMaximum request, reply and event sizes are computed by the generator and checked against 1 MiB limits
Errors reported through extended ACKErrors reported in the reply buffer with a reason, message and offsets (error group of the root family)

Operations

TopicNetlinkCTLV
Operation kindsOne command may offer do, dump, or both, and be tied to a notificationEach op has exactly one type: action, query or event
Request/reply shapeAttribute-name lists under do.request, do.reply, dump.request, dump.replyrequest and reply placements (attrs, optional-attrs)
Multi-part resultsDump: a series of messages terminated by NLMSG_DONEQuery: one reply returned in fragments, resumable by cursor, with a generation check for staleness
Permissionsflags: [admin-perm, uns-admin-perm] (capability checks)fd-access: read | read-write (how the descriptor was opened); default read-write
Hookspre and post function names per do / dumpA family-wide pre/post pair, plus a per-op pair with pre-post: true; implemented by name convention, not named in the spec
Conditional presenceconfig-cond (kernel config option)optional: true (family opts in per device at init) and availability: dynamic (enable and disable at run time)
Introspection of what existsPolicy dump via the nlctrl familyThe families-dump, ops-dump and op-schema-get operations of the root family, which report the effective per-device schema including disabled ops

Events and notifications

NetlinkCTLV
DeclarationA command with notify (reuse another command's reply type) or event (explicit attribute list), assigned an mcgrpAn op with type: event and its own reply
Delivery groupmcast-groups; a socket joins a groupNo groups. Each open file description subscribes to individual events (subscriptions-add) and has its own queue
OverflowSocket buffer overrunA bounded queue (event-queue-set), with an event-overflow record reporting a sequence range and dropped count
Device removalNone in the languagedevice-removed event in the root family

Evolution and tooling

NetlinkCTLV
Generated outputsUAPI header, kernel policy and operation tables, and user-space code, by ynl_gen_c.py from markers in the target filesUAPI header, kernel header and kernel source, by tools/ctlv from a manifest (generated-files.yaml)
User-space supportThe YNL Python CLI and generated C user libraries read the same specsThe generator has UAPI and kernel modes only; user space discovers the schema at run time through the root family's introspection operations
RegistryNone; family names are resolved by the kernel at run timeAppend-only families.yaml; position is the ID; deprecated entries keep their slot
Compatibility enforcementRegeneration keeps generated files in sync; no snapshot of the interface itselfPer-family ABI snapshot and a registry baseline; narrowing a bound, making a placement required, removing or renumbering anything fails the check
Schema levelsgenetlink for new families; genetlink-c, genetlink-legacy, netlink-raw for older protocols with quirks (fixed headers, structs, sub-messages, raw protocols)One schema; there are no pre-existing protocols to describe

Key-by-key map

Closest equivalents. A dash means there is no counterpart.

NetlinkCTLV
namefamily.name
doc—
protocol—
uapi-headergenerated-files.yaml outputs.uapi-header
definitions type: enumenums
definitions type: flagsflags
definitions type: const—
value-start, entriesvalues (fixed start)
render-max, header, scope—
attribute-sets entryattr-groups entry (declaration) plus attr-sets entry (use)
subset-ofSelecting a subset of a group in an attr-set
name-prefix, enum-name—
attribute typeattribute type, enum, flags, attrs or blob
value (attribute or op)—
multi-attrmax-occurrences
nested-attributesattrs, optional-attrs
checks.min, checks.maxmin, max
checks.min-len, max-len, exact-lenmin-length, max-length, length
checks.flags-maskimplied by flags
byte-order, display-hint— (unit, scale serve a different purpose)
operations.listops
dotype: action
dumptype: query
notify, event, mcgrptype: event with reply
attribute-set (on an operation)—; request / reply select any sets
request.attributes, reply.attributesrequest.attrs, reply.attrs and the optional-attrs forms
flags: [admin-perm]fd-access (different mechanism)
pre, postpre-post
config-condoptional / availability (per device, not per build)
dont-validate—
mcast-groups— (subscriptions)
kernel-familyfamily.pre-init, post-init, pre-fini (lifecycle callbacks)
—family.inherits, stub, recursive, blob, unit, scale, suggested-reply-length, availability, optional

Reading across

Most differences trace back to two facts.

Porting an interface from a Netlink spec to CTLV therefore mostly means collapsing integer widths to uint/sint with ranges, turning each operation's attribute list into an attr set with explicit required and optional members, replacing multi-attr with a bound, replacing multicast groups with event operations, and deciding what each operation's fd-access should be.