summaryrefslogtreecommitdiffstats
path: root/src/launch.c
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2012-06-18 16:06:34 +0100
committerRichard W.M. Jones <rjones@redhat.com>2012-06-27 16:36:16 +0100
commitbc2657ab1c52ae52464769457352719ad2adb1eb (patch)
treeaecf3a12e0f318a57e048ac03481c51c36332360 /src/launch.c
parentfc178bf8f9f1d8a04e340fdb07787430e5dec6ec (diff)
downloadlibguestfs-bc2657ab1c52ae52464769457352719ad2adb1eb.tar.gz
libguestfs-bc2657ab1c52ae52464769457352719ad2adb1eb.tar.xz
libguestfs-bc2657ab1c52ae52464769457352719ad2adb1eb.zip
EPEL 5: Add "null vmchannel" back for qemu without virtio-serial support.
Diffstat (limited to 'src/launch.c')
-rw-r--r--src/launch.c196
1 files changed, 140 insertions, 56 deletions
diff --git a/src/launch.c b/src/launch.c
index 2037a678..4d02c622 100644
--- a/src/launch.c
+++ b/src/launch.c
@@ -73,10 +73,14 @@
#include "guestfs-internal-actions.h"
#include "guestfs_protocol.h"
+#define NETWORK "10.0.2.0/24"
+#define ROUTER "10.0.2.2"
+
static int launch_appliance (guestfs_h *g);
static int64_t timeval_diff (const struct timeval *x, const struct timeval *y);
static void print_qemu_command_line (guestfs_h *g, char **argv);
static int connect_unix_socket (guestfs_h *g, const char *sock);
+static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn);
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);
@@ -549,7 +553,9 @@ launch_appliance (guestfs_h *g)
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;
/* At present you must add drives before starting the appliance. In
* future when we enable hotplugging you won't need to do this.
@@ -585,37 +591,43 @@ launch_appliance (guestfs_h *g)
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");
@@ -796,30 +808,20 @@ launch_appliance (guestfs_h *g)
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
- /* 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. */
@@ -831,14 +833,6 @@ launch_appliance (guestfs_h *g)
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");
- }
-
#if defined(__arm__)
#define SERIAL_CONSOLE "ttyAMA0"
#else
@@ -859,11 +853,13 @@ launch_appliance (guestfs_h *g)
LINUX_CMDLINE
"%s " /* (root) */
"%s " /* (selinux) */
+ "%s " /* (vmchannel) */
"%s " /* (verbose) */
"TERM=%s " /* (TERM environment variable) */
"%s", /* (append) */
appliance_root,
g->selinux ? "selinux=1 enforcing=0" : "selinux=0",
+ vmchannel,
g->verbose ? "guestfs_verbose=1" : "",
getenv ("TERM") ? : "linux",
g->append ? g->append : "");
@@ -1036,19 +1032,30 @@ launch_appliance (guestfs_h *g)
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) {
@@ -1590,6 +1597,83 @@ 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;
+}
+
/* You had to call this function after launch in versions <= 1.0.70,
* but it is now a no-op.
*/