This patch will allow the user to specify classless static routes for the replies from the built-in DHCP server.
Signed-off-by: Benjamin Drung <benjamin.dr...@profitbricks.com> --- net/slirp.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- qapi/net.json | 4 ++++ qemu-options.hx | 13 +++++++++++- slirp/bootp.c | 23 ++++++++++++++++++++ slirp/bootp.h | 2 ++ slirp/libslirp.h | 9 +++++++- slirp/slirp.c | 7 +++++- slirp/slirp.h | 2 ++ 8 files changed, 119 insertions(+), 6 deletions(-) diff --git a/net/slirp.c b/net/slirp.c index 8c08e5644f..78df361485 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -158,7 +158,7 @@ static int net_slirp_init(NetClientState *peer, const char *model, const char *vnameserver, const char *vnameserver6, const char *smb_export, const char *vsmbserver, const char **dnssearch, const char *vdomainname, - Error **errp) + const StringList *vroutes, Error **errp) { /* default settings according to historic slirp */ struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ @@ -177,8 +177,12 @@ static int net_slirp_init(NetClientState *peer, const char *model, char buf[20]; uint32_t addr; int shift; + int i = 0; char *end; + unsigned int route_count = 0; struct slirp_config_str *config; + struct StaticRoute *routes = NULL; + const StringList *iter; if (!ipv4 && (vnetwork || vhost || vnameserver)) { error_setg(errp, "IPv4 disabled but netmask/host/dns provided"); @@ -365,6 +369,58 @@ static int net_slirp_init(NetClientState *peer, const char *model, return -1; } + iter = vroutes; + while (iter) { + route_count++; + iter = iter->next; + } + routes = g_malloc(route_count * sizeof(StaticRoute)); + + iter = vroutes; + while(iter != NULL) { + char buf2[20]; + const char *gateway = iter->value->str; + const char *mask; + char *end; + long mask_width; + + // Split "subnet/mask:gateway" into its components + if (get_str_sep(buf2, sizeof(buf2), &gateway, ':') < 0) { + error_setg(errp, "Failed to parse route: No colon found in '%s'", + iter->value->str); + return -1; + } + mask = buf2; + if (get_str_sep(buf, sizeof(buf), &mask, '/') < 0) { + error_setg(errp, "Failed to parse route: No slash found in '%s'", + mask); + return -1; + } + if (!inet_aton(buf, &routes[i].subnet)) { + error_setg(errp, "Failed to parse route subnet '%s'", buf); + return -1; + } + + mask_width = strtol(mask, &end, 10); + if (*end != '\0') { + error_setg(errp, + "Failed to parse netmask '%s' (trailing chars)", mask); + return -1; + } else if (mask_width < 0 || mask_width > 32) { + error_setg(errp, + "Invalid netmask provided (must be in range 0-32)"); + return -1; + } + routes[i].mask_width = (uint8_t)mask_width; + + if (!inet_aton(gateway, &routes[i].gateway)) { + error_setg(errp, "Failed to parse route gateway '%s'", gateway); + return -1; + } + + iter = iter->next; + i++; + } nc = qemu_new_net_client(&net_slirp_info, peer, model, name); @@ -377,7 +433,8 @@ static int net_slirp_init(NetClientState *peer, const char *model, s->slirp = slirp_init(restricted, ipv4, net, mask, host, ipv6, ip6_prefix, vprefix6_len, ip6_host, vhostname, tftp_export, bootfile, dhcp, - dns, ip6_dns, dnssearch, vdomainname, s); + dns, ip6_dns, dnssearch, vdomainname, + route_count, routes, s); QTAILQ_INSERT_TAIL(&slirp_stacks, s, entry); for (config = slirp_configs; config; config = config->next) { @@ -409,6 +466,7 @@ static int net_slirp_init(NetClientState *peer, const char *model, return 0; error: + g_free(routes); qemu_del_net_client(nc); return -1; } @@ -964,7 +1022,8 @@ int net_init_slirp(const Netdev *netdev, const char *name, user->ipv6_host, user->hostname, user->tftp, user->bootfile, user->dhcpstart, user->dns, user->ipv6_dns, user->smb, - user->smbserver, dnssearch, user->domainname, errp); + user->smbserver, dnssearch, user->domainname, + user->route, errp); while (slirp_configs) { config = slirp_configs; diff --git a/qapi/net.json b/qapi/net.json index 9dfd34cafa..8a85debd92 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -163,6 +163,9 @@ # @domainname: guest-visible domain name of the virtual nameserver # (since 2.12) # +# @route: guest-visible static classless route of the virtual nameserver +# (since 2.12) +# # @ipv6-prefix: IPv6 network prefix (default is fec0::) (since # 2.6). The network prefix is given in the usual # hexadecimal IPv6 address notation. @@ -201,6 +204,7 @@ '*dns': 'str', '*dnssearch': ['String'], '*domainname': 'str', + '*route': ['String'], '*ipv6-prefix': 'str', '*ipv6-prefixlen': 'int', '*ipv6-host': 'str', diff --git a/qemu-options.hx b/qemu-options.hx index c000ef454e..e4bb5919ab 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1907,7 +1907,7 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, " [,ipv6[=on|off]][,ipv6-net=addr[/int]][,ipv6-host=addr]\n" " [,restrict=on|off][,hostname=host][,dhcpstart=addr]\n" " [,dns=addr][,ipv6-dns=addr][,dnssearch=domain][,domainname=domain]\n" - " [,tftp=dir][,bootfile=f][,hostfwd=rule][,guestfwd=rule]" + " [,route=addr/mask:gateway][,tftp=dir][,bootfile=f][,hostfwd=rule][,guestfwd=rule]" #ifndef _WIN32 "[,smb=dir[,smbserver=addr]]\n" #endif @@ -2119,6 +2119,17 @@ qemu -net user,dnssearch=mgmt.example.org,dnssearch=example.org [...] @item domainname=@var{domain} Specifies the client domain name reported by the built-in DHCP server. +@item route=@var{addr}/@var{mask}:@var{gateway} +Provides an entry for the classless static routes list sent by the built-in +DHCP server. More than one route can be transmitted by specifying +this option multiple times. If supported, this will cause the guest to +automatically set the given static routes instead of the given default gateway. + +Example: +@example +qemu -net user,route=10.0.2.0/24:10.0.2.2,route=192.168.0.0/16:10.0.2.2 [...] +@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/bootp.c b/slirp/bootp.c index 9e7b53ba94..a694a43189 100644 --- a/slirp/bootp.c +++ b/slirp/bootp.c @@ -306,6 +306,29 @@ static void bootp_reply(Slirp *slirp, const struct bootp_t *bp) q += val; } + if (slirp->route_count > 0) { + uint8_t option_length = 0; + uint8_t significant_octets; + + for (int i = 0; i < slirp->route_count; i++) { + significant_octets = slirp->routes[i].mask_width / 8 + + (slirp->routes[i].mask_width % 8 > 0); + option_length += significant_octets + 5; + } + + *q++ = RFC3442_CLASSLESS_STATIC_ROUTE; + *q++ = option_length; + for (int i = 0; i < slirp->route_count; i++) { + significant_octets = slirp->routes[i].mask_width / 8 + + (slirp->routes[i].mask_width % 8 > 0); + *q++ = slirp->routes[i].mask_width; + memcpy(q, &slirp->routes[i].subnet.s_addr, significant_octets); + q += significant_octets; + memcpy(q, &slirp->routes[i].gateway.s_addr, 4); + q += 4; + } + } + if (slirp->vdnssearch) { size_t spaceleft = sizeof(rbp->bp_vend) - (q - rbp->bp_vend); val = slirp->vdnssearch_len; diff --git a/slirp/bootp.h b/slirp/bootp.h index 394525733e..60bef4e80d 100644 --- a/slirp/bootp.h +++ b/slirp/bootp.h @@ -71,6 +71,8 @@ #define RFC2132_RENEWAL_TIME 58 #define RFC2132_REBIND_TIME 59 +#define RFC3442_CLASSLESS_STATIC_ROUTE 121 + #define DHCPDISCOVER 1 #define DHCPOFFER 2 #define DHCPREQUEST 3 diff --git a/slirp/libslirp.h b/slirp/libslirp.h index 740408a96e..3d2e395b08 100644 --- a/slirp/libslirp.h +++ b/slirp/libslirp.h @@ -5,6 +5,12 @@ typedef struct Slirp Slirp; +typedef struct StaticRoute { + struct in_addr subnet; + uint8_t mask_width; + struct in_addr gateway; +} StaticRoute; + int get_dns_addr(struct in_addr *pdns_addr); int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id); @@ -16,7 +22,8 @@ Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, const char *tftp_path, const char *bootfile, struct in_addr vdhcp_start, struct in_addr vnameserver, struct in6_addr vnameserver6, const char **vdnssearch, - const char *vdomainname, void *opaque); + const char *vdomainname, unsigned int routes_count, + const StaticRoute *vroutes, void *opaque); void slirp_cleanup(Slirp *slirp); void slirp_pollfds_fill(GArray *pollfds, uint32_t *timeout); diff --git a/slirp/slirp.c b/slirp/slirp.c index 4f29753444..4e5f249eeb 100644 --- a/slirp/slirp.c +++ b/slirp/slirp.c @@ -286,7 +286,8 @@ Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, const char *tftp_path, const char *bootfile, struct in_addr vdhcp_start, struct in_addr vnameserver, struct in6_addr vnameserver6, const char **vdnssearch, - const char *vdomainname, void *opaque) + const char *vdomainname, unsigned int route_count, + const StaticRoute *vroutes, void *opaque) { Slirp *slirp = g_malloc0(sizeof(Slirp)); @@ -321,6 +322,9 @@ Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, slirp->vdhcp_startaddr = vdhcp_start; slirp->vnameserver_addr = vnameserver; slirp->vnameserver_addr6 = vnameserver6; + slirp->route_count = route_count; + slirp->routes = g_malloc(route_count * sizeof(StaticRoute)); + memcpy(slirp->routes, vroutes, route_count * sizeof(StaticRoute)); if (vdnssearch) { translate_dnssearch(slirp, vdnssearch); @@ -351,6 +355,7 @@ void slirp_cleanup(Slirp *slirp) g_free(slirp->tftp_prefix); g_free(slirp->bootp_filename); g_free(slirp->vdomainname); + g_free(slirp->routes); g_free(slirp); } diff --git a/slirp/slirp.h b/slirp/slirp.h index 10b410898a..3c81b7f7cc 100644 --- a/slirp/slirp.h +++ b/slirp/slirp.h @@ -194,6 +194,8 @@ struct Slirp { size_t vdnssearch_len; uint8_t *vdnssearch; char *vdomainname; + unsigned int route_count; + struct StaticRoute *routes; /* tcp states */ struct socket tcb; -- 2.14.1