[RFC PATCH] genetlink: add multi-version family support for protocol negotiation
From: Christoph Böhmwalder
Date: Mon Mar 16 2026 - 11:25:43 EST
Extend the genetlink infrastructure to allow families to advertise a
range of supported protocol versions [min_version, max_version]. This
enables a single kernel module to serve both old and new userspace by
letting userspace discover the maximum version via CTRL_ATTR_MAX_VERSION
and select the desired protocol dialect in genlmsghdr.version.
Rename genl_family.version to .min_version across all families (pure
rename, no behavioral change). This avoids updating all callers.
Add genl_family.max_version. When 0 (default) the family behaves
exactly as before. When > min_version, the controller advertises
CTRL_ATTR_MAX_VERSION in CTRL_CMD_GETFAMILY replies.
When sending a reply, echo the request's genlmsghdr.version, so
userspace receives responses in the protocol version it spoke.
Notifications (where info->nlhdr is NULL) continue to use min_version.
Add genlmsg_put_ver() for families that need to stamp a specific
version on notifications.
The immediate motivation is upstreaming DRBD 9 (genl family v1)
while keeping unmodified DRBD 8 userspace (family v2) working. The
DRBD family would register with min_version=1, max_version=2 and
handle both protocol dialects.
Signed-off-by: Christoph Böhmwalder <christoph.boehmwalder@xxxxxxxxxx>
---
include/net/genetlink.h | 27 +++++++++++++++++------
include/uapi/linux/genetlink.h | 1 +
net/netlink/genetlink.c | 39 ++++++++++++++++++++++++++++------
3 files changed, 55 insertions(+), 12 deletions(-)
diff --git a/include/net/genetlink.h b/include/net/genetlink.h
index 7b84f2cef8b1..7e306bf8436a 100644
--- a/include/net/genetlink.h
+++ b/include/net/genetlink.h
@@ -38,7 +38,11 @@ struct genl_info;
* struct genl_family - generic netlink family
* @hdrsize: length of user specific header in bytes
* @name: name of family
- * @version: protocol version
+ * @version: Only supported version (alias of min_version)
+ * @min_version: lowest protocol version supported
+ * @max_version: highest protocol version supported; when set and
+ * greater than @min_version the family accepts versions
+ * [@min_version, @max_version] from userspace
* @maxattr: maximum number of attributes supported
* @policy: netlink policy
* @netnsok: set to true if the family can handle network
@@ -78,7 +82,11 @@ struct genl_info;
struct genl_family {
unsigned int hdrsize;
char name[GENL_NAMSIZ];
- unsigned int version;
+ union {
+ unsigned int version;
+ unsigned int min_version;
+ };
+ unsigned int max_version;
unsigned int maxattr;
u8 netnsok:1;
u8 parallel_ops:1;
@@ -335,12 +343,19 @@ void genl_notify(const struct genl_family *family, struct sk_buff *skb,
void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
const struct genl_family *family, int flags, u8 cmd);
+void *genlmsg_put_ver(struct sk_buff *skb, u32 portid, u32 seq,
+ const struct genl_family *family, int flags,
+ u8 cmd, u8 version);
static inline void *
__genlmsg_iput(struct sk_buff *skb, const struct genl_info *info, int flags)
{
- return genlmsg_put(skb, info->snd_portid, info->snd_seq, info->family,
- flags, info->genlhdr->cmd);
+ u8 version = info->nlhdr ? info->genlhdr->version
+ : info->family->version;
+
+ return genlmsg_put_ver(skb, info->snd_portid, info->snd_seq,
+ info->family, flags, info->genlhdr->cmd,
+ version);
}
/**
@@ -442,8 +457,8 @@ static inline void *genlmsg_put_reply(struct sk_buff *skb,
const struct genl_family *family,
int flags, u8 cmd)
{
- return genlmsg_put(skb, info->snd_portid, info->snd_seq, family,
- flags, cmd);
+ return genlmsg_put_ver(skb, info->snd_portid, info->snd_seq, family,
+ flags, cmd, info->genlhdr->version);
}
/**
diff --git a/include/uapi/linux/genetlink.h b/include/uapi/linux/genetlink.h
index ddba3ca01e39..659aa31592ff 100644
--- a/include/uapi/linux/genetlink.h
+++ b/include/uapi/linux/genetlink.h
@@ -66,6 +66,7 @@ enum {
CTRL_ATTR_POLICY,
CTRL_ATTR_OP_POLICY,
CTRL_ATTR_OP,
+ CTRL_ATTR_MAX_VERSION,
__CTRL_ATTR_MAX,
};
diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
index a23d4c51c089..50ba0c99f648 100644
--- a/net/netlink/genetlink.c
+++ b/net/netlink/genetlink.c
@@ -880,18 +880,24 @@ int genl_unregister_family(const struct genl_family *family)
EXPORT_SYMBOL(genl_unregister_family);
/**
- * genlmsg_put - Add generic netlink header to netlink message
+ * genlmsg_put_ver - Add generic netlink header with explicit version
* @skb: socket buffer holding the message
* @portid: netlink portid the message is addressed to
* @seq: sequence number (usually the one of the sender)
* @family: generic netlink family
* @flags: netlink message flags
* @cmd: generic netlink command
+ * @version: protocol version to stamp on the message
*
- * Returns pointer to user specific header
+ * Like genlmsg_put() but allows the caller to specify the version field
+ * in the genetlink header. Useful for families that support multiple
+ * protocol versions and need to send notifications in a specific version.
+ *
+ * Returns: pointer to user specific header, or %NULL on failure.
*/
-void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
- const struct genl_family *family, int flags, u8 cmd)
+void *genlmsg_put_ver(struct sk_buff *skb, u32 portid, u32 seq,
+ const struct genl_family *family, int flags,
+ u8 cmd, u8 version)
{
struct nlmsghdr *nlh;
struct genlmsghdr *hdr;
@@ -903,11 +909,30 @@ void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
hdr = nlmsg_data(nlh);
hdr->cmd = cmd;
- hdr->version = family->version;
+ hdr->version = version;
hdr->reserved = 0;
return (char *) hdr + GENL_HDRLEN;
}
+EXPORT_SYMBOL(genlmsg_put_ver);
+
+/**
+ * genlmsg_put - Add generic netlink header to netlink message
+ * @skb: socket buffer holding the message
+ * @portid: netlink portid the message is addressed to
+ * @seq: sequence number (usually the one of the sender)
+ * @family: generic netlink family
+ * @flags: netlink message flags
+ * @cmd: generic netlink command
+ *
+ * Returns pointer to user specific header
+ */
+void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
+ const struct genl_family *family, int flags, u8 cmd)
+{
+ return genlmsg_put_ver(skb, portid, seq, family, flags, cmd,
+ family->version);
+}
EXPORT_SYMBOL(genlmsg_put);
static struct genl_dumpit_info *genl_dumpit_info_alloc(void)
@@ -1237,7 +1262,9 @@ static int ctrl_fill_info(const struct genl_family *family, u32 portid, u32 seq,
if (nla_put_string(skb, CTRL_ATTR_FAMILY_NAME, family->name) ||
nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, family->id) ||
- nla_put_u32(skb, CTRL_ATTR_VERSION, family->version) ||
+ nla_put_u32(skb, CTRL_ATTR_VERSION, family->min_version) ||
+ (family->max_version > family->min_version &&
+ nla_put_u32(skb, CTRL_ATTR_MAX_VERSION, family->max_version)) ||
nla_put_u32(skb, CTRL_ATTR_HDRSIZE, family->hdrsize) ||
nla_put_u32(skb, CTRL_ATTR_MAXATTR, family->maxattr))
goto nla_put_failure;
base-commit: f338e77383789c0cae23ca3d48adcc5e9e137e3c
--
2.53.0