Patchwork Add domain-search option to slirp's DHCP server

login
register
mail settings
Submitter Klaus Stengel
Date Oct. 27, 2012, 5:53 p.m.
Message ID <1351372020.27191.4.camel@aquila.nathanet.lan>
Download mbox | patch
Permalink /patch/194636/
State New
Headers show

Comments

Klaus Stengel - Oct. 27, 2012, 5:53 p.m.
This patch will allow the user to include the domain-search option in
replies from the built-in DHCP server. The domain suffixes can be
specified by adding dnssearch= entries to the "-net user" parameter.

Signed-off-by: Klaus Stengel <Klaus.Stengel@asamnet.de>
---
 net/slirp.c         |   35 +++++-
 qapi-schema.json    |    4 +
 qemu-options.hx     |   18 +++-
 slirp/Makefile.objs |    2 +-
 slirp/bootp.c       |   12 ++
 slirp/dnssearch.c   |  320 +++++++++++++++++++++++++++++++++++++++++++++++++++
 slirp/libslirp.h    |    3 +-
 slirp/slirp.c       |    8 +-
 slirp/slirp.h       |    5 +
 9 files changed, 398 insertions(+), 9 deletions(-)
 create mode 100644 slirp/dnssearch.c
Jan Kiszka - Nov. 15, 2012, 9:33 a.m.
On 2012-10-27 19:53, Klaus Stengel wrote:
> This patch will allow the user to include the domain-search option in
> replies from the built-in DHCP server. The domain suffixes can be
> specified by adding dnssearch= entries to the "-net user" parameter.
> 
> Signed-off-by: Klaus Stengel <Klaus.Stengel@asamnet.de>
> ---
>  net/slirp.c         |   35 +++++-
>  qapi-schema.json    |    4 +
>  qemu-options.hx     |   18 +++-
>  slirp/Makefile.objs |    2 +-
>  slirp/bootp.c       |   12 ++
>  slirp/dnssearch.c   |  320 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  slirp/libslirp.h    |    3 +-
>  slirp/slirp.c       |    8 +-
>  slirp/slirp.h       |    5 +
>  9 files changed, 398 insertions(+), 9 deletions(-)
>  create mode 100644 slirp/dnssearch.c
> 
> diff --git a/net/slirp.c b/net/slirp.c
> index bf86a44..72ecc5e 100644
> --- a/net/slirp.c
> +++ b/net/slirp.c
> @@ -136,7 +136,7 @@ static int net_slirp_init(NetClientState *peer, const char *model,
>                            const char *vhostname, const char *tftp_export,
>                            const char *bootfile, const char *vdhcp_start,
>                            const char *vnameserver, const char *smb_export,
> -                          const char *vsmbserver)
> +                          const char *vsmbserver, const char **dnssearch)
>  {
>      /* default settings according to historic slirp */
>      struct in_addr net  = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */
> @@ -242,7 +242,7 @@ static int net_slirp_init(NetClientState *peer, const char *model,
>      s = DO_UPCAST(SlirpState, nc, nc);
> 
>      s->slirp = slirp_init(restricted, net, mask, host, vhostname,
> -                          tftp_export, bootfile, dhcp, dns, s);
> +                          tftp_export, bootfile, dhcp, dns, dnssearch, s);
>      QTAILQ_INSERT_TAIL(&slirp_stacks, s, entry);
> 
>      for (config = slirp_configs; config; config = config->next) {
> @@ -699,6 +699,31 @@ net_init_slirp_configs(const StringList *fwd, int flags)
>      }
>  }
> 
> +static const char**
> +slirp_dnssearch(const StringList *dnsname) {
> +    const StringList *c = dnsname;
> +    size_t i = 0, num_opts = 0;
> +    const char **ret;
> +
> +    while (c) {
> +        num_opts++;
> +        c = c->next;
> +    }
> +
> +    if (num_opts == 0) {
> +        return NULL;
> +    }
> +
> +    ret = g_malloc((num_opts + 1) * sizeof(*ret));
> +    c = dnsname;
> +    while (c) {
> +        ret[i++] = c->value->str;
> +        c = c->next;
> +    }
> +    ret[i] = NULL;
> +    return ret;
> +}
> +
>  int net_init_slirp(const NetClientOptions *opts, const char *name,
>                     NetClientState *peer)
>  {
> @@ -706,6 +731,7 @@ int net_init_slirp(const NetClientOptions *opts, const char *name,
>      char *vnet;
>      int ret;
>      const NetdevUserOptions *user;
> +    const char **dnssearch;
> 
>      assert(opts->kind == NET_CLIENT_OPTIONS_KIND_USER);
>      user = opts->user;
> @@ -714,6 +740,8 @@ int net_init_slirp(const NetClientOptions *opts, const char *name,
>             user->has_ip  ? g_strdup_printf("%s/24", user->ip) :
>             NULL;
> 
> +    dnssearch = slirp_dnssearch(user->dnssearch);
> +
>      /* all optional fields are initialized to "all bits zero" */
> 
>      net_init_slirp_configs(user->hostfwd, SLIRP_CFG_HOSTFWD);
> @@ -722,7 +750,7 @@ int net_init_slirp(const NetClientOptions *opts, const char *name,
>      ret = net_slirp_init(peer, "user", name, user->q_restrict, vnet,
>                           user->host, user->hostname, user->tftp,
>                           user->bootfile, user->dhcpstart, user->dns, user->smb,
> -                         user->smbserver);
> +                         user->smbserver, dnssearch);
> 
>      while (slirp_configs) {
>          config = slirp_configs;
> @@ -731,6 +759,7 @@ int net_init_slirp(const NetClientOptions *opts, const char *name,
>      }
> 
>      g_free(vnet);
> +    g_free(dnssearch);
> 
>      return ret;
>  }
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 6fd263e..b24ce95 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -2297,6 +2297,9 @@
>  #
>  # @dns: #optional guest-visible address of the virtual nameserver
>  #
> +# @dnssearch: #optional list of DNS suffixes to search, passed as DHCP option
> +#             to the guest
> +#
>  # @smb: #optional root directory of the built-in SMB server
>  #
>  # @smbserver: #optional IP address of the built-in SMB server
> @@ -2319,6 +2322,7 @@
>      '*bootfile':  'str',
>      '*dhcpstart': 'str',
>      '*dns':       'str',
> +    '*dnssearch': ['String'],
>      '*smb':       'str',
>      '*smbserver': 'str',
>      '*hostfwd':   ['String'],
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 46f0539..a6efc56 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1282,8 +1282,8 @@ DEF("net", HAS_ARG, QEMU_OPTION_net,
>      "                create a new Network Interface Card and connect it to VLAN 'n'\n"
>  #ifdef CONFIG_SLIRP
>      "-net user[,vlan=n][,name=str][,net=addr[/mask]][,host=addr][,restrict=on|off]\n"
> -    "         [,hostname=host][,dhcpstart=addr][,dns=addr][,tftp=dir][,bootfile=f]\n"
> -    "         [,hostfwd=rule][,guestfwd=rule]"
> +    "         [,hostname=host][,dhcpstart=addr][,dns=addr][,dnssearch=domain][,tftp=dir]\n"
> +    "         [,bootfile=f][,hostfwd=rule][,guestfwd=rule]"
>  #ifndef _WIN32
>                                               "[,smb=dir[,smbserver=addr]]\n"
>  #endif
> @@ -1392,7 +1392,7 @@ able to contact the host and no guest IP packets will be routed over the host
>  to the outside. This option does not affect any explicitly set forwarding rules.
> 
>  @item hostname=@var{name}
> -Specifies the client hostname reported by the builtin DHCP server.
> +Specifies the client hostname reported by the built-in DHCP server.
> 
>  @item dhcpstart=@var{addr}
>  Specify the first of the 16 IPs the built-in DHCP server can assign. Default
> @@ -1403,6 +1403,18 @@ Specify the guest-visible address of the virtual nameserver. The address must
>  be different from the host address. Default is the 3rd IP in the guest network,
>  i.e. x.x.x.3.
> 
> +@item dnssearch=@var{domain}
> +Provides an entry for the domain-search list sent by the built-in
> +DHCP server. More than one domain suffix can be transmitted by specifying
> +this option multiple times. If supported, this will cause the guest to
> +automatically try to append the given domain suffix(es) in case a domain name
> +can not be resolved.
> +
> +Example:
> +@example
> +qemu -net user,dnssearch=mgmt.example.org,dnssearch=example.org [...]
> +@end example
> +
>  @item tftp=@var{dir}
>  When using the user mode network stack, activate a built-in TFTP
>  server. The files in @var{dir} will be exposed as the root of a TFTP server.
> diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs
> index bb43d3c..2daa9dc 100644
> --- a/slirp/Makefile.objs
> +++ b/slirp/Makefile.objs
> @@ -1,3 +1,3 @@
> -common-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o
> +common-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o dnssearch.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 bootp.o tftp.o arp_table.o
> diff --git a/slirp/bootp.c b/slirp/bootp.c
> index 64eac7d..b7db9fa 100644
> --- a/slirp/bootp.c
> +++ b/slirp/bootp.c
> @@ -287,6 +287,18 @@ static void bootp_reply(Slirp *slirp, const struct bootp_t *bp)
>              memcpy(q, slirp->client_hostname, val);
>              q += val;
>          }
> +
> +        if (slirp->vdnssearch) {
> +            size_t spaceleft = sizeof(rbp->bp_vend) - (q - rbp->bp_vend);
> +            val = slirp->vdnssearch_len;
> +            if (val + 1 > spaceleft) {
> +                g_warning("DHCP packet size exceeded, "
> +                    "omitting domain-search option.");
> +            } else {
> +                memcpy(q, slirp->vdnssearch, val);
> +                q += val;
> +            }
> +        }
>      } else {
>          static const char nak_msg[] = "requested address not available";
> 
> diff --git a/slirp/dnssearch.c b/slirp/dnssearch.c
> new file mode 100644
> index 0000000..898f7d8
> --- /dev/null
> +++ b/slirp/dnssearch.c
> @@ -0,0 +1,320 @@
> +/*
> + * Domain search option for DHCP (RFC 3397)
> + *
> + * Copyright (c) 2012 Klaus Stengel
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <glib.h>
> +#include "slirp.h"
> +
> +static const uint8_t RFC3397_OPT_DOMAIN_SEARCH = 119;
> +static const uint8_t MAX_OPT_LEN = 255;
> +static const uint8_t OPT_HEADER_LEN = 2;
> +static const uint8_t REFERENCE_LEN = 2;
> +
> +struct compact_domain;
> +
> +typedef struct compact_domain {
> +    struct compact_domain *self;
> +    struct compact_domain *refdom;
> +    uint8_t *labels;
> +    size_t len;
> +    size_t common_octets;
> +} CompactDomain;
> +
> +static size_t
> +domain_suffix_diffoff(const CompactDomain *a, const CompactDomain *b)
> +{
> +    size_t la = a->len, lb = b->len;
> +    uint8_t *da = a->labels + la, *db = b->labels + lb;
> +    size_t i, lm = (la < lb) ? la : lb;
> +
> +    for (i = 0; i < lm; i++) {
> +        da--; db--;
> +        if (*da != *db) {
> +            break;
> +        }
> +    }
> +    return i;
> +}
> +
> +static int
> +domain_suffix_ord(const void *cva, const void *cvb)
> +{
> +    const CompactDomain *a = cva, *b = cvb;
> +    size_t la = a->len, lb = b->len;
> +    size_t doff = domain_suffix_diffoff(a, b);
> +    uint8_t ca = a->labels[la - doff];
> +    uint8_t cb = b->labels[lb - doff];
> +
> +    if (ca < cb) {
> +        return -1;
> +    }
> +    if (ca > cb) {
> +        return 1;
> +    }
> +    if (la < lb) {
> +        return -1;
> +    }
> +    if (la > lb) {
> +        return 1;
> +    }
> +    return 0;
> +}
> +
> +static size_t
> +domain_common_label(CompactDomain *a, CompactDomain *b)
> +{
> +    size_t res, doff = domain_suffix_diffoff(a, b);
> +    uint8_t *first_eq_pos = a->labels + (a->len - doff);
> +    uint8_t *label = a->labels;
> +
> +    while (*label && label < first_eq_pos) {
> +        label += *label + 1;
> +    }
> +    res = a->len - (label - a->labels);
> +    /* only report if it can help to reduce the packet size */
> +    return (res > REFERENCE_LEN) ? res : 0;
> +}
> +
> +static void
> +domain_fixup_order(CompactDomain *cd, size_t n)
> +{
> +    size_t i;
> +
> +    for (i = 0; i < n; i++) {
> +        CompactDomain *cur = cd + i, *next = cd[i].self;
> +
> +        while (!cur->common_octets) {
> +            CompactDomain *tmp = next->self; /* backup target value */
> +
> +            next->self = cur;
> +            cur->common_octets++;
> +
> +            cur = next;
> +            next = tmp;
> +        }
> +    }
> +}
> +
> +static void
> +domain_mklabels(CompactDomain *cd, const char *input)
> +{
> +    uint8_t *len_marker = cd->labels;
> +    uint8_t *output = len_marker; /* pre-incremented */
> +    const char *in = input;
> +    char cur_chr;
> +    size_t len = 0;
> +
> +    if (cd->len == 0) {
> +        goto fail;
> +    }
> +    cd->len++;
> +
> +    do {
> +        cur_chr = *in++;
> +        if (cur_chr == '.' || cur_chr == '\0') {
> +            len = output - len_marker;
> +            if ((len == 0 && cur_chr == '.') || len >= 64) {
> +                goto fail;
> +            }
> +            *len_marker = len;
> +
> +            output++;
> +            len_marker = output;
> +        } else {
> +            output++;
> +            *output = cur_chr;
> +        }
> +    } while (cur_chr != '\0');
> +
> +    /* ensure proper zero-termination */
> +    if (len != 0) {
> +        *len_marker = 0;
> +        cd->len++;
> +    }
> +    return;
> +
> +fail:
> +    g_warning("failed to parse domain name '%s'\n", input);
> +    cd->len = 0;
> +}
> +
> +static void
> +domain_mkxrefs(CompactDomain *doms, CompactDomain *last, size_t depth)
> +{
> +    CompactDomain *i = doms, *target = doms;
> +
> +    do {
> +        if (i->labels < target->labels) {
> +            target = i;
> +        }
> +    } while (i++ != last);
> +
> +    for (i = doms; i != last; i++) {
> +        CompactDomain *group_last;
> +        size_t next_depth;
> +
> +        if (i->common_octets == depth) {
> +            continue;
> +        }
> +
> +        next_depth = -1;
> +        for (group_last = i; group_last != last; group_last++) {
> +            size_t co = group_last->common_octets;
> +            if (co <= depth) {
> +                break;
> +            }
> +            if (co < next_depth) {
> +                next_depth = co;
> +            }
> +        }
> +        domain_mkxrefs(i, group_last, next_depth);
> +
> +        i = group_last;
> +        if (i == last) {
> +            break;
> +        }
> +    }
> +
> +    if (depth == 0) {
> +        return;
> +    }
> +
> +    i = doms;
> +    do {
> +        if (i != target && i->refdom == NULL) {
> +            i->refdom = target;
> +            i->common_octets = depth;
> +        }
> +    } while (i++ != last);
> +}
> +
> +static size_t
> +domain_compactify(CompactDomain *domains, size_t n)
> +{
> +    uint8_t *start = domains->self->labels, *outptr = start;
> +    size_t i;
> +
> +    for (i = 0; i < n; i++) {
> +        CompactDomain *cd = domains[i].self;
> +        CompactDomain *rd = cd->refdom;
> +
> +        if (rd != NULL) {
> +            size_t moff = (rd->labels - start)
> +                    + (rd->len - cd->common_octets);
> +            if (moff < 0x3FFFu) {
> +                cd->len -= cd->common_octets - 2;
> +                cd->labels[cd->len - 1] = moff & 0xFFu;
> +                cd->labels[cd->len - 2] = 0xC0u | (moff >> 8);
> +            }
> +        }
> +
> +        if (cd->labels != outptr) {
> +            memmove(outptr, cd->labels, cd->len);
> +            cd->labels = outptr;
> +        }
> +        outptr += cd->len;
> +    }
> +    return outptr - start;
> +}
> +
> +int
> +translate_dnssearch(Slirp *s, const char **names)
> +{
> +    size_t blocks, bsrc_start, bsrc_end, bdst_start;
> +    size_t i, num_domains, memreq = 0;
> +    uint8_t *result = NULL, *outptr;
> +    CompactDomain *domains = NULL;
> +    const char **nameptr = names;
> +
> +    while (*nameptr != NULL) {
> +        nameptr++;
> +    }
> +
> +    num_domains = nameptr - names;
> +    if (num_domains == 0) {
> +        return -2;
> +    }
> +
> +    domains = g_malloc(num_domains * sizeof(*domains));
> +
> +    for (i = 0; i < num_domains; i++) {
> +        size_t nlen = strlen(names[i]);
> +        memreq += nlen + 2; /* 1 zero octet + 1 label length octet */
> +        domains[i].self = domains + i;
> +        domains[i].len = nlen;
> +        domains[i].common_octets = 0;
> +        domains[i].refdom = NULL;
> +    }
> +
> +    /* reserve extra 2 header bytes for each 255 bytes of output */
> +    memreq += ((memreq + MAX_OPT_LEN - 1) / MAX_OPT_LEN) * OPT_HEADER_LEN;
> +    result = g_malloc(memreq * sizeof(*result));
> +
> +    outptr = result;
> +    for (i = 0; i < num_domains; i++) {
> +        domains[i].labels = outptr;
> +        domain_mklabels(domains + i, names[i]);
> +        outptr += domains[i].len;
> +    }
> +
> +    if (outptr == result) {
> +        g_free(domains);
> +        g_free(result);
> +        return -1;
> +    }
> +
> +    qsort(domains, num_domains, sizeof(*domains), domain_suffix_ord);
> +    domain_fixup_order(domains, num_domains);
> +
> +    for (i = 1; i < num_domains; i++) {
> +        size_t cl = domain_common_label(domains + i - 1, domains + i);
> +        domains[i - 1].common_octets = cl;
> +    }
> +
> +    domain_mkxrefs(domains, domains + num_domains - 1, 0);
> +    memreq = domain_compactify(domains, num_domains);
> +
> +    blocks = (memreq + MAX_OPT_LEN - 1) / MAX_OPT_LEN;
> +    bsrc_end = memreq;
> +    bsrc_start = (blocks - 1) * MAX_OPT_LEN;
> +    bdst_start = bsrc_start + blocks * OPT_HEADER_LEN;
> +    memreq += blocks * OPT_HEADER_LEN;
> +
> +    while (blocks--) {
> +        size_t len = bsrc_end - bsrc_start;
> +        memmove(result + bdst_start, result + bsrc_start, len);
> +        result[bdst_start - 2] = RFC3397_OPT_DOMAIN_SEARCH;
> +        result[bdst_start - 1] = len;
> +        bsrc_end = bsrc_start;
> +        bsrc_start -= MAX_OPT_LEN;
> +        bdst_start -= MAX_OPT_LEN + OPT_HEADER_LEN;
> +    }
> +
> +    g_free(domains);
> +    s->vdnssearch = result;
> +    s->vdnssearch_len = memreq;
> +    return 0;
> +}
> diff --git a/slirp/libslirp.h b/slirp/libslirp.h
> index 9b471b5..64878c4 100644
> --- a/slirp/libslirp.h
> +++ b/slirp/libslirp.h
> @@ -12,7 +12,8 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork,
>                    struct in_addr vnetmask, struct in_addr vhost,
>                    const char *vhostname, const char *tftp_path,
>                    const char *bootfile, struct in_addr vdhcp_start,
> -                  struct in_addr vnameserver, void *opaque);
> +                  struct in_addr vnameserver, const char **vdnssearch,
> +                   void *opaque);
>  void slirp_cleanup(Slirp *slirp);
> 
>  void slirp_update_timeout(uint32_t *timeout);
> diff --git a/slirp/slirp.c b/slirp/slirp.c
> index 38e0a21..3395d50 100644
> --- a/slirp/slirp.c
> +++ b/slirp/slirp.c
> @@ -203,7 +203,8 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork,
>                    struct in_addr vnetmask, struct in_addr vhost,
>                    const char *vhostname, const char *tftp_path,
>                    const char *bootfile, struct in_addr vdhcp_start,
> -                  struct in_addr vnameserver, void *opaque)
> +                  struct in_addr vnameserver, const char **vdnssearch,
> +                  void *opaque)
>  {
>      Slirp *slirp = g_malloc0(sizeof(Slirp));
> 
> @@ -233,6 +234,10 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork,
>      slirp->vdhcp_startaddr = vdhcp_start;
>      slirp->vnameserver_addr = vnameserver;
> 
> +    if (vdnssearch) {
> +        translate_dnssearch(slirp, vdnssearch);
> +    }
> +
>      slirp->opaque = opaque;
> 
>      register_savevm(NULL, "slirp", 0, 3,
> @@ -252,6 +257,7 @@ void slirp_cleanup(Slirp *slirp)
>      ip_cleanup(slirp);
>      m_cleanup(slirp);
> 
> +    g_free(slirp->vdnssearch);
>      g_free(slirp->tftp_prefix);
>      g_free(slirp->bootp_filename);
>      g_free(slirp);
> diff --git a/slirp/slirp.h b/slirp/slirp.h
> index f2c5eca..f169b97 100644
> --- a/slirp/slirp.h
> +++ b/slirp/slirp.h
> @@ -235,6 +235,8 @@ struct Slirp {
>      /* bootp/dhcp states */
>      BOOTPClient bootp_clients[NB_BOOTP_CLIENTS];
>      char *bootp_filename;
> +    size_t vdnssearch_len;
> +    uint8_t *vdnssearch;
> 
>      /* tcp states */
>      struct socket tcb;
> @@ -294,6 +296,9 @@ void lprint(const char *, ...) GCC_FMT_ATTR(1, 2);
>  #define SO_OPTIONS DO_KEEPALIVE
>  #define TCP_MAXIDLE (TCPTV_KEEPCNT * TCPTV_KEEPINTVL)
> 
> +/* dnssearch.c */
> +int translate_dnssearch(Slirp *, const char **);
> +
>  /* cksum.c */
>  int cksum(struct mbuf *m, int len);
> 
> --
> 1.7.2.5
> 

Thanks for this nice feature. Applied to slirp queue with just a few
style adjustments.

Jan

Patch

diff --git a/net/slirp.c b/net/slirp.c
index bf86a44..72ecc5e 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -136,7 +136,7 @@  static int net_slirp_init(NetClientState *peer, const char *model,
                           const char *vhostname, const char *tftp_export,
                           const char *bootfile, const char *vdhcp_start,
                           const char *vnameserver, const char *smb_export,
-                          const char *vsmbserver)
+                          const char *vsmbserver, const char **dnssearch)
 {
     /* default settings according to historic slirp */
     struct in_addr net  = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */
@@ -242,7 +242,7 @@  static int net_slirp_init(NetClientState *peer, const char *model,
     s = DO_UPCAST(SlirpState, nc, nc);
 
     s->slirp = slirp_init(restricted, net, mask, host, vhostname,
-                          tftp_export, bootfile, dhcp, dns, s);
+                          tftp_export, bootfile, dhcp, dns, dnssearch, s);
     QTAILQ_INSERT_TAIL(&slirp_stacks, s, entry);
 
     for (config = slirp_configs; config; config = config->next) {
@@ -699,6 +699,31 @@  net_init_slirp_configs(const StringList *fwd, int flags)
     }
 }
 
+static const char**
+slirp_dnssearch(const StringList *dnsname) {
+    const StringList *c = dnsname;
+    size_t i = 0, num_opts = 0;
+    const char **ret;
+
+    while (c) {
+        num_opts++;
+        c = c->next;
+    }
+
+    if (num_opts == 0) {
+        return NULL;
+    }
+
+    ret = g_malloc((num_opts + 1) * sizeof(*ret));
+    c = dnsname;
+    while (c) {
+        ret[i++] = c->value->str;
+        c = c->next;
+    }
+    ret[i] = NULL;
+    return ret;
+}
+
 int net_init_slirp(const NetClientOptions *opts, const char *name,
                    NetClientState *peer)
 {
@@ -706,6 +731,7 @@  int net_init_slirp(const NetClientOptions *opts, const char *name,
     char *vnet;
     int ret;
     const NetdevUserOptions *user;
+    const char **dnssearch;
 
     assert(opts->kind == NET_CLIENT_OPTIONS_KIND_USER);
     user = opts->user;
@@ -714,6 +740,8 @@  int net_init_slirp(const NetClientOptions *opts, const char *name,
            user->has_ip  ? g_strdup_printf("%s/24", user->ip) :
            NULL;
 
+    dnssearch = slirp_dnssearch(user->dnssearch);
+
     /* all optional fields are initialized to "all bits zero" */
 
     net_init_slirp_configs(user->hostfwd, SLIRP_CFG_HOSTFWD);
@@ -722,7 +750,7 @@  int net_init_slirp(const NetClientOptions *opts, const char *name,
     ret = net_slirp_init(peer, "user", name, user->q_restrict, vnet,
                          user->host, user->hostname, user->tftp,
                          user->bootfile, user->dhcpstart, user->dns, user->smb,
-                         user->smbserver);
+                         user->smbserver, dnssearch);
 
     while (slirp_configs) {
         config = slirp_configs;
@@ -731,6 +759,7 @@  int net_init_slirp(const NetClientOptions *opts, const char *name,
     }
 
     g_free(vnet);
+    g_free(dnssearch);
 
     return ret;
 }
diff --git a/qapi-schema.json b/qapi-schema.json
index 6fd263e..b24ce95 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2297,6 +2297,9 @@ 
 #
 # @dns: #optional guest-visible address of the virtual nameserver
 #
+# @dnssearch: #optional list of DNS suffixes to search, passed as DHCP option
+#             to the guest
+#
 # @smb: #optional root directory of the built-in SMB server
 #
 # @smbserver: #optional IP address of the built-in SMB server
@@ -2319,6 +2322,7 @@ 
     '*bootfile':  'str',
     '*dhcpstart': 'str',
     '*dns':       'str',
+    '*dnssearch': ['String'],
     '*smb':       'str',
     '*smbserver': 'str',
     '*hostfwd':   ['String'],
diff --git a/qemu-options.hx b/qemu-options.hx
index 46f0539..a6efc56 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1282,8 +1282,8 @@  DEF("net", HAS_ARG, QEMU_OPTION_net,
     "                create a new Network Interface Card and connect it to VLAN 'n'\n"
 #ifdef CONFIG_SLIRP
     "-net user[,vlan=n][,name=str][,net=addr[/mask]][,host=addr][,restrict=on|off]\n"
-    "         [,hostname=host][,dhcpstart=addr][,dns=addr][,tftp=dir][,bootfile=f]\n"
-    "         [,hostfwd=rule][,guestfwd=rule]"
+    "         [,hostname=host][,dhcpstart=addr][,dns=addr][,dnssearch=domain][,tftp=dir]\n"
+    "         [,bootfile=f][,hostfwd=rule][,guestfwd=rule]"
 #ifndef _WIN32
                                              "[,smb=dir[,smbserver=addr]]\n"
 #endif
@@ -1392,7 +1392,7 @@  able to contact the host and no guest IP packets will be routed over the host
 to the outside. This option does not affect any explicitly set forwarding rules.
 
 @item hostname=@var{name}
-Specifies the client hostname reported by the builtin DHCP server.
+Specifies the client hostname reported by the built-in DHCP server.
 
 @item dhcpstart=@var{addr}
 Specify the first of the 16 IPs the built-in DHCP server can assign. Default
@@ -1403,6 +1403,18 @@  Specify the guest-visible address of the virtual nameserver. The address must
 be different from the host address. Default is the 3rd IP in the guest network,
 i.e. x.x.x.3.
 
+@item dnssearch=@var{domain}
+Provides an entry for the domain-search list sent by the built-in
+DHCP server. More than one domain suffix can be transmitted by specifying
+this option multiple times. If supported, this will cause the guest to
+automatically try to append the given domain suffix(es) in case a domain name
+can not be resolved.
+
+Example:
+@example
+qemu -net user,dnssearch=mgmt.example.org,dnssearch=example.org [...]
+@end example
+
 @item tftp=@var{dir}
 When using the user mode network stack, activate a built-in TFTP
 server. The files in @var{dir} will be exposed as the root of a TFTP server.
diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs
index bb43d3c..2daa9dc 100644
--- a/slirp/Makefile.objs
+++ b/slirp/Makefile.objs
@@ -1,3 +1,3 @@ 
-common-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o
+common-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o dnssearch.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 bootp.o tftp.o arp_table.o
diff --git a/slirp/bootp.c b/slirp/bootp.c
index 64eac7d..b7db9fa 100644
--- a/slirp/bootp.c
+++ b/slirp/bootp.c
@@ -287,6 +287,18 @@  static void bootp_reply(Slirp *slirp, const struct bootp_t *bp)
             memcpy(q, slirp->client_hostname, val);
             q += val;
         }
+
+        if (slirp->vdnssearch) {
+            size_t spaceleft = sizeof(rbp->bp_vend) - (q - rbp->bp_vend);
+            val = slirp->vdnssearch_len;
+            if (val + 1 > spaceleft) {
+                g_warning("DHCP packet size exceeded, "
+                    "omitting domain-search option.");
+            } else {
+                memcpy(q, slirp->vdnssearch, val);
+                q += val;
+            }
+        }
     } else {
         static const char nak_msg[] = "requested address not available";
 
diff --git a/slirp/dnssearch.c b/slirp/dnssearch.c
new file mode 100644
index 0000000..898f7d8
--- /dev/null
+++ b/slirp/dnssearch.c
@@ -0,0 +1,320 @@ 
+/*
+ * Domain search option for DHCP (RFC 3397)
+ *
+ * Copyright (c) 2012 Klaus Stengel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <glib.h>
+#include "slirp.h"
+
+static const uint8_t RFC3397_OPT_DOMAIN_SEARCH = 119;
+static const uint8_t MAX_OPT_LEN = 255;
+static const uint8_t OPT_HEADER_LEN = 2;
+static const uint8_t REFERENCE_LEN = 2;
+
+struct compact_domain;
+
+typedef struct compact_domain {
+    struct compact_domain *self;
+    struct compact_domain *refdom;
+    uint8_t *labels;
+    size_t len;
+    size_t common_octets;
+} CompactDomain;
+
+static size_t
+domain_suffix_diffoff(const CompactDomain *a, const CompactDomain *b)
+{
+    size_t la = a->len, lb = b->len;
+    uint8_t *da = a->labels + la, *db = b->labels + lb;
+    size_t i, lm = (la < lb) ? la : lb;
+
+    for (i = 0; i < lm; i++) {
+        da--; db--;
+        if (*da != *db) {
+            break;
+        }
+    }
+    return i;
+}
+
+static int
+domain_suffix_ord(const void *cva, const void *cvb)
+{
+    const CompactDomain *a = cva, *b = cvb;
+    size_t la = a->len, lb = b->len;
+    size_t doff = domain_suffix_diffoff(a, b);
+    uint8_t ca = a->labels[la - doff];
+    uint8_t cb = b->labels[lb - doff];
+
+    if (ca < cb) {
+        return -1;
+    }
+    if (ca > cb) {
+        return 1;
+    }
+    if (la < lb) {
+        return -1;
+    }
+    if (la > lb) {
+        return 1;
+    }
+    return 0;
+}
+
+static size_t
+domain_common_label(CompactDomain *a, CompactDomain *b)
+{
+    size_t res, doff = domain_suffix_diffoff(a, b);
+    uint8_t *first_eq_pos = a->labels + (a->len - doff);
+    uint8_t *label = a->labels;
+
+    while (*label && label < first_eq_pos) {
+        label += *label + 1;
+    }
+    res = a->len - (label - a->labels);
+    /* only report if it can help to reduce the packet size */
+    return (res > REFERENCE_LEN) ? res : 0;
+}
+
+static void
+domain_fixup_order(CompactDomain *cd, size_t n)
+{
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        CompactDomain *cur = cd + i, *next = cd[i].self;
+
+        while (!cur->common_octets) {
+            CompactDomain *tmp = next->self; /* backup target value */
+
+            next->self = cur;
+            cur->common_octets++;
+
+            cur = next;
+            next = tmp;
+        }
+    }
+}
+
+static void
+domain_mklabels(CompactDomain *cd, const char *input)
+{
+    uint8_t *len_marker = cd->labels;
+    uint8_t *output = len_marker; /* pre-incremented */
+    const char *in = input;
+    char cur_chr;
+    size_t len = 0;
+
+    if (cd->len == 0) {
+        goto fail;
+    }
+    cd->len++;
+
+    do {
+        cur_chr = *in++;
+        if (cur_chr == '.' || cur_chr == '\0') {
+            len = output - len_marker;
+            if ((len == 0 && cur_chr == '.') || len >= 64) {
+                goto fail;
+            }
+            *len_marker = len;
+
+            output++;
+            len_marker = output;
+        } else {
+            output++;
+            *output = cur_chr;
+        }
+    } while (cur_chr != '\0');
+
+    /* ensure proper zero-termination */
+    if (len != 0) {
+        *len_marker = 0;
+        cd->len++;
+    }
+    return;
+
+fail:
+    g_warning("failed to parse domain name '%s'\n", input);
+    cd->len = 0;
+}
+
+static void
+domain_mkxrefs(CompactDomain *doms, CompactDomain *last, size_t depth)
+{
+    CompactDomain *i = doms, *target = doms;
+
+    do {
+        if (i->labels < target->labels) {
+            target = i;
+        }
+    } while (i++ != last);
+
+    for (i = doms; i != last; i++) {
+        CompactDomain *group_last;
+        size_t next_depth;
+
+        if (i->common_octets == depth) {
+            continue;
+        }
+
+        next_depth = -1;
+        for (group_last = i; group_last != last; group_last++) {
+            size_t co = group_last->common_octets;
+            if (co <= depth) {
+                break;
+            }
+            if (co < next_depth) {
+                next_depth = co;
+            }
+        }
+        domain_mkxrefs(i, group_last, next_depth);
+
+        i = group_last;
+        if (i == last) {
+            break;
+        }
+    }
+
+    if (depth == 0) {
+        return;
+    }
+
+    i = doms;
+    do {
+        if (i != target && i->refdom == NULL) {
+            i->refdom = target;
+            i->common_octets = depth;
+        }
+    } while (i++ != last);
+}
+
+static size_t
+domain_compactify(CompactDomain *domains, size_t n)
+{
+    uint8_t *start = domains->self->labels, *outptr = start;
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        CompactDomain *cd = domains[i].self;
+        CompactDomain *rd = cd->refdom;
+
+        if (rd != NULL) {
+            size_t moff = (rd->labels - start)
+                    + (rd->len - cd->common_octets);
+            if (moff < 0x3FFFu) {
+                cd->len -= cd->common_octets - 2;
+                cd->labels[cd->len - 1] = moff & 0xFFu;
+                cd->labels[cd->len - 2] = 0xC0u | (moff >> 8);
+            }
+        }
+
+        if (cd->labels != outptr) {
+            memmove(outptr, cd->labels, cd->len);
+            cd->labels = outptr;
+        }
+        outptr += cd->len;
+    }
+    return outptr - start;
+}
+
+int
+translate_dnssearch(Slirp *s, const char **names)
+{
+    size_t blocks, bsrc_start, bsrc_end, bdst_start;
+    size_t i, num_domains, memreq = 0;
+    uint8_t *result = NULL, *outptr;
+    CompactDomain *domains = NULL;
+    const char **nameptr = names;
+
+    while (*nameptr != NULL) {
+        nameptr++;
+    }
+
+    num_domains = nameptr - names;
+    if (num_domains == 0) {
+        return -2;
+    }
+
+    domains = g_malloc(num_domains * sizeof(*domains));
+
+    for (i = 0; i < num_domains; i++) {
+        size_t nlen = strlen(names[i]);
+        memreq += nlen + 2; /* 1 zero octet + 1 label length octet */
+        domains[i].self = domains + i;
+        domains[i].len = nlen;
+        domains[i].common_octets = 0;
+        domains[i].refdom = NULL;
+    }
+
+    /* reserve extra 2 header bytes for each 255 bytes of output */
+    memreq += ((memreq + MAX_OPT_LEN - 1) / MAX_OPT_LEN) * OPT_HEADER_LEN;
+    result = g_malloc(memreq * sizeof(*result));
+
+    outptr = result;
+    for (i = 0; i < num_domains; i++) {
+        domains[i].labels = outptr;
+        domain_mklabels(domains + i, names[i]);
+        outptr += domains[i].len;
+    }
+
+    if (outptr == result) {
+        g_free(domains);
+        g_free(result);
+        return -1;
+    }
+
+    qsort(domains, num_domains, sizeof(*domains), domain_suffix_ord);
+    domain_fixup_order(domains, num_domains);
+
+    for (i = 1; i < num_domains; i++) {
+        size_t cl = domain_common_label(domains + i - 1, domains + i);
+        domains[i - 1].common_octets = cl;
+    }
+
+    domain_mkxrefs(domains, domains + num_domains - 1, 0);
+    memreq = domain_compactify(domains, num_domains);
+
+    blocks = (memreq + MAX_OPT_LEN - 1) / MAX_OPT_LEN;
+    bsrc_end = memreq;
+    bsrc_start = (blocks - 1) * MAX_OPT_LEN;
+    bdst_start = bsrc_start + blocks * OPT_HEADER_LEN;
+    memreq += blocks * OPT_HEADER_LEN;
+
+    while (blocks--) {
+        size_t len = bsrc_end - bsrc_start;
+        memmove(result + bdst_start, result + bsrc_start, len);
+        result[bdst_start - 2] = RFC3397_OPT_DOMAIN_SEARCH;
+        result[bdst_start - 1] = len;
+        bsrc_end = bsrc_start;
+        bsrc_start -= MAX_OPT_LEN;
+        bdst_start -= MAX_OPT_LEN + OPT_HEADER_LEN;
+    }
+
+    g_free(domains);
+    s->vdnssearch = result;
+    s->vdnssearch_len = memreq;
+    return 0;
+}
diff --git a/slirp/libslirp.h b/slirp/libslirp.h
index 9b471b5..64878c4 100644
--- a/slirp/libslirp.h
+++ b/slirp/libslirp.h
@@ -12,7 +12,8 @@  Slirp *slirp_init(int restricted, struct in_addr vnetwork,
                   struct in_addr vnetmask, struct in_addr vhost,
                   const char *vhostname, const char *tftp_path,
                   const char *bootfile, struct in_addr vdhcp_start,
-                  struct in_addr vnameserver, void *opaque);
+                  struct in_addr vnameserver, const char **vdnssearch,
+                   void *opaque);
 void slirp_cleanup(Slirp *slirp);
 
 void slirp_update_timeout(uint32_t *timeout);
diff --git a/slirp/slirp.c b/slirp/slirp.c
index 38e0a21..3395d50 100644
--- a/slirp/slirp.c
+++ b/slirp/slirp.c
@@ -203,7 +203,8 @@  Slirp *slirp_init(int restricted, struct in_addr vnetwork,
                   struct in_addr vnetmask, struct in_addr vhost,
                   const char *vhostname, const char *tftp_path,
                   const char *bootfile, struct in_addr vdhcp_start,
-                  struct in_addr vnameserver, void *opaque)
+                  struct in_addr vnameserver, const char **vdnssearch,
+                  void *opaque)
 {
     Slirp *slirp = g_malloc0(sizeof(Slirp));
 
@@ -233,6 +234,10 @@  Slirp *slirp_init(int restricted, struct in_addr vnetwork,
     slirp->vdhcp_startaddr = vdhcp_start;
     slirp->vnameserver_addr = vnameserver;
 
+    if (vdnssearch) {
+        translate_dnssearch(slirp, vdnssearch);
+    }
+
     slirp->opaque = opaque;
 
     register_savevm(NULL, "slirp", 0, 3,
@@ -252,6 +257,7 @@  void slirp_cleanup(Slirp *slirp)
     ip_cleanup(slirp);
     m_cleanup(slirp);
 
+    g_free(slirp->vdnssearch);
     g_free(slirp->tftp_prefix);
     g_free(slirp->bootp_filename);
     g_free(slirp);
diff --git a/slirp/slirp.h b/slirp/slirp.h
index f2c5eca..f169b97 100644
--- a/slirp/slirp.h
+++ b/slirp/slirp.h
@@ -235,6 +235,8 @@  struct Slirp {
     /* bootp/dhcp states */
     BOOTPClient bootp_clients[NB_BOOTP_CLIENTS];
     char *bootp_filename;
+    size_t vdnssearch_len;
+    uint8_t *vdnssearch;
 
     /* tcp states */
     struct socket tcb;
@@ -294,6 +296,9 @@  void lprint(const char *, ...) GCC_FMT_ATTR(1, 2);
 #define SO_OPTIONS DO_KEEPALIVE
 #define TCP_MAXIDLE (TCPTV_KEEPCNT * TCPTV_KEEPINTVL)
 
+/* dnssearch.c */
+int translate_dnssearch(Slirp *, const char **);
+
 /* cksum.c */
 int cksum(struct mbuf *m, int len);