Showing posts with label XEN. Show all posts
Showing posts with label XEN. Show all posts

Tuesday, December 24, 2013

libxenstore bug

Opensource is perfect till you hit the critical bug. Then you are doomed. Ok, just joking. There is a community, in other words there are a lot of doomed people like you and bugs like this.
An example. There was (and probably is) a bug in Xen qemu-dm. It can just hang forever due to some circumstances. Some is a key word here. First time I've seen this in Xen Opensource 3.4.2. Taking into account that qemu-dm is responsible for all IO of some VM, this VM hangs too.

The problem was in libxenstore that called pthread_cond_wait():
#define condvar_wait(c,m,hnd)   pthread_cond_wait(c,m)

mutex_lock(&h->watch_mutex);

/* Wait on the condition variable for a watch to fire. */
while (list_empty(&h->watch_list))
        condvar_wait(&h->watch_condvar, &h->watch_mutex, h);
msg = list_top(&h->watch_list, struct xs_stored_msg, list);
list_del(&msg->list);

/* Clear the pipe token if there are no more pending watches. */
if (list_empty(&h->watch_list) && (h->watch_pipe[0] != -1))
        while (read(h->watch_pipe[0], &c, 1) != 1)
                continue;
watch_pipe is used for notifications (for select) about events in watch_list. Separate thread is writing to this pipe during element addition to this list in method read_message():
#define condvar_signal(c)       pthread_cond_signal(c)

mutex_lock(&h->watch_mutex);

/* Kick users out of their select() loop. */
if (list_empty(&h->watch_list) &&
    (h->watch_pipe[1] != -1))
        while (write(h->watch_pipe[1], body, 1) != 1)
                continue;

list_add_tail(&msg->list, &h->watch_list);

condvar_signal(&h->watch_condvar);

mutex_unlock(&h->watch_mutex);
It looks ok but it is not. Sometimes select() reports a read readiness for watch_pipe[0] when watch_list is empty and pthread_cond_wait() hangs indefinitely. This is a big mistake. First of all, kernel can mistakenly give you false positives, second, even if your pipe contains something to read before you actually read from list some another thread can modify this list.
Having said that the patch is obvious
diff --git a/xen-3.4.2/tools/ioemu-qemu-xen/xenstore.c b/xen-3.4.2/tools/ioemu-qemu-xen/xenstore.c
index 11b305d..894e9a5 100644
--- a/xen-3.4.2/tools/ioemu-qemu-xen/xenstore.c
+++ b/xen-3.4.2/tools/ioemu-qemu-xen/xenstore.c
@@ -953,7 +953,7 @@ void xenstore_process_event(void *opaque)
     char **vec, *offset, *bpath = NULL, *buf = NULL, *drv = NULL, *image = NULL;
     unsigned int len, num, hd_index;
 
-    vec = xs_read_watch(xsh, &num);
+    vec = xs_read_watch_noblock(xsh, &num);
     if (!vec)
         return;
 
diff --git a/xen-3.4.2/tools/xenstore/xs.c b/xen-3.4.2/tools/xenstore/xs.c
index 9707d19..9ce5be6 100644
--- a/xen-3.4.2/tools/xenstore/xs.c
+++ b/xen-3.4.2/tools/xenstore/xs.c
@@ -611,11 +611,11 @@ bool xs_watch(struct xs_handle *h, const char *path, const char *token)
                 ARRAY_SIZE(iov), NULL));
 }
 
-/* Find out what node change was on (will block if nothing pending).
+/* Find out what node change was on.
  * Returns array of two pointers: path and token, or NULL.
  * Call free() after use.
  */
-char **xs_read_watch(struct xs_handle *h, unsigned int *num)
+static char **xs_read_watch_(struct xs_handle *h, unsigned int *num, bool block)
 {
     struct xs_stored_msg *msg;
     char **ret, *strings, c = 0;
@@ -623,9 +623,19 @@ char **xs_read_watch(struct xs_handle *h, unsigned int *num)
 
     mutex_lock(&h->watch_mutex);
 
-    /* Wait on the condition variable for a watch to fire. */
-    while (list_empty(&h->watch_list))
-        condvar_wait(&h->watch_condvar, &h->watch_mutex, h);
+    if (block) {
+        /* Wait on the condition variable for a watch to fire. */
+        while (list_empty(&h->watch_list))
+            condvar_wait(&h->watch_condvar, &h->watch_mutex, h);
+    }
+    else if (list_empty(&h->watch_list)) {
+        /* Clear the pipe token if there are no more pending watches. */
+        if (list_empty(&h->watch_list) && (h->watch_pipe[0] != -1))
+            while (read(h->watch_pipe[0], &c, 1) != 1)
+                continue;
+        mutex_unlock(&h->watch_mutex);
+        return NULL;
+    }
     msg = list_top(&h->watch_list, struct xs_stored_msg, list);
     list_del(&msg->list);
 
@@ -662,6 +672,15 @@ char **xs_read_watch(struct xs_handle *h, unsigned int *num)
     return ret;
 }
 
+char **xs_read_watch(struct xs_handle *h, unsigned int *num)
+{
+    return xs_read_watch_(h, num, true);
+}
+
+char **xs_read_watch_noblock(struct xs_handle *h, unsigned int *num)
+{
+    return xs_read_watch_(h, num, false);
+}
 /* Remove a watch on a node.
  * Returns false on failure (no watch on that node).
  */
diff --git a/xen-3.4.2/tools/xenstore/xs.h b/xen-3.4.2/tools/xenstore/xs.h
index bd36a0b..a921a40 100644
--- a/xen-3.4.2/tools/xenstore/xs.h
+++ b/xen-3.4.2/tools/xenstore/xs.h
@@ -109,6 +109,7 @@ int xs_fileno(struct xs_handle *h);
  * elements. Call free() after use.
  */
 char **xs_read_watch(struct xs_handle *h, unsigned int *num);
+char **xs_read_watch_noblock(struct xs_handle *h, unsigned int *num);
 
 /* Remove a watch on a node: implicitly acks any outstanding watch.
  * Returns false on failure (no watch on that node).

Xenstore for fun and profit

The XenStore is the configuration database in which Xen stores information on the running domUs. Although Xen uses the XenStore internally for vital matters like setting up virtual devices, you can also write arbitrary data to it from domUs as well as from dom0. Think of it as some sort of interdomain socket.
I've stolen a useful script from The Book of Xen
#!/bin/sh

function dumpkey() {
  local param=${1}
  local key
  local result
  result=$(xenstore-list ${param})
  if [ "${result}" != "" ] ; then
    for key in ${result} ; do dumpkey ${param}/${key} ; done
  else
    echo -n ${param}'='
    xenstore-read ${param}
  fi
}

for key in /vm /local/domain /tool ; do dumpkey ${key} ; done
Lets do this
/vm/00000000-0000-0000-0000-000000000000/on_xend_stop=ignore
/vm/00000000-0000-0000-0000-000000000000/shadow_memory=0
/vm/00000000-0000-0000-0000-000000000000/uuid=00000000-0000-0000-0000-000000000000
/vm/00000000-0000-0000-0000-000000000000/on_reboot=restart
/vm/00000000-0000-0000-0000-000000000000/image/ostype=linux
/vm/00000000-0000-0000-0000-000000000000/image/kernel=
/vm/00000000-0000-0000-0000-000000000000/image/cmdline=
/vm/00000000-0000-0000-0000-000000000000/image/ramdisk=
/vm/00000000-0000-0000-0000-000000000000/on_poweroff=destroy
/vm/00000000-0000-0000-0000-000000000000/bootloader_args=
/vm/00000000-0000-0000-0000-000000000000/on_xend_start=ignore
/vm/00000000-0000-0000-0000-000000000000/on_crash=restart
/vm/00000000-0000-0000-0000-000000000000/xend/restart_count=0
/vm/00000000-0000-0000-0000-000000000000/vcpus=4
/vm/00000000-0000-0000-0000-000000000000/vcpu_avail=15
/vm/00000000-0000-0000-0000-000000000000/bootloader=
/vm/00000000-0000-0000-0000-000000000000/name=Domain-0
/vm/00000000-0000-0000-0000-000000000000/memory=2796
/local/domain/0/vm=/vm/00000000-0000-0000-0000-000000000000
/local/domain/0/device=
/local/domain/0/control/platform-feature-multiprocessor-suspend=1
/local/domain/0/error=
/local/domain/0/memory/target=2863104
/local/domain/0/guest=
/local/domain/0/hvmpv=
/local/domain/0/cpu/1/availability=online
/local/domain/0/cpu/3/availability=online
/local/domain/0/cpu/2/availability=online
/local/domain/0/cpu/0/availability=online
/local/domain/0/name=Domain-0
/local/domain/0/console/limit=1048576
/local/domain/0/console/type=xenconsoled
/local/domain/0/domid=0
/local/domain/0/device-model=
/tool/xenstored=

Xenoprof, Xen profiling

Xenoprof is a system-wide profiler for Xen virtual machine environments, capable of profiling the Xen virtual machine monitor, multiple Linux guest operating systems, and applications running on them. (http://xenoprof.sourceforge.net/)
A little bit outdated but still useful Xenoprof overview & Networking Performance Analysis.

Xen VNC and stale tcp connections

The problem: in case vnc client terminates and doesn't send packet with FIN/RST flag (for instance, VNC connection was tunneled through VPN and tunnel was closed), connection on server side remains in ESTABLISHED state and one cannot connect to this VM via VNC again, see vnc stops working after a while.
The solution of this simple problem is a bit complicated. First of all tcp keepalive on vnc server socket should be turned on, in other words one must patch qemu-dm that implements VNC in Xen (Xen Opensource 3.4.2).
diff -u -x '*.o' -x '*.o.d' xen-3.4.2/tools/ioemu-qemu-xen/osdep.c xen-3.4.2_fix/tools/ioemu-qemu-xen/osdep.c
--- xen-3.4.2/tools/ioemu-qemu-xen/osdep.c      2009-11-05 11:44:56.000000000 +0000
+++ xen-3.4.2_fix/tools/ioemu-qemu-xen/osdep.c  2011-12-28 13:43:29.938649747 +0000
@@ -338,4 +338,11 @@
     f = fcntl(fd, F_GETFL);
     fcntl(fd, F_SETFL, f | O_NONBLOCK);
 }
+
+int socket_set_keepalive(int fd) {
+    int optval;
+    
+    optval = 1;
+    return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
+}
 #endif
diff -u -x '*.o' -x '*.o.d' xen-3.4.2/tools/ioemu-qemu-xen/qemu_socket.h xen-3.4.2_fix/tools/ioemu-qemu-xen/qemu_socket.h
--- xen-3.4.2/tools/ioemu-qemu-xen/qemu_socket.h        2009-11-05 11:44:56.000000000 +0000
+++ xen-3.4.2_fix/tools/ioemu-qemu-xen/qemu_socket.h    2011-12-28 13:43:32.788255661 +0000
@@ -41,6 +41,7 @@
 
 /* misc helpers */
 void socket_set_nonblock(int fd);
+int socket_set_keepalive(int fd);
 int send_all(int fd, const void *buf, int len1);
 
 /* New, ipv6-ready socket helper functions, see qemu-sockets.c */
diff -u -x '*.o' -x '*.o.d' xen-3.4.2/tools/ioemu-qemu-xen/vnc.c xen-3.4.2_fix/tools/ioemu-qemu-xen/vnc.c
--- xen-3.4.2/tools/ioemu-qemu-xen/vnc.c        2009-11-05 11:44:56.000000000 +0000
+++ xen-3.4.2_fix/tools/ioemu-qemu-xen/vnc.c    2011-12-28 13:44:59.283974757 +0000
@@ -2389,6 +2389,8 @@
        VNC_DEBUG("New client on socket %d\n", vs->csock);
        dcl->idle = 0;
         socket_set_nonblock(vs->csock);
+    if (socket_set_keepalive(vs->csock) == -1)
+        VNC_DEBUG("Cannot set KEEPALIVE on socket %d\n", vs->csock);
        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque);
        vnc_write(vs, "RFB 003.008\n", 12);
        vnc_flush(vs);
Recompile xen tools (make tools, see xen README) and replace qemu-dm executable with dist/install/usr/lib64/xen/bin/qemu-dm.
Configure linux kernel
HDC11:~# sysctl -a | grep ipv4.tcp_keep
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 10
This means in case there are no packets during 30 secs send empty packet, if no ACK received send 5 packets every 10 secs, if still no answer send RST and close connection.
Test
HDC11:~# netstat -nap | grep 5901
tcp 0 0 0.0.0.0:5901 0.0.0.0:* LISTEN 28905/qemu-dm
HDC11:~# netstat -nap | grep 5901
tcp 0 0 0.0.0.0:5901 0.0.0.0:* LISTEN 28905/qemu-dm
tcp 0 0 10.10.17.11:5901 10.10.17.216:1334 ESTABLISHED 28905/qemu-dm <---- VNC connection via VPN
HDC11:~# tcpdump -n -i eth2 port 1334
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth2, link-type EN10MB (Ethernet), capture size 96 bytes
07:42:15.119612 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1601431976 win 5840
07:42:15.316704 IP 10.10.17.216.1334 > 10.10.17.11.5901: . ack 1 win 64228
07:42:45.319980 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1 win 5840 <--- acks every 30 secs
07:42:45.701610 IP 10.10.17.216.1334 > 10.10.17.11.5901: . ack 1 win 64228 <--- response to ack
07:43:15.700307 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1 win 5840 <--- kill vpn, no response anymore
07:43:25.700398 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1 win 5840
07:43:35.700586 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1 win 5840
07:43:45.700639 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1 win 5840
07:43:55.700765 IP 10.10.17.11.5901 > 10.10.17.216.1334: . ack 1 win 5840 <--- 5 probes every 10 secs
07:44:05.700913 IP 10.10.17.11.5901 > 10.10.17.216.1334: R 1:1(0) ack 1 win 5840 <--- still no response, RST
^C
10 packets captured
10 packets received by filter
0 packets dropped by kernel
HDC11:~# netstat -nap | grep 5901
tcp 0 0 0.0.0.0:5901 0.0.0.0:* LISTEN 28905/qemu-dm
<--- no ESTABLISHED connections
HDC11:~# telnet localhost 5901 <--- we can connect to this port again
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
RFB 003.008 <--- server responds
^]
telnet> quit
But we need to go deeper. Tcp keepalive works only when there are no packets on the wire. Imagine VNC server sent some packet and connection terminated in silent way (i.e. it didn't receive ACK in his packet). In this case linux will try to resend packet until RTO.
07:57:36.123013 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1557:1598(41) ack 377 win 5840
07:57:36.317922 IP 10.10.17.216.1421 > 10.10.17.11.5901: P 377:387(10) ack 1598 win 63353 <--- here we lost connection
07:57:36.360862 IP 10.10.17.11.5901 > 10.10.17.216.1421: . ack 387 win 5840 07:58:06.317741 IP 10.10.17.11.5901 > 10.10.17.216.1421: . ack 387 win 5840
07:58:16.321445 IP 10.10.17.11.5901 > 10.10.17.216.1421: . ack 387 win 5840
07:58:16.932260 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840 <--- vnc server (qemu-dm) tries to send some data
07:58:17.621451 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840 <--- retransmit algorithm starts with dynamic growing intervals
07:58:19.001467 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
07:58:21.761500 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
07:58:27.281590 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
07:58:38.321648 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
07:59:00.401919 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
07:59:44.562552 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:01:12.883584 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:03:12.885069 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:05:12.886487 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:07:12.887861 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:09:12.889364 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:11:12.890812 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:13:12.892282 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
08:15:12.893702 IP 10.10.17.11.5901 > 10.10.17.216.1421: P 1598:1617(19) ack 387 win 5840
<--- ok, now reno gives up, connection is closed after ~15 mins
HDC11:~# netstat -nap | grep 5901
tcp 0 0 0.0.0.0:5901 0.0.0.0:* LISTEN 28905/qemu-dm
15 mins without ability to connect to VM is too long for client. Configure kernel again - decrease /proc/sys/net/ipv4/tcp_retries2 (net.ipv4.tcp_retries2) from 15 to 5 tries.
09:05:45.286580 IP 10.10.17.216.1312 > 10.10.17.11.5901: P 84:94(10) ack 3649 win 63679
09:05:45.320669 IP 10.10.17.11.5901 > 10.10.17.216.1312: . ack 94 win 5840
09:06:15.281030 IP 10.10.17.11.5901 > 10.10.17.216.1312: . ack 94 win 5840
09:06:25.281213 IP 10.10.17.11.5901 > 10.10.17.216.1312: . ack 94 win 5840
09:06:25.961866 IP 10.10.17.11.5901 > 10.10.17.216.1312: P 3649:3668(19) ack 94 win 5840
09:06:26.661261 IP 10.10.17.11.5901 > 10.10.17.216.1312: P 3649:3668(19) ack 94 win 5840
09:06:28.061265 IP 10.10.17.11.5901 > 10.10.17.216.1312: P 3649:3668(19) ack 94 win 5840
09:06:30.861301 IP 10.10.17.11.5901 > 10.10.17.216.1312: P 3649:3668(19) ack 94 win 5840
09:06:36.461346 IP 10.10.17.11.5901 > 10.10.17.216.1312: P 3649:3668(19) ack 94 win 5840
09:06:47.661424 IP 10.10.17.11.5901 > 10.10.17.216.1312: P 3649:3668(19) ack 94 win 5840
~60-90 secs
As a result one can reestablish connection to VNC in 1-2 mins.

Note, one can kill stale connection with the help of netfilter by setting conntrack net.netfilter.nf_conntrack_tcp_timeout_established to 1-2 hours (default is 5 days). After this time elapses iptables will send RST in both directions. But one still need to set proper net.ipv4.tcp_retries2.

Monday, December 23, 2013

How to change path to backing file in qcow2

Standard utility cannot do this. Take C and qcow2 image format doc and write something like
#define ntohll(x) (((uint64_t)(ntohl((uint32_t)((x << 32) >> 32))) << 32) | ntohl(((uint32_t)(x >> 32))))


/* change filename in qcow2 */
int backing_file_patch(const char *in_file, const char *new_backing_file) {
    
    int f;
    uint64_t backing_file_offset;
    uint32_t backing_file_size;
        
    if ((f = open(in_file, O_RDWR)) == -1) {
        perror("open");
        goto error;
    }
    
    if (lseek(f, 8, SEEK_SET) == (off_t)-1) {
        perror("lseek");
        goto error;
    }

    if (read(f, &backing_file_offset, sizeof(backing_file_offset)) == -1) {
        perror("read");
        goto error;
    }
    backing_file_offset = ntohll(backing_file_offset);
    
    backing_file_size = strlen(new_backing_file);
    backing_file_size = htonl(backing_file_size);
    
    /* update backing_file_size */
    if (write(f, &backing_file_size, sizeof(backing_file_size)) == -1) {
        perror("write");
        goto error;
    }
    
    /* update path */
    if (lseek(f, backing_file_offset, SEEK_SET) == (off_t)-1) {
        perror("lseek");
        goto error;
    }
    if (write(f, new_backing_file, strlen(new_backing_file)) == -1) {
        perror("write");
        goto error;
    }
        
    close(f);
    return 0;
    
error:
    close(f);
    return 1;
}
To test this
/usr/sbin/xm block-attach 0 tap:qcow2:/home/test/vm_drive_C_snapshot.qcow2 /dev/xvda w 0
/bin/ntfs-3g -o dev_offset=7340032,rw,noatime,force,entry_timeout=60000,negative_timeout=60000,attr_timeout=60000,ac_attr_timeout=60000 /dev/xvda exposed
Cleanup
/bin/umount exposed
/usr/sbin/xm block-detach 0 51712