The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5099
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Closes #4874. Signed-off-by: Christian Brauner <[email protected]>
From d1990de61e82f6b02d7a3324b0a08221f39177e0 Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Wed, 3 Oct 2018 23:28:26 +0200 Subject: [PATCH] lxd: support uevent injection for USB devices Closes #4874. Signed-off-by: Christian Brauner <[email protected]> --- lxd/daemon.go | 8 ++ lxd/devices.go | 58 +++++++---- lxd/main.go | 4 + lxd/main_checkfeature.go | 23 ++++- lxd/main_forkuevent.go | 214 +++++++++++++++++++++++++++++++++++++++ lxd/main_nsexec.go | 3 + lxd/sys/os.go | 1 + 7 files changed, 292 insertions(+), 19 deletions(-) create mode 100644 lxd/main_forkuevent.go diff --git a/lxd/daemon.go b/lxd/daemon.go index 2a94c65cf9..2219ee0054 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -439,6 +439,14 @@ func (d *Daemon) init() error { logger.Debugf("Running kernel does not support netnsid-based network retrieval") } + /* Check if uevent injection is supported. */ + d.os.UeventInjection = CanUseUeventInjection() + if d.os.UeventInjection { + logger.Debugf("Running kernel supports uevent injection") + } else { + logger.Debugf("Running kernel does not support uevent injection") + } + /* Initialize the database */ dump, err := initializeDbObject(d) if err != nil { diff --git a/lxd/devices.go b/lxd/devices.go index 82540e3303..4296934d47 100644 --- a/lxd/devices.go +++ b/lxd/devices.go @@ -53,9 +53,11 @@ type usbDevice struct { vendor string product string - path string - major int - minor int + path string + major int + minor int + ueventParts []string + ueventLen int } // /dev/nvidia[0-9]+ @@ -378,7 +380,7 @@ func deviceLoadGpu(all bool) ([]gpuDevice, []nvidiaGpuDevices, error) { return gpus, nvidiaDevices, nil } -func createUSBDevice(action string, vendor string, product string, major string, minor string, busnum string, devnum string, devname string) (usbDevice, error) { +func createUSBDevice(action string, vendor string, product string, major string, minor string, busnum string, devnum string, devname string, ueventParts []string, ueventLen int) (usbDevice, error) { majorInt, err := strconv.Atoi(major) if err != nil { return usbDevice{}, err @@ -414,6 +416,8 @@ func createUSBDevice(action string, vendor string, product string, major string, path, majorInt, minorInt, + ueventParts, + ueventLen, }, nil } @@ -448,28 +452,29 @@ func deviceNetlinkListener() (chan []string, chan []string, chan usbDevice, erro go func(chCPU chan []string, chNetwork chan []string, chUSB chan usbDevice) { b := make([]byte, UEVENT_BUFFER_SIZE*2) for { - _, err := syscall.Read(fd, b) + r, err := syscall.Read(fd, b) if err != nil { continue } + ueventBuf := make([]byte, r) + copy(ueventBuf, b) + ueventLen := 0 + ueventParts := strings.Split(string(ueventBuf), "\x00") props := map[string]string{} - last := 0 - for i, e := range b { - if i == len(b) || e == 0 { - msg := string(b[last+1 : i]) - last = i - if len(msg) == 0 || msg == "\x00" { - continue - } + for _, part := range ueventParts { + if strings.HasPrefix(part, "SEQNUM=") { + continue + } - fields := strings.SplitN(msg, "=", 2) - if len(fields) != 2 { - continue - } + ueventLen += len(part) + 1 - props[fields[0]] = fields[1] + fields := strings.SplitN(part, "=", 2) + if len(fields) != 2 { + continue } + + props[fields[0]] = fields[1] } if props["SUBSYSTEM"] == "cpu" { @@ -554,6 +559,8 @@ func deviceNetlinkListener() (chan []string, chan []string, chan usbDevice, erro busnum, devnum, devname, + ueventParts[:len(ueventParts)-1], + ueventLen, ) if err != nil { logger.Error("Error reading usb device", log.Ctx{"err": err, "path": props["PHYSDEVPATH"]}) @@ -851,18 +858,31 @@ func deviceUSBEvent(s *state.State, usb usbDevice) { continue } + ueventArray := make([]string, 4) + ueventArray[0] = "forkuevent" + ueventArray[1] = "inject" + ueventArray[2] = fmt.Sprintf("%d", c.InitPID()) + ueventArray[3] = fmt.Sprintf("%d", usb.ueventLen) + ueventArray = append(ueventArray, usb.ueventParts...) + if usb.action == "add" { err := c.insertUnixDeviceNum(fmt.Sprintf("unix.%s", name), m, usb.major, usb.minor, usb.path, false) if err != nil { logger.Error("Failed to create usb device", log.Ctx{"err": err, "usb": usb, "container": c.Name()}) return } + shared.RunCommand(s.OS.ExecPath, ueventArray...) } else if usb.action == "remove" { err := c.removeUnixDeviceNum(fmt.Sprintf("unix.%s", name), m, usb.major, usb.minor, usb.path) if err != nil { logger.Error("Failed to remove usb device", log.Ctx{"err": err, "usb": usb, "container": c.Name()}) return } + shared.RunCommand(s.OS.ExecPath, ueventArray...) + } else if usb.action == "bind" { + shared.RunCommand(s.OS.ExecPath, ueventArray...) + } else if usb.action == "change" { + shared.RunCommand(s.OS.ExecPath, ueventArray...) } else { logger.Error("Unknown action for usb device", log.Ctx{"usb": usb}) continue @@ -1392,6 +1412,8 @@ func deviceLoadUsb() ([]usbDevice, error) { values["busnum"], values["devnum"], values["devname"], + []string{}, + 0, ) if err != nil { if os.IsNotExist(err) { diff --git a/lxd/main.go b/lxd/main.go index e4ccec708c..49b0f6520b 100644 --- a/lxd/main.go +++ b/lxd/main.go @@ -124,6 +124,10 @@ func main() { forkstartCmd := cmdForkstart{global: &globalCmd} app.AddCommand(forkstartCmd.Command()) + // forkuevent sub-command + forkueventCmd := cmdForkuevent{global: &globalCmd} + app.AddCommand(forkueventCmd.Command()) + // import sub-command importCmd := cmdImport{global: &globalCmd} app.AddCommand(importCmd.Command()) diff --git a/lxd/main_checkfeature.go b/lxd/main_checkfeature.go index 9b7b3d443b..ea4aa8e0c2 100644 --- a/lxd/main_checkfeature.go +++ b/lxd/main_checkfeature.go @@ -22,8 +22,11 @@ import ( #include "../shared/netns_getifaddrs.c" bool netnsid_aware = false; +bool uevent_aware = false; char errbuf[4096]; +extern int inject_uevent(const char *uevent, size_t len); + static int netns_set_nsid(int fd) { int sockfd, ret; @@ -64,7 +67,8 @@ static int netns_set_nsid(int fd) return 0; } -void checkfeature() { +void is_netnsid_aware() +{ int netnsid, ret; struct netns_ifaddrs *ifaddrs; int hostnetns_fd = -1, newnetns_fd = -1; @@ -118,6 +122,19 @@ on_error: close(newnetns_fd); } +void is_uevent_aware() +{ + if (inject_uevent("dummy", 6) < 0) + _exit(1); + + uevent_aware = true; +} + +void checkfeature() { + is_netnsid_aware(); + is_uevent_aware(); +} + static bool is_empty_string(char *s) { return (errbuf[0] == '\0'); @@ -133,3 +150,7 @@ func CanUseNetnsGetifaddrs() bool { return bool(C.netnsid_aware) } + +func CanUseUeventInjection() bool { + return bool(C.uevent_aware) +} diff --git a/lxd/main_forkuevent.go b/lxd/main_forkuevent.go new file mode 100644 index 0000000000..0710264013 --- /dev/null +++ b/lxd/main_forkuevent.go @@ -0,0 +1,214 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +/* + +#define _GNU_SOURCE +#include <asm/types.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <sched.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "../shared/network.c" + +#ifndef UEVENT_SEND +#define UEVENT_SEND 16 +#endif + +extern char *advance_arg(bool required); +extern void attach_userns(int pid); +extern int dosetns(int pid, char *nstype); + +struct nlmsg { + struct nlmsghdr *nlmsghdr; + ssize_t cap; +}; + +static struct nlmsg *nlmsg_alloc(size_t size) +{ + struct nlmsg *nlmsg; + size_t len = NLMSG_HDRLEN + NLMSG_ALIGN(size); + + nlmsg = (struct nlmsg *)malloc(sizeof(struct nlmsg)); + if (!nlmsg) + return NULL; + + nlmsg->nlmsghdr = (struct nlmsghdr *)malloc(len); + if (!nlmsg->nlmsghdr) + goto errout; + + memset(nlmsg->nlmsghdr, 0, len); + nlmsg->cap = len; + nlmsg->nlmsghdr->nlmsg_len = NLMSG_HDRLEN; + + return nlmsg; +errout: + free(nlmsg); + return NULL; +} + +static void *nlmsg_reserve_unaligned(struct nlmsg *nlmsg, size_t len) +{ + char *buf; + size_t nlmsg_len = nlmsg->nlmsghdr->nlmsg_len; + size_t tlen = len; + + if ((ssize_t)(nlmsg_len + tlen) > nlmsg->cap) + return NULL; + + buf = ((char *)(nlmsg->nlmsghdr)) + nlmsg_len; + nlmsg->nlmsghdr->nlmsg_len += tlen; + + if (tlen > len) + memset(buf + len, 0, tlen - len); + + return buf; +} + +int inject_uevent(const char *uevent, size_t len) +{ + int ret, sock_fd; + char *umsg = NULL; + struct nlmsg *nlmsg = NULL; + + sock_fd = netlink_open(NETLINK_KOBJECT_UEVENT); + if (sock_fd < 0) { + return -1; + } + + nlmsg = nlmsg_alloc(len); + if (!nlmsg) { + ret = -1; + goto on_error; + } + + nlmsg->nlmsghdr->nlmsg_flags = NLM_F_ACK | NLM_F_REQUEST; + nlmsg->nlmsghdr->nlmsg_type = UEVENT_SEND; + nlmsg->nlmsghdr->nlmsg_pid = 0; + + umsg = nlmsg_reserve_unaligned(nlmsg, len); + if (!umsg) { + ret = -1; + goto on_error; + } + + memcpy(umsg, uevent, len); + + ret = netlink_transaction(sock_fd, nlmsg->nlmsghdr, nlmsg->nlmsghdr); + if (ret < 0) { + ret = -1; + goto on_error; + } + + ret = 0; + +on_error: + close(sock_fd); + free(nlmsg); + return ret; +} + +void forkuevent() { + char *uevent = NULL; + char *cur = NULL; + pid_t pid = 0; + size_t len = 0; + + cur = advance_arg(false); + if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) { + fprintf(stderr, "Error: Missing PID\n"); + _exit(1); + } + + // Get the pid + cur = advance_arg(false); + if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) { + fprintf(stderr, "Error: Missing PID\n"); + _exit(1); + } + pid = atoi(cur); + + // Get the size + cur = advance_arg(false); + if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) { + fprintf(stderr, "Error: Missing uevent length\n"); + _exit(1); + } + len = atoi(cur); + + // Get the uevent + cur = advance_arg(false); + if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) { + fprintf(stderr, "Error: Missing uevent\n"); + _exit(1); + } + uevent = cur; + + // Check that we're root + if (geteuid() != 0) { + fprintf(stderr, "Error: forkuevent requires root privileges\n"); + _exit(1); + } + + attach_userns(pid); + + if (dosetns(pid, "net") < 0) { + fprintf(stderr, "Failed to setns to container network namespace: %s\n", strerror(errno)); + _exit(1); + } + + if (inject_uevent(uevent, len) < 0) { + fprintf(stderr, "Failed to inject uevent\n"); + _exit(1); + } +} +*/ +// #cgo CFLAGS: -std=gnu11 -Wvla +import "C" + +type cmdForkuevent struct { + global *cmdGlobal +} + +func (c *cmdForkuevent) Command() *cobra.Command { + // Main subcommand + cmd := &cobra.Command{} + cmd.Use = "forkuevent" + cmd.Short = "Inject uevents into container's network namespace" + cmd.Long = `Description: + Inject uevent into a container's network namespace + + This internal command is used to inject uevents into unprivileged container's + network namespaces. +` + cmd.Hidden = true + + // pull + cmdInject := &cobra.Command{} + cmdInject.Use = "inject <PID> <len> <uevent>" + cmdInject.Args = cobra.ExactArgs(3) + cmdInject.RunE = c.Run + cmd.AddCommand(cmdInject) + + return cmd +} + +func (c *cmdForkuevent) Run(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go index 56fbf2fd46..c03a5d24f0 100644 --- a/lxd/main_nsexec.go +++ b/lxd/main_nsexec.go @@ -39,6 +39,7 @@ extern void forkfile(); extern void forkmount(); extern void forknet(); extern void forkproxy(); +extern void forkuevent(); // Command line parsing and tracking #define CMDLINE_SIZE (8 * PATH_MAX) @@ -246,6 +247,8 @@ __attribute__((constructor)) void init(void) { forknet(); else if (strcmp(cmdline_cur, "forkproxy") == 0) forkproxy(); + else if (strcmp(cmdline_cur, "forkuevent") == 0) + forkuevent(); else if (strncmp(cmdline_cur, "-", 1) == 0 || strcmp(cmdline_cur, "daemon") == 0) checkfeature(); } diff --git a/lxd/sys/os.go b/lxd/sys/os.go index 9e93da327a..3d28f0e7a9 100644 --- a/lxd/sys/os.go +++ b/lxd/sys/os.go @@ -59,6 +59,7 @@ type OS struct { CGroupSwapAccounting bool InotifyWatch InotifyInfo NetnsGetifaddrs bool + UeventInjection bool MockMode bool // If true some APIs will be mocked (for testing) }
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
