diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs
index 6748e4f..1baa1f1 100644
--- a/slirp/Makefile.objs
+++ b/slirp/Makefile.objs
@@ -1,5 +1,5 @@
 common-obj-y = cksum.o if.o ip_icmp.o ip6_icmp.o ip6_input.o ip6_output.o \
-               ip_input.o ip_output.o dnssearch.o
+               ip_input.o ip_output.o dnssearch.o dhcpv6.o
 common-obj-y += slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o
 common-obj-y += tcp_subr.o tcp_timer.o udp.o udp6.o bootp.o tftp.o arp_table.o \
                 ndp_table.o
diff --git a/slirp/dhcpv6.c b/slirp/dhcpv6.c
new file mode 100644
index 0000000..02c51c7
--- /dev/null
+++ b/slirp/dhcpv6.c
@@ -0,0 +1,209 @@
+/*
+ * SLIRP stateless DHCPv6
+ *
+ * We only support stateless DHCPv6, e.g. for network booting.
+ * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details.
+ *
+ * Copyright 2016 Thomas Huth, Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "slirp.h"
+#include "dhcpv6.h"
+
+/* DHCPv6 message types */
+#define MSGTYPE_REPLY        7
+#define MSGTYPE_INFO_REQUEST 11
+
+/* DHCPv6 option types */
+#define OPTION_CLIENTID      1
+#define OPTION_IAADDR        5
+#define OPTION_ORO           6
+#define OPTION_DNS_SERVERS   23
+#define OPTION_BOOTFILE_URL  59
+
+struct requested_infos {
+    uint8_t *client_id;
+    int client_id_len;
+    bool want_dns;
+    bool want_boot_url;
+};
+
+/**
+ * Analyze the info request message sent by the client to see what data it
+ * provided and what it wants to have. The information is gathered in the
+ * "requested_infos" struct. Note that client_id (if provided) points into
+ * the odata region, thus the caller must keep odata valid as long as it
+ * needs to access the requested_infos struct.
+ */
+static int dhcpv6_parse_info_request(uint8_t *odata, int olen,
+                                     struct requested_infos *ri)
+{
+    int i, req_opt;
+
+    while (olen > 4) {
+        /* Parse one option */
+        int option = odata[0] << 8 | odata[1];
+        int len = odata[2] << 8 | odata[3];
+
+        if (len + 4 > olen) {
+            qemu_log_mask(LOG_GUEST_ERROR, "Guest sent bad DHCPv6 packet!\n");
+            return -E2BIG;
+        }
+
+        switch (option) {
+        case OPTION_IAADDR:
+            /* According to RFC3315, we must discard requests with IA option */
+            return -EINVAL;
+        case OPTION_CLIENTID:
+            if (len > 256) {
+                /* Avoid very long IDs which could cause problems later */
+                return -E2BIG;
+            }
+            ri->client_id = odata + 4;
+            ri->client_id_len = len;
+            break;
+        case OPTION_ORO:        /* Option request option */
+            if (len & 1) {
+                return -EINVAL;
+            }
+            /* Check which options the client wants to have */
+            for (i = 0; i < len; i += 2) {
+                req_opt = odata[4 + i] << 8 | odata[4 + i + 1];
+                switch (req_opt) {
+                case OPTION_DNS_SERVERS:
+                    ri->want_dns = true;
+                    break;
+                case OPTION_BOOTFILE_URL:
+                    ri->want_boot_url = true;
+                    break;
+                default:
+                    DEBUG_MISC((dfd, "dhcpv6: Unsupported option request %d\n",
+                                req_opt));
+                }
+            }
+            break;
+        default:
+            DEBUG_MISC((dfd, "dhcpv6 info req: Unsupported option %d, len=%d\n",
+                        option, len));
+        }
+
+        odata += len + 4;
+        olen -= len + 4;
+    }
+
+    return 0;
+}
+
+
+/**
+ * Handle information request messages
+ */
+static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas,
+                                uint32_t xid, uint8_t *odata, int olen)
+{
+    struct requested_infos ri = { NULL };
+    struct sockaddr_in6 sa6, da6;
+    struct mbuf *m;
+    uint8_t *resp;
+
+    if (dhcpv6_parse_info_request(odata, olen, &ri) < 0) {
+        return;
+    }
+
+    m = m_get(slirp);
+    if (!m) {
+        return;
+    }
+    memset(m->m_data, 0, m->m_size);
+    m->m_data += IF_MAXLINKHDR;
+    resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr);
+
+    /* Fill in response */
+    *resp++ = MSGTYPE_REPLY;
+    *resp++ = (uint8_t)(xid >> 16);
+    *resp++ = (uint8_t)(xid >> 8);
+    *resp++ = (uint8_t)xid;
+
+    if (ri.client_id) {
+        *resp++ = OPTION_CLIENTID >> 8;         /* option-code high byte */
+        *resp++ = OPTION_CLIENTID;              /* option-code low byte */
+        *resp++ = ri.client_id_len >> 8;        /* option-len high byte */
+        *resp++ = ri.client_id_len;             /* option-len low byte */
+        memcpy(resp, ri.client_id, ri.client_id_len);
+        resp += ri.client_id_len;
+    }
+    if (ri.want_dns) {
+        *resp++ = OPTION_DNS_SERVERS >> 8;      /* option-code high byte */
+        *resp++ = OPTION_DNS_SERVERS;           /* option-code low byte */
+        *resp++ = 0;                            /* option-len high byte */
+        *resp++ = 16;                           /* option-len low byte */
+        memcpy(resp, &slirp->vnameserver_addr6, 16);
+        resp += 16;
+    }
+    if (ri.want_boot_url) {
+        uint8_t *sa = slirp->vhost_addr6.s6_addr;
+        int slen, smaxlen;
+
+        *resp++ = OPTION_BOOTFILE_URL >> 8;     /* option-code high byte */
+        *resp++ = OPTION_BOOTFILE_URL;          /* option-code low byte */
+        smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2);
+        slen = snprintf((char *)resp + 2, smaxlen,
+                        "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
+                                "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s",
+                        sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7],
+                        sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14],
+                        sa[15], slirp->bootp_filename);
+        slen = min(slen, smaxlen);
+        *resp++ = slen >> 8;                    /* option-len high byte */
+        *resp++ = slen;                         /* option-len low byte */
+        resp += slen;
+    }
+
+    sa6.sin6_addr = slirp->vhost_addr6;
+    sa6.sin6_port = DHCPV6_SERVER_PORT;
+    da6.sin6_addr = srcsas->sin6_addr;
+    da6.sin6_port = srcsas->sin6_port;
+    m->m_data += sizeof(struct ip6) + sizeof(struct udphdr);
+    m->m_len = resp - (uint8_t *)m->m_data;
+    udp6_output(NULL, m, &sa6, &da6);
+}
+
+/**
+ * Handle DHCPv6 messages sent by the client
+ */
+void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m)
+{
+    uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr);
+    int data_len = m->m_len - sizeof(struct udphdr);
+    uint32_t xid;
+
+    if (data_len < 4) {
+        return;
+    }
+
+    xid = ntohl(*(uint32_t *)data) & 0xffffff;
+
+    switch (data[0]) {
+    case MSGTYPE_INFO_REQUEST:
+        dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4);
+        break;
+    default:
+        DEBUG_MISC((dfd, "dhcpv6_input: Unsupported message type 0x%x\n",
+                    data[0]));
+    }
+}
diff --git a/slirp/dhcpv6.h b/slirp/dhcpv6.h
new file mode 100644
index 0000000..9189cd3
--- /dev/null
+++ b/slirp/dhcpv6.h
@@ -0,0 +1,22 @@
+/*
+ * Definitions and prototypes for SLIRP stateless DHCPv6
+ *
+ * Copyright 2016 Thomas Huth, Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2
+ * or later. See the COPYING file in the top-level directory.
+ */
+#ifndef SLIRP_DHCPV6_H
+#define SLIRP_DHCPV6_H
+
+#define DHCPV6_SERVER_PORT 547
+
+#define ALLDHCP_MULTICAST { .s6_addr = \
+                            { 0xff, 0x02, 0x00, 0x00,\
+                            0x00, 0x00, 0x00, 0x00,\
+                            0x00, 0x00, 0x00, 0x00,\
+                            0x00, 0x01, 0x00, 0x02 } }
+
+void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m);
+
+#endif
diff --git a/slirp/udp6.c b/slirp/udp6.c
index 94efb13..9fa314b 100644
--- a/slirp/udp6.c
+++ b/slirp/udp6.c
@@ -7,6 +7,7 @@
 #include "qemu-common.h"
 #include "slirp.h"
 #include "udp.h"
+#include "dhcpv6.h"
 
 void udp6_input(struct mbuf *m)
 {
@@ -61,7 +62,17 @@ void udp6_input(struct mbuf *m)
     lhost.sin6_addr = ip->ip_src;
     lhost.sin6_port = uh->uh_sport;
 
-    /* TODO handle DHCP/BOOTP */
+    /* handle DHCPv6 */
+    if (ntohs(uh->uh_dport) == DHCPV6_SERVER_PORT &&
+        (in6_equal(&ip->ip_dst, &slirp->vhost_addr6) ||
+         in6_equal(&ip->ip_dst, &(struct in6_addr)ALLDHCP_MULTICAST))) {
+        m->m_data += iphlen;
+        m->m_len -= iphlen;
+        dhcpv6_input(&lhost, m);
+        m->m_data -= iphlen;
+        m->m_len += iphlen;
+        goto bad;
+    }
 
     /* handle TFTP */
     if (ntohs(uh->uh_dport) == TFTP_SERVER &&
