Watch
1
0
Fork
You've already forked netlink
0
mirror of https://git.0x1b.me/netlink synced 2026-08-10 00:28:49 -05:00
A low-level library for creating and parsing netlink messages. Inspired by libmnl. https://git.0x1b.me/netlink
  • Zig 94.8%
  • Python 5%
  • C 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jordan Griege 70d9f5f75d Update trunk to latest
Also use llvm in debug builds to workaround an Aro bug.
2026-05-21 18:36:14 -05:00
bin Update trunk to latest 2026-05-21 18:36:14 -05:00
lib Move @cImport to build.zig 2026-04-18 14:03:23 -05:00
.gitignore gitignore new zig-pkg directory 2026-04-07 08:10:09 -05:00
build.zig Update trunk to latest 2026-05-21 18:36:14 -05:00
build.zig.zon Update trunk to latest 2026-05-21 18:36:14 -05:00
gen.py Fix enum entries with non-alpha leading character 2024-09-14 11:41:25 -05:00
LICENSE add license 2023-10-09 20:32:18 -05:00
README.md Refactor Client 2025-02-02 20:48:20 -06:00

netlink

A low-level library for creating and parsing netlink messages. Inspired by libmnl.

While this library is a decent replacement for libmnl, it is nowhere near the scope of libraries like libnl.

There is also a program that reimplements some functionality of iproute2, but its primary purpose is to verify that the library works.

Usage

Request and Response are comptime functions that wrap a type which is transferred over an AF_NETLINK socket, e.g. rtgenmsg and ifinfomsg. Both are backed by a buffer so that appending nlattrs does not require allocation.

So to create a new link, create a new type for that specific request:

const std = @import("std");
const nl = @import("netlink");
const linux = std.os.linux;

const LinkNewRequest = nl.Request(linux.NetlinkMessageType.RTM_NEWLINK, linux.ifinfomsg);

var buf = [_]u8{0} ** 128;
var req = LinkListRequest.init(&buf);
req.nlh.*.flags |= (linux.NLM_F_CREATE | linux.NLM_F_EXCL);

const name: [:0]u8 = "asdf";
_ = try req.add_str(@intFromEnum(linux.IFLA.IFNAME), name);
_ = req.done(); // Finalize the request by setting its length.

The Response type is created similarly, but supports an iterator pattern for traversing multiple messages in a single response and each message's attributes.

const LinkListResponse = nl.Response(linux.NetlinkMessageType.RTM_NEWLINK, linux.ifinfomsg);

const seq = 0;
const buf: []u8 = ""; // say this contains a netlink response
var res = LinkListResponse.init(seq, buf);

while (true) {
    var msg = try res.next();
    switch (msg) {
        // No more messages in this sequence are expected, and all future calls
        // of `res.next()` will return `.done`.
        .done => {},
        // There are more messages, but not in the current buffer.  You need to
        // call `std.os.recv()` on the netlink socket again and recreate `res`.
        .more => {},
        // The message contained the `Response`'s inner message type (e.g.
        // `ifinfomsg`).
        // The capture should _always_ be a pointer.
        .payload => |*payload| {
            while (try payload.next()) |attr| {
                if (attr.type == @intFromEnum(linux.IFLA.IFNAME)) std.debug.print("{s}\n", .{attr.read_slice()});
            }
        },
    }
}

The Handle structure wraps a netlink socket and byte buffer and reduces boilerplate required when receiving and parsing messages. Because when using NLM_F_ACK, the client should check that a message sequence is terminated by a NLMSG_DONE or NLMSG_ERROR message.

Example

const std = @import("std");
const nl = @import("netlink");

const rtgenmsg = extern struct {
    rtgen_family: u8,
};

const LinkListRequest = nl.message.Request(linux.NetlinkMessageType.RTM_GETLINK, rtgenmsg);
const LinkResponse = nl.message.Response(linux.NetlinkMessageType.RTM_NEWLINK, std.os.linux.ifinfomsg);

var buf = [_]u8{0} ** 4096;
var sk = try std.os.socket(std.os.linux.AF.NETLINK, std.os.linux.SOCK.RAW, std.os.linux.NETLINK.ROUTE);
defer std.os.close(sk);
var nlh = nl.Handle.init(sk, &buf);

var req = try nlh.new_req(LinkListRequest);
req.hdr.*.rtgen_family = std.os.AF.PACKET;
req.nlh.*.flags |= std.os.linux.NLM_F_DUMP;
try nlh.send(req);

var res = nlh.recv_all(LinkResponse);
while (try res.next()) |payload| {
    std.debug.print("{}\n", .{payload.value.index});
}

There is plenty more example code in the bin/ directory.

Code Generation

$ export KERNEL_PATH=...
$ export PYTHONPATH="${KERNEL_PATH}/tools/net/ynl/lib"
$ for name in rt_addr rt_link rt_route; do ./gen.py ${KERNEL_PATH}/Documentation/netlink/specs/${name}.yaml > lib/${name}.zig && zig fmt lib/${name}.zig; done