From 52af6f30a0efc4990ea2302e2467851af3ffd61d Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones <rjones@redhat.com>
Date: Mon, 18 Jun 2012 16:06:34 +0100
Subject: [PATCH 14/35] EPEL 5: Add "null vmchannel" back for qemu without virtio-serial support.
---
appliance/init | 4 +-
daemon/guestfsd.c | 99 ++++++++++++++++++++++--
src/guestfs-internal.h | 2 +-
src/launch-appliance.c | 196 ++++++++++++++++++++++++++++++++++--------------
src/launch.c | 4 +-
5 files changed, 236 insertions(+), 69 deletions(-)
diff --git a/appliance/init b/appliance/init
index 290d6ac..20c8870 100755
--- a/appliance/init
+++ b/appliance/init
@@ -80,10 +80,10 @@ hwclock -u -s
ip addr add 127.0.0.1/8 brd + dev lo scope host
ip link set dev lo up
-ip addr add 169.254.2.10/16 brd + dev eth0 scope global
+ip addr add 10.0.2.0/24 brd + dev eth0 scope global
ip link set dev eth0 up
-ip route add default via 169.254.2.2
+ip route add default via 10.0.2.2
# Scan for MDs.
mdadm -As --auto=yes --run
diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c
index 254e0ea..bcb836a 100644
--- a/daemon/guestfsd.c
+++ b/daemon/guestfsd.c
@@ -118,9 +118,6 @@ int autosync_umount = 1;
/* Not used explicitly, but required by the gnulib 'error' module. */
const char *program_name = "guestfsd";
-/* Name of the virtio-serial channel. */
-#define VIRTIO_SERIAL_CHANNEL "/dev/virtio-ports/org.libguestfs.channel.0"
-
static void
usage (void)
{
@@ -139,6 +136,7 @@ main (int argc, char *argv[])
};
int c;
char *cmdline;
+ char *vmchannel = NULL;
ignore_value (chdir ("/"));
@@ -210,8 +208,6 @@ main (int argc, char *argv[])
printf ("could not read linux command line\n");
}
- free (cmdline);
-
#ifndef WIN32
/* Make sure SIGPIPE doesn't kill us. */
struct sigaction sa;
@@ -253,9 +249,94 @@ main (int argc, char *argv[])
*/
copy_lvm ();
- /* Connect to virtio-serial channel. */
- int sock = open (VIRTIO_SERIAL_CHANNEL, O_RDWR|O_CLOEXEC);
+ /* Get the vmchannel string.
+ *
+ * Sources:
+ * --channel/-c option on the command line
+ * guestfs_vmchannel=... from the kernel command line
+ * guestfs=... from the kernel command line
+ * built-in default
+ *
+ * At the moment we expect this to contain "tcp:ip:port" but in
+ * future it might contain a device name, eg. "/dev/vcon4" for
+ * virtio-console vmchannel.
+ */
+ if (cmdline) {
+ char *p;
+ size_t len;
+
+ p = strstr (cmdline, "guestfs_vmchannel=");
+ if (p) {
+ len = strcspn (p + 18, " \t\n");
+ vmchannel = strndup (p + 18, len);
+ if (!vmchannel) {
+ perror ("strndup");
+ exit (EXIT_FAILURE);
+ }
+ }
+ }
+
+ /* Default vmchannel. */
+ if (vmchannel == NULL)
+ goto no_vmchannel;
+
+ if (verbose)
+ printf ("vmchannel: %s\n", vmchannel);
+
+ /* Connect to vmchannel. */
+ int sock = -1;
+
+ if (STREQLEN (vmchannel, "tcp:", 4)) {
+ /* Resolve the hostname. */
+ struct addrinfo *res, *rr;
+ struct addrinfo hints;
+ int r;
+ char *host, *port;
+
+ host = vmchannel+4;
+ port = strchr (host, ':');
+ if (port) {
+ port[0] = '\0';
+ port++;
+ } else {
+ fprintf (stderr, "vmchannel: expecting \"tcp:<ip>:<port>\": %s\n",
+ vmchannel);
+ exit (EXIT_FAILURE);
+ }
+
+ memset (&hints, 0, sizeof hints);
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+ r = getaddrinfo (host, port, &hints, &res);
+ if (r != 0) {
+ fprintf (stderr, "%s:%s: %s\n",
+ host, port, gai_strerror (r));
+ exit (EXIT_FAILURE);
+ }
+
+ /* Connect to the given TCP socket. */
+ for (rr = res; rr != NULL; rr = rr->ai_next) {
+ sock = socket (rr->ai_family, rr->ai_socktype, rr->ai_protocol);
+ if (sock != -1) {
+ if (connect (sock, rr->ai_addr, rr->ai_addrlen) == 0)
+ break;
+ perror ("connect");
+
+ close (sock);
+ sock = -1;
+ }
+ }
+ freeaddrinfo (res);
+ } else {
+ fprintf (stderr,
+ "unknown vmchannel connection type: %s\n"
+ "expecting \"tcp:<ip>:<port>\"\n",
+ vmchannel);
+ exit (EXIT_FAILURE);
+ }
+
if (sock == -1) {
+ no_vmchannel:
fprintf (stderr,
"\n"
"Failed to connect to virtio-serial channel.\n"
@@ -269,7 +350,7 @@ main (int argc, char *argv[])
"output to the libguestfs developers, either in a bug report\n"
"or on the libguestfs redhat com mailing list.\n"
"\n");
- perror (VIRTIO_SERIAL_CHANNEL);
+ perror ("last error");
exit (EXIT_FAILURE);
}
@@ -300,6 +381,8 @@ main (int argc, char *argv[])
xdr_destroy (&xdr);
+ free (cmdline);
+
/* Enter the main loop, reading and performing actions. */
main_loop (sock);
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index aaa9d41..cb812ed 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -516,7 +516,7 @@ extern int64_t guestfs___timeval_diff (const struct timeval *x, const struct tim
extern void guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3)));
extern void guestfs___launch_send_progress (guestfs_h *g, int perdozen);
extern void guestfs___launch_failed_error (guestfs_h *g);
-extern char *guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev, int flags);
+extern char *guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev, int flags, const char *vmchannel);
#define APPLIANCE_COMMAND_LINE_IS_TCG 1
/* launch-appliance.c */
diff --git a/src/launch-appliance.c b/src/launch-appliance.c
index 5de3b0a..71d7aa6 100644
--- a/src/launch-appliance.c
+++ b/src/launch-appliance.c
@@ -74,12 +74,16 @@ free_regexps (void)
pcre_free (re_major_minor);
}
+#define NETWORK "10.0.2.0/24"
+#define ROUTER "10.0.2.2"
+
static int is_openable (guestfs_h *g, const char *path, int flags);
static void print_qemu_command_line (guestfs_h *g, char **argv);
static int qemu_supports (guestfs_h *g, const char *option);
static int qemu_supports_device (guestfs_h *g, const char *device_name);
static int qemu_supports_virtio_scsi (guestfs_h *g);
static char *qemu_drive_param (guestfs_h *g, const struct drive *drv, size_t index);
+static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn);
/* Functions to build up the qemu command line. These are only run
* in the child process so no clean-up is required.
@@ -169,7 +173,9 @@ launch_appliance (guestfs_h *g, const char *arg)
int r;
int wfd[2], rfd[2];
char guestfsd_sock[256];
- struct sockaddr_un addr;
+ struct sockaddr_in addr;
+ socklen_t addrlen = sizeof addr;
+ int null_vmchannel_port;
CLEANUP_FREE char *kernel = NULL, *initrd = NULL, *appliance = NULL;
int has_appliance_drive;
uint32_t size;
@@ -203,37 +209,43 @@ launch_appliance (guestfs_h *g, const char *arg)
if (qemu_supports (g, NULL) == -1)
goto cleanup0;
- /* Using virtio-serial, we need to create a local Unix domain socket
- * for qemu to connect to.
+ /* "Null vmchannel" implementation: We allocate a random port
+ * number on the host, and the daemon connects back to it. To
+ * make this secure, we check that the peer UID is the same as our
+ * UID. This requires SLIRP (user mode networking in qemu).
*/
- snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir);
- unlink (guestfsd_sock);
-
- g->sock = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ g->sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (g->sock == -1) {
perrorf (g, "socket");
goto cleanup0;
}
- if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) {
- perrorf (g, "fcntl");
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons (0);
+ addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ if (bind (g->sock, (struct sockaddr *) &addr, addrlen) == -1) {
+ perrorf (g, "bind");
goto cleanup0;
}
- addr.sun_family = AF_UNIX;
- strncpy (addr.sun_path, guestfsd_sock, UNIX_PATH_MAX);
- addr.sun_path[UNIX_PATH_MAX-1] = '\0';
+ if (listen (g->sock, 256) == -1) {
+ perrorf (g, "listen");
+ goto cleanup0;
+ }
- if (bind (g->sock, &addr, sizeof addr) == -1) {
- perrorf (g, "bind");
+ if (getsockname (g->sock, (struct sockaddr *) &addr, &addrlen) == -1) {
+ perrorf (g, "getsockname");
goto cleanup0;
}
- if (listen (g->sock, 1) == -1) {
- perrorf (g, "listen");
+ if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) {
+ perrorf (g, "fcntl");
goto cleanup0;
}
+ null_vmchannel_port = ntohs (addr.sin_port);
+ debug (g, "null_vmchannel_port = %d", null_vmchannel_port);
+
if (!g->direct) {
if (pipe (wfd) == -1 || pipe (rfd) == -1) {
perrorf (g, "pipe");
@@ -398,23 +410,9 @@ launch_appliance (guestfs_h *g, const char *arg)
if (qemu_supports (g, "-rtc-td-hack"))
add_cmdline (g, "-rtc-td-hack");
- /* Create the virtio serial bus. */
- add_cmdline (g, "-device");
- add_cmdline (g, "virtio-serial");
-
-#if 0
- /* Use virtio-console (a variant form of virtio-serial) for the
- * guest's serial console.
- */
- add_cmdline (g, "-chardev");
- add_cmdline (g, "stdio,id=console");
- add_cmdline (g, "-device");
- add_cmdline (g, "virtconsole,chardev=console,name=org.libguestfs.console.0");
-#else
- /* When the above works ... until then: */
+ /* Serial console. */
add_cmdline (g, "-serial");
add_cmdline (g, "stdio");
-#endif
if (qemu_supports_device (g, "Serial Graphics Adapter")) {
/* Use sgabios instead of vgabios. This means we'll see BIOS
@@ -427,12 +425,16 @@ launch_appliance (guestfs_h *g, const char *arg)
add_cmdline (g, "sga");
}
- /* Set up virtio-serial for the communications channel. */
- add_cmdline (g, "-chardev");
- snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", guestfsd_sock);
- add_cmdline (g, buf);
- add_cmdline (g, "-device");
- add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0");
+ /* Null vmchannel. */
+ add_cmdline (g, "-net");
+ add_cmdline (g, "user,vlan=0,net=" NETWORK);
+ add_cmdline (g, "-net");
+ add_cmdline (g, "nic,model=virtio,vlan=0");
+
+ snprintf (buf, sizeof buf,
+ "guestfs_vmchannel=tcp:" ROUTER ":%d",
+ null_vmchannel_port);
+ char *vmchannel = strdup (buf);
#ifdef VALGRIND_DAEMON
/* Set up virtio-serial channel for valgrind messages. */
@@ -444,14 +446,6 @@ launch_appliance (guestfs_h *g, const char *arg)
add_cmdline (g, "virtserialport,chardev=valgrind,name=org.libguestfs.valgrind");
#endif
- /* Enable user networking. */
- if (g->enable_network) {
- add_cmdline (g, "-netdev");
- add_cmdline (g, "user,id=usernet,net=169.254.0.0/16");
- add_cmdline (g, "-device");
- add_cmdline (g, "virtio-net-pci,netdev=usernet");
- }
-
add_cmdline (g, "-kernel");
add_cmdline (g, kernel);
add_cmdline (g, "-initrd");
@@ -459,7 +453,7 @@ launch_appliance (guestfs_h *g, const char *arg)
add_cmdline (g, "-append");
CLEANUP_FREE char *cmdline =
- guestfs___appliance_command_line (g, appliance_dev, 0);
+ guestfs___appliance_command_line (g, appliance_dev, 0, vmchannel);
add_cmdline (g, cmdline);
/* Note: custom command line parameters must come last so that
@@ -620,19 +614,30 @@ launch_appliance (guestfs_h *g, const char *arg)
g->state = LAUNCHING;
- /* Wait for qemu to start and to connect back to us via
- * virtio-serial and send the GUESTFS_LAUNCH_FLAG message.
+ /* Null vmchannel implementation: We listen on g->sock for a
+ * connection. The connection could come from any local process
+ * so we must check it comes from the appliance (or at least
+ * from our UID) for security reasons.
*/
- r = guestfs___accept_from_daemon (g);
- if (r == -1)
- goto cleanup1;
+ r = -1;
+ while (r == -1) {
+ uid_t uid;
- /* NB: We reach here just because qemu has opened the socket. It
- * does not mean the daemon is up until we read the
- * GUESTFS_LAUNCH_FLAG below. Failures in qemu startup can still
- * happen even if we reach here, even early failures like not being
- * able to open a drive.
- */
+ r = guestfs___accept_from_daemon (g);
+ if (r == -1)
+ goto cleanup1;
+
+ if (check_peer_euid (g, r, &uid) == -1)
+ goto cleanup1;
+ if (uid != geteuid ()) {
+ fprintf (stderr,
+ "libguestfs: warning: unexpected connection from UID %d to port %d\n",
+ uid, null_vmchannel_port);
+ close (r);
+ r = -1;
+ continue;
+ }
+ }
/* Close the listening socket. */
if (close (g->sock) != 0) {
@@ -987,6 +992,83 @@ guestfs___drive_name (size_t index, char *ret)
return ret;
}
+/* Check the peer effective UID for a TCP socket. Ideally we'd like
+ * SO_PEERCRED for a loopback TCP socket. This isn't possible on
+ * Linux (but it is on Solaris!) so we read /proc/net/tcp instead.
+ */
+static int
+check_peer_euid (guestfs_h *g, int sock, uid_t *rtn)
+{
+ struct sockaddr_in peer;
+ socklen_t addrlen = sizeof peer;
+
+ if (getpeername (sock, (struct sockaddr *) &peer, &addrlen) == -1) {
+ perrorf (g, "getpeername");
+ return -1;
+ }
+
+ if (peer.sin_family != AF_INET ||
+ ntohl (peer.sin_addr.s_addr) != INADDR_LOOPBACK) {
+ error (g, "check_peer_euid: unexpected connection from non-IPv4, non-loopback peer (family = %d, addr = %s)",
+ peer.sin_family, inet_ntoa (peer.sin_addr));
+ return -1;
+ }
+
+ struct sockaddr_in our;
+ addrlen = sizeof our;
+ if (getsockname (sock, (struct sockaddr *) &our, &addrlen) == -1) {
+ perrorf (g, "getsockname");
+ return -1;
+ }
+
+ FILE *fp = fopen ("/proc/net/tcp", "r");
+ if (fp == NULL) {
+ perrorf (g, "/proc/net/tcp");
+ return -1;
+ }
+
+ char line[256];
+ if (fgets (line, sizeof line, fp) == NULL) { /* Drop first line. */
+ error (g, "unexpected end of file in /proc/net/tcp");
+ fclose (fp);
+ return -1;
+ }
+
+ while (fgets (line, sizeof line, fp) != NULL) {
+ unsigned line_our_addr, line_our_port, line_peer_addr, line_peer_port;
+ int dummy0, dummy1, dummy2, dummy3, dummy4, dummy5, dummy6;
+ int line_uid;
+
+ if (sscanf (line, "%d:%08X:%04X %08X:%04X %02X %08X:%08X %02X:%08X %08X %d",
+ &dummy0,
+ &line_our_addr, &line_our_port,
+ &line_peer_addr, &line_peer_port,
+ &dummy1, &dummy2, &dummy3, &dummy4, &dummy5, &dummy6,
+ &line_uid) == 12) {
+ /* Note about /proc/net/tcp: local_address and rem_address are
+ * always in network byte order. However the port part is
+ * always in host byte order.
+ *
+ * The sockname and peername that we got above are in network
+ * byte order. So we have to byte swap the port but not the
+ * address part.
+ */
+ if (line_our_addr == our.sin_addr.s_addr &&
+ line_our_port == ntohs (our.sin_port) &&
+ line_peer_addr == peer.sin_addr.s_addr &&
+ line_peer_port == ntohs (peer.sin_port)) {
+ *rtn = line_uid;
+ fclose (fp);
+ return 0;
+ }
+ }
+ }
+
+ error (g, "check_peer_euid: no matching TCP connection found in /proc/net/tcp");
+ fclose (fp);
+ return -1;
+}
+
static int
shutdown_appliance (guestfs_h *g, int check_for_errors)
{
diff --git a/src/launch.c b/src/launch.c
index 4333821..c77c229 100644
--- a/src/launch.c
+++ b/src/launch.c
@@ -316,7 +316,7 @@ guestfs__config (guestfs_h *g,
char *
guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev,
- int flags)
+ int flags, const char *vmchannel)
{
char *term = getenv ("TERM");
char *ret;
@@ -344,12 +344,14 @@ guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev,
" cgroup_disable=memory" /* saves us about 5 MB of RAM */
" root=%s" /* root (appliance_dev) */
" %s" /* selinux */
+ " %s" /* vmchannel */
"%s" /* verbose */
" TERM=%s" /* TERM environment variable */
"%s%s", /* append */
lpj_s,
appliance_dev,
g->selinux ? "selinux=1 enforcing=0" : "selinux=0",
+ vmchannel,
g->verbose ? " guestfs_verbose=1" : "",
term ? term : "linux",
g->append ? " " : "", g->append ? g->append : "");
--
1.7.4.1