nvlist/netlink/CBOR/etc. (Was: Support for nv(9) in if_clone)

From: Vadim Goncharov <vadimnuclight_at_gmail.com>
Date: Sun, 19 Oct 2025 20:51:48 UTC
On Sat, 18 Oct 2025 10:37:00 +0200
Kristof Provost <kp@FreeBSD.org> wrote:

> On 16 Oct 2025, at 22:56, Vadim Goncharov wrote:
> > On Wed, 15 Oct 2025 11:15:03 +0330
> > Seyed Pouria Mousavizadeh Tehrani <info@spmzt.net> wrote:
> >  
> >> Currently our ifreq interface supports nv(9) lists via ifreq_nv_req.
> >> This allows network interface drivers to implement their 
> >> configuration
> >> using nvlists (for example: if_ovpn, if_pf*).
> >>
> >> There is a problem: an interface that is implemented entirely via 
> >> nv(9)
> >> cannot be configured during the cloning phase (via if_clone_create).
> >> if_clone_create converts the ifreq struct to ifdrv and only copies
> >> ifr_data to params, so the ifru_nv field is lost during cloning.
> >>
> >> I am interested in implementing a solution to this issue and am
> >> considering two possible approaches:
> >>
> >>  1. Extend the ifdrv struct to include the ifreq_nv_req struct, and
> >>     verify/make sure that other implemented modules are not affected.
> >>  2. Introduce a new ioctl, SIOCIFCREATENV, to handle the nv part
> >>     separately, which would be more complicated but potentially less
> >>     disruptive to existing modules.
> >>
> >> Which one do you think would be more suitable, or do you have any
> >> alternative suggestions?  
> >
> > I'd vote for the latter, because it also will make task easier 
> > when/if/ever
> > the nvlist plague will be replaced by something more sane.
> >  
> Absent detail this isn’t particularly useful feedback.
> 
> Having used nvlists in pf I’ve found them to be fairly useful in 
> defining extensible interfaces.
> There are a few downsides though. The big one being horrendous 
> performance and memory use for large requests. This likely isn’t a 
> problem for this use case. It really only bit pf for state export, which 
> is potentially very large.
> 
> The other downside, and the reason I’d be inclined to look at just 
> transitioning entirely to netlink is that dealing with them is annoying, 
> because you have to cope with three sizes: the size of the nvlist passed 
> from userspace, the size of the buffer userspace allocated and the size 
> of the kernel’s reply (if applicable). It also leaves userspace to 
> deal with ‘Is this buffer large enough?’, which isn’t really 
> something you have to think about with netlink.
> 
> As an aside, because you mention if_ovpn: I wound up using nvlists 
> because netlink wasn’t available yet for FreeBSD. The Linux equivalent 
> uses netlink (of course), and it would have been very nice to be able to 
> have the same interface on Linux and FreeBSD.

That's not a detail question but rather, a values/goal one: nvlists must be
considered legacy. It is understandable when they appeared in ZFS on century
boundary, as there market hardly had anything usable in this area then. It is
understandable that they were still used before 2014, alternatives were young
then. But for nowadays, it's just an old NIH syndrome reinvented bicycle wheel.

First of all, it is not a standard of any kind. Thus, it is not known outside
FreeBSD and systems having ZFS. Situation is somewhat similar to netlink: many
tools (e.g. on GitHub) target Linux, and supporting FreeBSD requires effort,
while for netlink, they can just use netlink. The same with serialization
formats - any (e.g. monitoring) tool e.g. in Python could consume JSON or CBOR
with a single line of code. With nvlists, they can't - and this is not easy
even if they wanted to - because unpopular format of course has no bindings
for popular languages. Yes, is we want to promote FreeBSD, dynamic scripting
languages must be considered as primary target.

Then, it is not open = not being extensible. This means you can't just add new
data types or constructs to it even if you want - in contrast to CBOR or even
XML ir YAML where new types could be easily defined.

Last but not least, a horrible API. Very large number of functions, so
unsurprisingly vulnerabilities history, with not always consistent/intuitive
naming. Consider e.g. XML: you have at least two modes, an entire accessible
parsed tree (DOM) or a streaming event-driven interface (callbacks on each new
element). You can find both kind of APIs for JSON, or at least something
simple and understandable in memory (e.g. cJSON); same true for the binary
CBOR [RFC 8949], and our base system even have libcbor for years. In contrast,
nvlist API looks like if it was something customary above XML or JSON.

The only possible upside which I see with nvlists is ability to pass file
descriptor, which made into Capsicum, so nvlists, sadly, will not disappear
any soon. But for all other (new) uses, it must be considered bronze age
legacy and discarded in favour of something more modern, simple, extensible.

Regarding netlink, this is not strict comparison - netlink is more about
sending data than format of that data, like Layer 4 OSI (TCP) vs Layer 6
(JSON), so nothing prevents you from sending over netlink e.g. also nvlists
or something better, like older netgraph(4)'s ng_parse (it has advantage of
automatical C structs conversion) or binary CBOR or even textual JSON... the
own netlink's default offered format is same stone-aged as nvlist, it just
have a bunch of ready-made macros, though you will have to extend them for
your data, but netlink does not insist, so you can use anything you want.

-- 
WBR, @nuclight