#include <sys/ioctl.h>
#include <sys/socket.h>
+#include "nbd-netlink.h"
+#include <libnl3/netlink/genl/genl.h>
+#include <libnl3/netlink/genl/ctrl.h>
+#include <libnl3/netlink/genl/mngt.h>
+
#include <fstream>
#include <iostream>
#include <memory>
bool exclusive = false;
bool readonly = false;
bool set_max_part = false;
+ bool try_netlink = false;
std::string poolname;
std::string nsname;
<< " --max_part <limit> Override for module param max_part\n"
<< " --exclusive Forbid writes by other clients\n"
<< " --timeout <seconds> Set nbd request timeout\n"
+ << " --try-netlink Use the nbd netlink interface\n"
<< "\n"
<< "List options:\n"
<< " --format plain|json|xml Output format (default: plain)\n"
}
static int nbd = -1;
+static int nbd_index = -1;
enum Command {
None,
return index;
}
-static int try_ioctl_setup(Config *cfg, int fd, uint64_t size, int *nbd_index,
- uint64_t flags)
+static int try_ioctl_setup(Config *cfg, int fd, uint64_t size, uint64_t flags)
{
int index = 0, r;
}
}
- *nbd_index = index;
+ dout(10) << "ioctl setup complete for " << cfg->devpath << dendl;
+ nbd_index = index;
return 0;
close_nbd:
return r;
}
+static void netlink_cleanup(struct nl_sock *sock)
+{
+ if (!sock)
+ return;
+
+ nl_close(sock);
+ nl_socket_free(sock);
+}
+
+static struct nl_sock *netlink_init(int *id)
+{
+ struct nl_sock *sock;
+ int ret;
+
+ sock = nl_socket_alloc();
+ if (!sock) {
+ cerr << "rbd-nbd: Could not allocate netlink socket." << std::endl;
+ return NULL;
+ }
+
+ ret = genl_connect(sock);
+ if (ret < 0) {
+ cerr << "rbd-nbd: Could not connect netlink socket. Error " << ret
+ << std::endl;
+ goto free_sock;
+ }
+
+ *id = genl_ctrl_resolve(sock, "nbd");
+ if (*id < 0)
+ // nbd netlink interface not supported.
+ goto close_sock;
+
+ return sock;
+
+close_sock:
+ nl_close(sock);
+free_sock:
+ nl_socket_free(sock);
+ return NULL;
+}
+
+static int netlink_disconnect(int index)
+{
+ struct nl_sock *sock;
+ struct nl_msg *msg;
+ int ret, nl_id;
+
+ sock = netlink_init(&nl_id);
+ if (!sock)
+ // Try ioctl
+ return 1;
+
+ nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, genl_handle_msg, NULL);
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ cerr << "rbd-nbd: Could not allocate netlink message." << std::endl;
+ goto free_sock;
+ }
+
+ if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl_id, 0, 0,
+ NBD_CMD_DISCONNECT, 0)) {
+ cerr << "rbd-nbd: Could not setup message." << std::endl;
+ goto nla_put_failure;
+ }
+
+ NLA_PUT_U32(msg, NBD_ATTR_INDEX, index);
+
+ ret = nl_send_sync(sock, msg);
+ netlink_cleanup(sock);
+ if (ret < 0) {
+ cerr << "rbd-nbd: netlink disconnect failed: " << nl_geterror(-ret)
+ << std::endl;
+ return -EIO;
+ }
+
+ return 0;
+
+nla_put_failure:
+ nlmsg_free(msg);
+free_sock:
+ netlink_cleanup(sock);
+ return -EIO;
+}
+
+static int netlink_disconnect_by_path(const std::string& devpath)
+{
+ int index;
+
+ index = parse_nbd_index(devpath);
+ if (index < 0)
+ return index;
+
+ return netlink_disconnect(index);
+}
+
+static int netlink_connect_cb(struct nl_msg *msg, void *arg)
+{
+ struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+ Config *cfg = (Config *)arg;
+ struct nlattr *msg_attr[NBD_ATTR_MAX + 1];
+ uint32_t index;
+ int ret;
+
+ ret = nla_parse(msg_attr, NBD_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+ if (ret) {
+ cerr << "rbd-nbd: Unsupported netlink reply" << std::endl;
+ return -NLE_MSGTYPE_NOSUPPORT;
+ }
+
+ if (!msg_attr[NBD_ATTR_INDEX]) {
+ cerr << "rbd-nbd: netlink connect reply missing device index." << std::endl;
+ return -NLE_MSGTYPE_NOSUPPORT;
+ }
+
+ index = nla_get_u32(msg_attr[NBD_ATTR_INDEX]);
+ cfg->devpath = "/dev/nbd" + stringify(index);
+ nbd_index = index;
+
+ return NL_OK;
+}
+
+static int netlink_connect(Config *cfg, struct nl_sock *sock, int nl_id, int fd,
+ uint64_t size, uint64_t flags)
+{
+ struct nlattr *sock_attr;
+ struct nlattr *sock_opt;
+ struct nl_msg *msg;
+ int ret;
+
+ nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, netlink_connect_cb, cfg);
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ cerr << "rbd-nbd: Could not allocate netlink message." << std::endl;
+ return -ENOMEM;
+ }
+
+ if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl_id, 0, 0, NBD_CMD_CONNECT,
+ 0)) {
+ cerr << "rbd-nbd: Could not setup message." << std::endl;
+ goto free_msg;
+ }
+
+ if (!cfg->devpath.empty()) {
+ ret = parse_nbd_index(cfg->devpath);
+ if (ret < 0)
+ goto free_msg;
+
+ NLA_PUT_U32(msg, NBD_ATTR_INDEX, ret);
+ }
+
+ if (cfg->timeout >= 0)
+ NLA_PUT_U64(msg, NBD_ATTR_TIMEOUT, cfg->timeout);
+
+ NLA_PUT_U64(msg, NBD_ATTR_SIZE_BYTES, size);
+ NLA_PUT_U64(msg, NBD_ATTR_BLOCK_SIZE_BYTES, RBD_NBD_BLKSIZE);
+ NLA_PUT_U64(msg, NBD_ATTR_SERVER_FLAGS, flags);
+
+ sock_attr = nla_nest_start(msg, NBD_ATTR_SOCKETS);
+ if (!sock_attr) {
+ cerr << "rbd-nbd: Could not init sockets in netlink message." << std::endl;
+ goto free_msg;
+ }
+
+ sock_opt = nla_nest_start(msg, NBD_SOCK_ITEM);
+ if (!sock_opt) {
+ cerr << "rbd-nbd: Could not init sock in netlink message." << std::endl;
+ goto free_msg;
+ }
+
+ NLA_PUT_U32(msg, NBD_SOCK_FD, fd);
+ nla_nest_end(msg, sock_opt);
+ nla_nest_end(msg, sock_attr);
+
+ ret = nl_send_sync(sock, msg);
+ if (ret < 0) {
+ cerr << "rbd-nbd: netlink connect failed: " << nl_geterror(ret)
+ << std::endl;
+ return -EIO;
+ }
+
+ dout(10) << "netlink connect complete for " << cfg->devpath << dendl;
+ return 0;
+
+nla_put_failure:
+free_msg:
+ nlmsg_free(msg);
+ return -EIO;
+}
+
+static int try_netlink_setup(Config *cfg, int fd, uint64_t size, uint64_t flags)
+{
+ struct nl_sock *sock;
+ int nl_id, ret;
+
+ sock = netlink_init(&nl_id);
+ if (!sock) {
+ cerr << "rbd-nbd: Netlink interface not supported. Using ioctl interface."
+ << std::endl;
+ return 1;
+ }
+
+ dout(10) << "netlink interface supported." << dendl;
+
+ ret = netlink_connect(cfg, sock, nl_id, fd, size, flags);
+ netlink_cleanup(sock);
+
+ if (ret != 0)
+ return ret;
+
+ nbd = open_device(cfg->devpath.c_str());
+ if (nbd < 0) {
+ cerr << "rbd-nbd: failed to open device: " << cfg->devpath << std::endl;
+ return nbd;
+ }
+
+ return 0;
+}
+
static void handle_signal(int signum)
{
+ int ret;
+
ceph_assert(signum == SIGINT || signum == SIGTERM);
derr << "*** Got signal " << sig_str(signum) << " ***" << dendl;
+
+ if (nbd < 0 || nbd_index < 0) {
+ dout(20) << __func__ << ": " << "disconnect not needed." << dendl;
+ return;
+ }
+
dout(20) << __func__ << ": " << "sending NBD_DISCONNECT" << dendl;
- if (ioctl(nbd, NBD_DISCONNECT) < 0) {
- derr << "rbd-nbd: disconnect failed: " << cpp_strerror(errno) << dendl;
+ ret = netlink_disconnect(nbd_index);
+ if (ret == 1)
+ ret = ioctl(nbd, NBD_DISCONNECT);
+
+ if (ret != 0) {
+ derr << "rbd-nbd: disconnect failed. Error: " << ret << dendl;
} else {
dout(20) << __func__ << ": " << "disconnected" << dendl;
}
}
-static NBDServer *start_server(Preforker& forker, int fd, librbd::Image& image)
+static NBDServer *start_server(int fd, librbd::Image& image)
{
NBDServer *server;
- if (g_conf()->daemonize) {
- global_init_postfork_finish(g_ceph_context);
- forker.daemonize();
- }
-
server = new NBDServer(fd, image);
server->start();
return server;
}
-static void run_server(void)
+static void run_server(Preforker& forker, NBDServer *server, bool netlink_used)
{
- ioctl(nbd, NBD_DO_IT);
+ if (g_conf()->daemonize) {
+ global_init_postfork_finish(g_ceph_context);
+ forker.daemonize();
+ }
+
+ if (netlink_used)
+ server->wait_for_disconnect();
+ else
+ ioctl(nbd, NBD_DO_IT);
unregister_async_signal_handler(SIGHUP, sighup_handler);
unregister_async_signal_handler(SIGINT, handle_signal);
int read_only = 0;
unsigned long flags;
unsigned long size;
+ bool use_netlink;
- int nbd_index;
int fd[2];
librbd::image_info_t info;
size = info.size;
- r = try_ioctl_setup(cfg, fd[0], size, &nbd_index, flags);
- if (r < 0)
- goto close_fd;
+ server = start_server(fd[1], image);
+
+ use_netlink = cfg->try_netlink;
+ if (use_netlink) {
+ r = try_netlink_setup(cfg, fd[0], size, flags);
+ if (r < 0) {
+ goto free_server;
+ } else if (r == 1) {
+ use_netlink = false;
+ }
+ }
+
+ if (!use_netlink) {
+ r = try_ioctl_setup(cfg, fd[0], size, flags);
+ if (r < 0)
+ goto free_server;
+ }
r = check_device_size(nbd_index, size);
- if (r < 0) {
+ if (r < 0)
goto close_nbd;
- }
r = ioctl(nbd, BLKROSET, (unsigned long) &read_only);
if (r < 0) {
cout << cfg->devpath << std::endl;
- server = start_server(forker, fd[1], image);
- run_server();
+ run_server(forker, server, use_netlink);
r = image.update_unwatch(handle);
ceph_assert(r == 0);
}
- delete server;
close_nbd:
if (r < 0) {
- ioctl(nbd, NBD_CLEAR_SOCK);
- cerr << "rbd-nbd: failed to map, status: " << cpp_strerror(-r) << std::endl;
+ if (use_netlink) {
+ netlink_disconnect(nbd_index);
+ } else {
+ ioctl(nbd, NBD_CLEAR_SOCK);
+ cerr << "rbd-nbd: failed to map, status: " << cpp_strerror(-r)
+ << std::endl;
+ }
}
close(nbd);
+free_server:
+ delete server;
close_fd:
close(fd[0]);
close(fd[1]);
return r;
}
-static int do_unmap(const std::string &devpath)
+static int do_unmap(Config *cfg)
{
- int r = 0;
+ int r, nbd;
+
+ /*
+ * The netlink disconnect call supports devices setup with netlink or ioctl,
+ * so we always try that first.
+ */
+ r = netlink_disconnect_by_path(cfg->devpath);
+ if (r != 1)
+ return r;
- int nbd = open_device(devpath.c_str());
+ nbd = open_device(cfg->devpath.c_str());
if (nbd < 0) {
- cerr << "rbd-nbd: failed to open device: " << devpath << std::endl;
+ cerr << "rbd-nbd: failed to open device: " << cfg->devpath << std::endl;
return nbd;
}
r = ioctl(nbd, NBD_DISCONNECT);
if (r < 0) {
- cerr << "rbd-nbd: the device is not used" << std::endl;
+ cerr << "rbd-nbd: the device is not used" << std::endl;
}
close(nbd);
-
return r;
}
(char *)NULL)) {
} else if (ceph_argparse_flag(args, i, "--pretty-format", (char *)NULL)) {
cfg->pretty_format = true;
+ } else if (ceph_argparse_flag(args, i, "--try-netlink", (char *)NULL)) {
+ cfg->try_netlink = true;
} else {
++i;
}
return -EINVAL;
break;
case Disconnect:
- r = do_unmap(cfg.devpath);
+ r = do_unmap(&cfg);
if (r < 0)
return -EINVAL;
break;