From 1a89b60885ccc2abf7cc50275fcee70d0347425e Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Mon, 12 Nov 2012 17:59:49 +0100 Subject: [PATCH 1/2] slirp: Don't crash on packets from 0.0.0.0/8. LWIP can generate packets with a source of 0.0.0.0, which triggers an assertion failure in arp_table_add(). Instead of crashing, simply return to avoid adding an invalid ARP table entry. Signed-off-by: Nickolai Zeldovich Signed-off-by: Jan Kiszka --- slirp/arp_table.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slirp/arp_table.c b/slirp/arp_table.c index 5d7b8acd1d..bf698c1ac5 100644 --- a/slirp/arp_table.c +++ b/slirp/arp_table.c @@ -38,7 +38,9 @@ void arp_table_add(Slirp *slirp, uint32_t ip_addr, uint8_t ethaddr[ETH_ALEN]) ethaddr[3], ethaddr[4], ethaddr[5])); /* Check 0.0.0.0/8 invalid source-only addresses */ - assert((ip_addr & htonl(~(0xf << 28))) != 0); + if ((ip_addr & htonl(~(0xf << 28))) == 0) { + return; + } if (ip_addr == 0xffffffff || ip_addr == broadcast_addr) { /* Do not register broadcast addresses */ From 63d2960bc46f63137d7fbd5ff56b81e54710d195 Mon Sep 17 00:00:00 2001 From: Klaus Stengel Date: Sat, 27 Oct 2012 19:53:39 +0200 Subject: [PATCH 2/2] slirp: Add domain-search option to slirp's DHCP server 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. [Jan: tiny style adjustments] Signed-off-by: Klaus Stengel Signed-off-by: Jan Kiszka --- net/slirp.c | 35 ++++- qapi-schema.json | 4 + qemu-options.hx | 18 ++- slirp/Makefile.objs | 2 +- slirp/bootp.c | 12 ++ slirp/dnssearch.c | 314 ++++++++++++++++++++++++++++++++++++++++++++ slirp/libslirp.h | 3 +- slirp/slirp.c | 8 +- slirp/slirp.h | 5 + 9 files changed, 392 insertions(+), 9 deletions(-) create mode 100644 slirp/dnssearch.c diff --git a/net/slirp.c b/net/slirp.c index bf86a446c3..afb52c3af1 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 542e3ac069..5dfa052391 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -2404,6 +2404,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 @@ -2426,6 +2429,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 fe8f15c541..a165cff071 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1318,8 +1318,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 @@ -1428,7 +1428,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 @@ -1439,6 +1439,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 bb43d3c08c..2daa9dc58d 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 64eac7d101..b7db9fa335 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 0000000000..4c9064ecb6 --- /dev/null +++ b/slirp/dnssearch.c @@ -0,0 +1,314 @@ +/* + * 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 +#include +#include +#include +#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 9b471b5053..49609c2ad7 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 38e0a2193a..3395d509a2 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 f2c5eca892..0107b07e66 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 *s, const char ** names); + /* cksum.c */ int cksum(struct mbuf *m, int len);