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 --- 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