diff --git a/configure b/configure index 9f2a7e19d3..763db24cd0 100755 --- a/configure +++ b/configure @@ -2942,6 +2942,8 @@ echo "sysconfdir=$sysconfdir" >> $config_host_mak echo "docdir=$docdir" >> $config_host_mak echo "confdir=$confdir" >> $config_host_mak echo "libexecdir=\${prefix}/libexec" >> $config_host_mak +echo "CONFIG_QEMU_SHAREDIR=\"$prefix$datasuffix\"" >> $config_host_mak +echo "CONFIG_QEMU_HELPERDIR=\"$prefix/libexec\"" >> $config_host_mak echo "ARCH=$ARCH" >> $config_host_mak if test "$debug_tcg" = "yes" ; then diff --git a/net.c b/net.c index 2b0f766408..1944539050 100644 --- a/net.c +++ b/net.c @@ -951,6 +951,10 @@ static const struct { .name = "downscript", .type = QEMU_OPT_STRING, .help = "script to shut down the interface", + }, { + .name = "helper", + .type = QEMU_OPT_STRING, + .help = "command to execute to configure bridge", }, { .name = "sndbuf", .type = QEMU_OPT_SIZE, @@ -1053,6 +1057,23 @@ static const struct { { /* end of list */ } }, }, + [NET_CLIENT_TYPE_BRIDGE] = { + .type = "bridge", + .init = net_init_bridge, + .desc = { + NET_COMMON_PARAMS_DESC, + { + .name = "br", + .type = QEMU_OPT_STRING, + .help = "bridge name", + }, { + .name = "helper", + .type = QEMU_OPT_STRING, + .help = "command to execute to configure bridge", + }, + { /* end of list */ } + }, + }, }; int net_client_init(Monitor *mon, QemuOpts *opts, int is_netdev) @@ -1075,7 +1096,8 @@ int net_client_init(Monitor *mon, QemuOpts *opts, int is_netdev) #ifdef CONFIG_VDE strcmp(type, "vde") != 0 && #endif - strcmp(type, "socket") != 0) { + strcmp(type, "socket") != 0 && + strcmp(type, "bridge") != 0) { qerror_report(QERR_INVALID_PARAMETER_VALUE, "type", "a netdev backend type"); return -1; @@ -1145,6 +1167,7 @@ static int net_host_check_device(const char *device) #ifdef CONFIG_VDE ,"vde" #endif + , "bridge" }; for (i = 0; i < sizeof(valid_param_list) / sizeof(char *); i++) { if (!strncmp(valid_param_list[i], device, diff --git a/net.h b/net.h index eab089d577..75a8c155e5 100644 --- a/net.h +++ b/net.h @@ -37,6 +37,7 @@ typedef enum { NET_CLIENT_TYPE_SOCKET, NET_CLIENT_TYPE_VDE, NET_CLIENT_TYPE_DUMP, + NET_CLIENT_TYPE_BRIDGE, NET_CLIENT_TYPE_MAX } net_client_type; @@ -174,6 +175,8 @@ int do_netdev_del(Monitor *mon, const QDict *qdict, QObject **ret_data); #define DEFAULT_NETWORK_SCRIPT "/etc/qemu-ifup" #define DEFAULT_NETWORK_DOWN_SCRIPT "/etc/qemu-ifdown" +#define DEFAULT_BRIDGE_HELPER CONFIG_QEMU_HELPERDIR "/qemu-bridge-helper" +#define DEFAULT_BRIDGE_INTERFACE "br0" void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd); diff --git a/net/tap.c b/net/tap.c index 6c27a9427f..f2400283c3 100644 --- a/net/tap.c +++ b/net/tap.c @@ -382,6 +382,171 @@ static int launch_script(const char *setup_script, const char *ifname, int fd) return -1; } +static int recv_fd(int c) +{ + int fd; + uint8_t msgbuf[CMSG_SPACE(sizeof(fd))]; + struct msghdr msg = { + .msg_control = msgbuf, + .msg_controllen = sizeof(msgbuf), + }; + struct cmsghdr *cmsg; + struct iovec iov; + uint8_t req[1]; + ssize_t len; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + msg.msg_controllen = cmsg->cmsg_len; + + iov.iov_base = req; + iov.iov_len = sizeof(req); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + len = recvmsg(c, &msg, 0); + if (len > 0) { + memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd)); + return fd; + } + + return len; +} + +static int net_bridge_run_helper(const char *helper, const char *bridge) +{ + sigset_t oldmask, mask; + int pid, status; + char *args[5]; + char **parg; + int sv[2]; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &oldmask); + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { + return -1; + } + + /* try to launch bridge helper */ + pid = fork(); + if (pid == 0) { + int open_max = sysconf(_SC_OPEN_MAX), i; + char fd_buf[6+10]; + char br_buf[6+IFNAMSIZ] = {0}; + char helper_cmd[PATH_MAX + sizeof(fd_buf) + sizeof(br_buf) + 15]; + + for (i = 0; i < open_max; i++) { + if (i != STDIN_FILENO && + i != STDOUT_FILENO && + i != STDERR_FILENO && + i != sv[1]) { + close(i); + } + } + + snprintf(fd_buf, sizeof(fd_buf), "%s%d", "--fd=", sv[1]); + + if (strrchr(helper, ' ') || strrchr(helper, '\t')) { + /* assume helper is a command */ + + if (strstr(helper, "--br=") == NULL) { + snprintf(br_buf, sizeof(br_buf), "%s%s", "--br=", bridge); + } + + snprintf(helper_cmd, sizeof(helper_cmd), "%s %s %s %s", + helper, "--use-vnet", fd_buf, br_buf); + + parg = args; + *parg++ = (char *)"sh"; + *parg++ = (char *)"-c"; + *parg++ = helper_cmd; + *parg++ = NULL; + + execv("/bin/sh", args); + } else { + /* assume helper is just the executable path name */ + + snprintf(br_buf, sizeof(br_buf), "%s%s", "--br=", bridge); + + parg = args; + *parg++ = (char *)helper; + *parg++ = (char *)"--use-vnet"; + *parg++ = fd_buf; + *parg++ = br_buf; + *parg++ = NULL; + + execv(helper, args); + } + _exit(1); + + } else if (pid > 0) { + int fd; + + close(sv[1]); + + do { + fd = recv_fd(sv[0]); + } while (fd == -1 && errno == EINTR); + + close(sv[0]); + + while (waitpid(pid, &status, 0) != pid) { + /* loop */ + } + sigprocmask(SIG_SETMASK, &oldmask, NULL); + if (fd < 0) { + fprintf(stderr, "failed to recv file descriptor\n"); + return -1; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return fd; + } + } + fprintf(stderr, "failed to launch bridge helper\n"); + return -1; +} + +int net_init_bridge(QemuOpts *opts, Monitor *mon, const char *name, + VLANState *vlan) +{ + TAPState *s; + int fd, vnet_hdr; + + if (!qemu_opt_get(opts, "br")) { + qemu_opt_set(opts, "br", DEFAULT_BRIDGE_INTERFACE); + } + if (!qemu_opt_get(opts, "helper")) { + qemu_opt_set(opts, "helper", DEFAULT_BRIDGE_HELPER); + } + + fd = net_bridge_run_helper(qemu_opt_get(opts, "helper"), + qemu_opt_get(opts, "br")); + if (fd == -1) { + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + vnet_hdr = tap_probe_vnet_hdr(fd); + + s = net_tap_fd_init(vlan, "bridge", name, fd, vnet_hdr); + if (!s) { + close(fd); + return -1; + } + + snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s,br=%s", + qemu_opt_get(opts, "helper"), qemu_opt_get(opts, "br")); + + return 0; +} + static int net_tap_init(QemuOpts *opts, int *vnet_hdr) { int fd, vnet_hdr_required; @@ -422,13 +587,16 @@ int net_init_tap(QemuOpts *opts, Monitor *mon, const char *name, VLANState *vlan { TAPState *s; int fd, vnet_hdr = 0; + const char *model; if (qemu_opt_get(opts, "fd")) { if (qemu_opt_get(opts, "ifname") || qemu_opt_get(opts, "script") || qemu_opt_get(opts, "downscript") || - qemu_opt_get(opts, "vnet_hdr")) { - error_report("ifname=, script=, downscript= and vnet_hdr= is invalid with fd="); + qemu_opt_get(opts, "vnet_hdr") || + qemu_opt_get(opts, "helper")) { + error_report("ifname=, script=, downscript=, vnet_hdr=, " + "and helper= are invalid with fd="); return -1; } @@ -440,6 +608,31 @@ int net_init_tap(QemuOpts *opts, Monitor *mon, const char *name, VLANState *vlan fcntl(fd, F_SETFL, O_NONBLOCK); vnet_hdr = tap_probe_vnet_hdr(fd); + + model = "tap"; + + } else if (qemu_opt_get(opts, "helper")) { + if (qemu_opt_get(opts, "ifname") || + qemu_opt_get(opts, "script") || + qemu_opt_get(opts, "downscript") || + qemu_opt_get(opts, "vnet_hdr")) { + error_report("ifname=, script=, downscript=, and vnet_hdr= " + "are invalid with helper="); + return -1; + } + + fd = net_bridge_run_helper(qemu_opt_get(opts, "helper"), + DEFAULT_BRIDGE_INTERFACE); + if (fd == -1) { + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + vnet_hdr = tap_probe_vnet_hdr(fd); + + model = "bridge"; + } else { if (!qemu_opt_get(opts, "script")) { qemu_opt_set(opts, "script", DEFAULT_NETWORK_SCRIPT); @@ -453,9 +646,11 @@ int net_init_tap(QemuOpts *opts, Monitor *mon, const char *name, VLANState *vlan if (fd == -1) { return -1; } + + model = "tap"; } - s = net_tap_fd_init(vlan, "tap", name, fd, vnet_hdr); + s = net_tap_fd_init(vlan, model, name, fd, vnet_hdr); if (!s) { close(fd); return -1; @@ -467,6 +662,9 @@ int net_init_tap(QemuOpts *opts, Monitor *mon, const char *name, VLANState *vlan if (qemu_opt_get(opts, "fd")) { snprintf(s->nc.info_str, sizeof(s->nc.info_str), "fd=%d", fd); + } else if (qemu_opt_get(opts, "helper")) { + snprintf(s->nc.info_str, sizeof(s->nc.info_str), + "helper=%s", qemu_opt_get(opts, "helper")); } else { const char *ifname, *script, *downscript; diff --git a/net/tap.h b/net/tap.h index e44bd2bc5f..56c591fc8e 100644 --- a/net/tap.h +++ b/net/tap.h @@ -57,4 +57,7 @@ int tap_get_fd(VLANClientState *vc); struct vhost_net; struct vhost_net *tap_get_vhost_net(VLANClientState *vc); +int net_init_bridge(QemuOpts *opts, Monitor *mon, const char *name, + VLANState *vlan); + #endif /* QEMU_NET_TAP_H */ diff --git a/qemu-options.hx b/qemu-options.hx index 3a07ae8231..19906e57e1 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1222,11 +1222,14 @@ DEF("net", HAS_ARG, QEMU_OPTION_net, "-net tap[,vlan=n][,name=str],ifname=name\n" " connect the host TAP network interface to VLAN 'n'\n" #else - "-net tap[,vlan=n][,name=str][,fd=h][,ifname=name][,script=file][,downscript=dfile][,sndbuf=nbytes][,vnet_hdr=on|off][,vhost=on|off][,vhostfd=h][,vhostforce=on|off]\n" - " connect the host TAP network interface to VLAN 'n' and use the\n" - " network scripts 'file' (default=" DEFAULT_NETWORK_SCRIPT ")\n" - " and 'dfile' (default=" DEFAULT_NETWORK_DOWN_SCRIPT ")\n" + "-net tap[,vlan=n][,name=str][,fd=h][,ifname=name][,script=file][,downscript=dfile][,helper=helper][,sndbuf=nbytes][,vnet_hdr=on|off][,vhost=on|off][,vhostfd=h][,vhostforce=on|off]\n" + " connect the host TAP network interface to VLAN 'n' \n" + " use network scripts 'file' (default=" DEFAULT_NETWORK_SCRIPT ")\n" + " to configure it and 'dfile' (default=" DEFAULT_NETWORK_DOWN_SCRIPT ")\n" + " to deconfigure it\n" " use '[down]script=no' to disable script execution\n" + " use network helper 'helper' (default=" DEFAULT_BRIDGE_HELPER ") to\n" + " configure it\n" " use 'fd=h' to connect to an already opened TAP interface\n" " use 'sndbuf=nbytes' to limit the size of the send buffer (the\n" " default is disabled 'sndbuf=0' to enable flow control set 'sndbuf=1048576')\n" @@ -1236,6 +1239,10 @@ DEF("net", HAS_ARG, QEMU_OPTION_net, " (only has effect for virtio guests which use MSIX)\n" " use vhostforce=on to force vhost on for non-MSIX virtio guests\n" " use 'vhostfd=h' to connect to an already opened vhost net device\n" + "-net bridge[,vlan=n][,name=str][,br=bridge][,helper=helper]\n" + " connects a host TAP network interface to a host bridge device 'br'\n" + " (default=" DEFAULT_BRIDGE_INTERFACE ") using the program 'helper'\n" + " (default=" DEFAULT_BRIDGE_HELPER ")\n" #endif "-net socket[,vlan=n][,name=str][,fd=h][,listen=[host]:port][,connect=host:port]\n" " connect the vlan 'n' to another VLAN using a socket connection\n" @@ -1261,6 +1268,7 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, "user|" #endif "tap|" + "bridge|" #ifdef CONFIG_VDE "vde|" #endif @@ -1397,26 +1405,65 @@ processed and applied to -net user. Mixing them with the new configuration syntax gives undefined results. Their use for new applications is discouraged as they will be removed from future versions. -@item -net tap[,vlan=@var{n}][,name=@var{name}][,fd=@var{h}][,ifname=@var{name}] [,script=@var{file}][,downscript=@var{dfile}] -Connect the host TAP network interface @var{name} to VLAN @var{n}, use -the network script @var{file} to configure it and the network script +@item -net tap[,vlan=@var{n}][,name=@var{name}][,fd=@var{h}][,ifname=@var{name}][,script=@var{file}][,downscript=@var{dfile}][,helper=@var{helper}] +Connect the host TAP network interface @var{name} to VLAN @var{n}. + +Use the network script @var{file} to configure it and the network script @var{dfile} to deconfigure it. If @var{name} is not provided, the OS -automatically provides one. @option{fd}=@var{h} can be used to specify -the handle of an already opened host TAP interface. The default network -configure script is @file{/etc/qemu-ifup} and the default network -deconfigure script is @file{/etc/qemu-ifdown}. Use @option{script=no} -or @option{downscript=no} to disable script execution. Example: +automatically provides one. The default network configure script is +@file{/etc/qemu-ifup} and the default network deconfigure script is +@file{/etc/qemu-ifdown}. Use @option{script=no} or @option{downscript=no} +to disable script execution. + +If running QEMU as an unprivileged user, use the network helper +@var{helper} to configure the TAP interface. The default network +helper executable is @file{/usr/local/libexec/qemu-bridge-helper}. + +@option{fd}=@var{h} can be used to specify the handle of an already +opened host TAP interface. + +Examples: @example +#launch a QEMU instance with the default network script qemu linux.img -net nic -net tap @end example -More complicated example (two NICs, each one connected to a TAP device) @example +#launch a QEMU instance with two NICs, each one connected +#to a TAP device qemu linux.img -net nic,vlan=0 -net tap,vlan=0,ifname=tap0 \ -net nic,vlan=1 -net tap,vlan=1,ifname=tap1 @end example +@example +#launch a QEMU instance with the default network helper to +#connect a TAP device to bridge br0 +qemu linux.img -net nic -net tap,"helper=/usr/local/libexec/qemu-bridge-helper" +@end example + +@item -net bridge[,vlan=@var{n}][,name=@var{name}][,br=@var{bridge}][,helper=@var{helper}] +Connect a host TAP network interface to a host bridge device. + +Use the network helper @var{helper} to configure the TAP interface and +attach it to the bridge. The default network helper executable is +@file{/usr/local/libexec/qemu-bridge-helper} and the default bridge +device is @file{br0}. + +Examples: + +@example +#launch a QEMU instance with the default network helper to +#connect a TAP device to bridge br0 +qemu linux.img -net bridge -net nic,model=virtio +@end example + +@example +#launch a QEMU instance with the default network helper to +#connect a TAP device to bridge qemubr0 +qemu linux.img -net bridge,br=qemubr0 -net nic,model=virtio +@end example + @item -net socket[,vlan=@var{n}][,name=@var{name}][,fd=@var{h}] [,listen=[@var{host}]:@var{port}][,connect=@var{host}:@var{port}] Connect the VLAN @var{n} to a remote VLAN in another QEMU virtual