0

I'm working on modifying the nbd driver in the Linux kernel (which needs to speak to a remote server) to add support for a new message type in the NBD protocol.

Since the feature negotiation of nbd is done in userspace, and since the new message format needs to be negotiated at that step if it is to be enabled, but must not be negotiated if the kernel does not support it (otherwise the server will send messages that the kernel does not understand), I want to add a way for user space to detect that this message format is supported.

So I thought I'd add a flag in a netlink message that marks support for this feature. I added a netlink message whose purpose would be to contain some flags, and then added the flag:

static const struct genl_small_ops nbd_connect_genl_ops[] = {
/* this struct was pre-existing, but we add: */
        {
                .cmd    = NBD_CMD_GET_FEATURES,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit   = nbd_genl_features,
        },
};

/* nbd later on initializes (this was not touched by me): */
static struct genl_family nbd_genl_family __ro_after_init = {
        .hdrsize        = 0,
        .name           = NBD_GENL_FAMILY_NAME,
        .version        = NBD_GENL_VERSION,
        .module         = THIS_MODULE,
        .small_ops      = nbd_connect_genl_ops,
        .n_small_ops    = ARRAY_SIZE(nbd_connect_genl_ops),
        .resv_start_op  = NBD_CMD_GET_FEATURES + 1,
        .maxattr        = NBD_ATTR_MAX,
        .netnsok        = 1,
        .policy = nbd_attr_policy,
        .mcgrps         = nbd_mcast_grps,
        .n_mcgrps       = ARRAY_SIZE(nbd_mcast_grps),
};      
/* which is registered with */
if(genl_register_family(&nbd_genl_family)) { /* error handling */ }

/* and then I added the function to handle this new message */
static int nbd_genl_features(struct sk_buff *skb, struct genl_info *info)
{       
        struct sk_buff *repl;
        void *msg_head;
        int ret;
        
        repl = genlmsg_new(nla_total_size(sizeof(u32)), GFP_KERNEL);
        if(!skb)
                return -EMSGSIZE;
        msg_head = genlmsg_put_reply(repl, info, &nbd_genl_family, 0,
                                     NBD_CMD_GET_FEATURES);
        if(!msg_head) { 
                nlmsg_free(skb);
                return -EMSGSIZE;
        }
        ret = nla_put_flag(skb, NBD_FEATURE_STRUCTURED_REPLIES);
        if(ret) {       
                nlmsg_free(repl);
                return -ENOMEM;
        }
        genlmsg_end(repl, msg_head);
        return genlmsg_reply(repl, info);
}

In user space, I then use libnl and libnl-genl to do this:

static struct nl_sock *get_nbd_socket(int *driver_id) {
        struct nl_sock *socket;

        socket = nl_socket_alloc();
        if (!socket)
                err("Couldn't allocate netlink socket\n");
        
        if (genl_connect(socket))
                err("Couldn't connect to the generic netlink socket\n");
        *driver_id = genl_ctrl_resolve(socket, "nbd");
        if (*driver_id < 0)
                err("Couldn't resolve the nbd netlink family, make sure the nbd module is loaded and your nbd driver supports the netlink interface.\n");
        return socket;
}

static int features_callback(struct nl_msg *msg, void *arg) {
        bool *f = (bool*)arg;
        struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
        struct nlattr *msg_attr[NBD_ATTR_MAX + 1];
        int ret;
        uint32_t feature;

        ret = nla_parse(msg_attr, NBD_FEATURE_MAX, genlmsg_attrdata(gnlh, 0),
                        genlmsg_attrlen(gnlh, 0), NULL);
        if(ret) {
                err("Invalid response from the kernel\n");
        }
        if(msg_attr[NBD_FEATURE_STRUCTURED_REPLIES]) {
                printf("Kernel supports structured replies\n");
                *f = true;
        }
        return NL_OK;
}

static void netlink_get_features(bool *can_structured) {
        int driver_id;
        struct nl_sock *socket = get_nbd_socket(&driver_id);
        struct nl_msg *msg;

        msg = nlmsg_alloc();
        if(!msg) {
                err("Couldn't allocate netlink message\n");
        }
        nl_socket_modify_cb(socket, NL_CB_VALID, NL_CB_CUSTOM, features_callback, (void*)can_structured);
        genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, 0, NBD_CMD_GET_FEATURES, 0);
        if(nl_send_sync(socket, msg) < 0) {
                // CMD_GET_FEATURES not supported by kernel, so no known features are supported
                return;
        }
        nl_socket_free(socket);
        return;
nla_put_failure:
        err("Failed to create netlink message\n");
}

The problem, however, is that the msg_attr[NBD_FEATURE_STRUCTURED_REPLIES] is always zero, whether I remove the nla_put_flag message or leave it in.

How do I figure out in user space whether this flag is set? Did I set the flag the correct way in the kernel?

Wouter Verhelst
  • 1,269
  • 1
  • 12
  • 27

0 Answers0