Hi, "local interface" (-L) is an amazing feature and I use it every day; but it is IPv4-only and now I realized that I need IPv6 too.
The attached diff implements IPv6 support for local interfaces. A few notes and limitations: - Unlike the embedded IPv4 DHCP server, it does not implement a DHCPv6/rtsol responder in vmd. It relies on a rad(8) change that I've sent earlier today. Configuring rad is easy enough and IPv6 users are used to jumping though extra hoops: use my rad diff and run the daemon with "interface tap" in /etc/rad.conf. - It is disabled by default. You can enable it with a global option "local inet6" (to get a runtime random fd00::/8 ULA prefix) or "local inet6 prefix xxx::/64" (to configure your own prefix). For simplicity, the prefix is a global and not a per-VM option. - Once enabled, IPv6 will be enabled and an additional IPv6 address configured on the host's VM tap(4) interface whenever you create it with "local interface" / -L. - The IPv6 address is derived from the configured prefix and the IPv4 address of the local interface on the VM side. This way it embeds the VM and interface Id and you can even pf af-to it to IPv4 again! ``` vm_priv_ifconfig: interface tap0 address 100.64.9.2/31 vm_priv_ifconfig: interface tap0 address fdfc:6be5:806:930a:6440:903:0:1/96 ^^^^^^^^ 100.64.9.3 ``` - The resulting address is suitable for rad(8) - just run "ifconfig vio0 inet6 autoconf" in the guest and you'll get your /96 IPv6 address. ``` vio0: flags=208b43<UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST,AUTOCONF6> mtu 1500 lladdr fe:e1:bb:d1:88:4f index 1 priority 0 llprio 3 groups: egress media: Ethernet autoselect status: active inet 100.64.9.3 netmask 0xfffffffe inet6 fe80::7a1f:6128:505d:4ea5%vio0 prefixlen 64 scopeid 0x1 inet6 fdfc:6be5:806:930a:6440:903:b11c:516 prefixlen 96 autoconf autoconfprivacy pltime 86063 vltime 604794 inet6 fdfc:6be5:806:930a:6440:903:d457:347a prefixlen 96 autoconf pltime 604794 vltime 2591994 ``` - The only problem is that the IPv6 address is nondeterministic where you cannot guess the VM's IPv6 address "from the outside" (32 bits of entropy for the guest IP). It tried it with a /127 prefix but slaacd/rad don't handle this very well as it has a 50% chance of creating a duplicate with the host's IP. I didn't attempt to "fix" it as it would probably be incompatible with other rtsol clients. So I eventually decided that this is not important as I would still use the IPv4 address to log in - the IPv6 address is primarily used for outbound connections. OK? Reyk Index: usr.sbin/vmd/config.c =================================================================== RCS file: /cvs/src/usr.sbin/vmd/config.c,v retrieving revision 1.54 diff -u -p -u -p -r1.54 config.c --- usr.sbin/vmd/config.c 26 Oct 2018 11:24:45 -0000 1.54 +++ usr.sbin/vmd/config.c 16 Nov 2018 15:53:10 -0000 @@ -42,17 +42,40 @@ /* Supported bridge types */ const char *vmd_descsw[] = { "switch", "bridge", NULL }; +int config_init_localprefix(struct vmd_config *); + +int +config_init_localprefix(struct vmd_config *cfg) +{ + struct sockaddr_in6 *sin6; + + if (host(VMD_DHCP_PREFIX, &cfg->cfg_localprefix) == -1) + return (-1); + + if (host(VMD_ULA_PREFIX, &cfg->cfg_localprefix6) == -1) + return (-1); + /* Randomize the 56 bits "Global ID" and "Subnet ID" */ + sin6 = ss2sin6(&cfg->cfg_localprefix6.ss); + arc4random_buf(&sin6->sin6_addr.s6_addr[1], 7); + + /* IPv6 is disabled by default */ + cfg->cfg_flags &= ~VMD_CFG_INET6; + + return (0); +} + int config_init(struct vmd *env) { - struct privsep *ps = &env->vmd_ps; - unsigned int what; + struct privsep *ps = &env->vmd_ps; + unsigned int what; /* Global configuration */ ps->ps_what[PROC_PARENT] = CONFIG_ALL; ps->ps_what[PROC_VMM] = CONFIG_VMS; - if (host(VMD_DHCP_PREFIX, &env->vmd_cfg.cfg_localprefix) == -1) + /* Local prefix */ + if (config_init_localprefix(&env->vmd_cfg) == -1) return (-1); /* Other configuration */ @@ -90,7 +113,7 @@ config_purge(struct vmd *env, unsigned i __func__, ps->ps_title[privsep_process]); /* Reset global configuration (prefix was verified before) */ - (void)host(VMD_DHCP_PREFIX, &env->vmd_cfg.cfg_localprefix); + config_init_localprefix(&env->vmd_cfg); /* Reset other configuration */ what = ps->ps_what[privsep_process] & reset; Index: usr.sbin/vmd/dhcp.c =================================================================== RCS file: /cvs/src/usr.sbin/vmd/dhcp.c,v retrieving revision 1.5 diff -u -p -u -p -r1.5 dhcp.c --- usr.sbin/vmd/dhcp.c 17 Aug 2018 07:12:28 -0000 1.5 +++ usr.sbin/vmd/dhcp.c 16 Nov 2018 15:53:10 -0000 @@ -109,7 +109,7 @@ dhcp_request(struct vionet_dev *dev, cha resp.xid = req.xid; if ((client_addr.s_addr = - vm_priv_addr(&env->vmd_cfg.cfg_localprefix, + vm_priv_addr(&env->vmd_cfg, dev->vm_vmid, dev->idx, 1)) == 0) return (-1); memcpy(&resp.yiaddr, &client_addr, @@ -119,7 +119,7 @@ dhcp_request(struct vionet_dev *dev, cha ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT); if ((server_addr.s_addr = - vm_priv_addr(&env->vmd_cfg.cfg_localprefix, + vm_priv_addr(&env->vmd_cfg, dev->vm_vmid, dev->idx, 0)) == 0) return (-1); memcpy(&resp.siaddr, &server_addr, Index: usr.sbin/vmd/parse.y =================================================================== RCS file: /cvs/src/usr.sbin/vmd/parse.y,v retrieving revision 1.48 diff -u -p -u -p -r1.48 parse.y --- usr.sbin/vmd/parse.y 1 Nov 2018 00:18:44 -0000 1.48 +++ usr.sbin/vmd/parse.y 16 Nov 2018 15:53:10 -0000 @@ -120,9 +120,9 @@ typedef struct { %token INCLUDE ERROR -%token ADD ALLOW BOOT CDROM DISABLE DISK DOWN ENABLE FORMAT GROUP INSTANCE -%token INTERFACE LLADDR LOCAL LOCKED MEMORY NIFS OWNER PATH PREFIX RDOMAIN -%token SIZE SOCKET SWITCH UP VM VMID +%token ADD ALLOW BOOT CDROM DISABLE DISK DOWN ENABLE FORMAT GROUP INET6 +%token INSTANCE INTERFACE LLADDR LOCAL LOCKED MEMORY NIFS OWNER PATH PREFIX +%token RDOMAIN SIZE SOCKET SWITCH UP VM VMID %token <v.number> NUMBER %token <v.string> STRING %type <v.lladdr> lladdr @@ -181,10 +181,26 @@ varset : STRING '=' STRING { } ; -main : LOCAL PREFIX STRING { +main : LOCAL INET6 { + env->vmd_cfg.cfg_flags |= VMD_CFG_INET6; + } + | LOCAL INET6 PREFIX STRING { + struct address h; + + if (host($4, &h) == -1 || + h.ss.ss_family != AF_INET6 || + h.prefixlen > 64 || h.prefixlen < 0) { + yyerror("invalid local inet6 prefix: %s", $4); + free($4); + YYERROR; + } + + env->vmd_cfg.cfg_flags |= VMD_CFG_INET6; + memcpy(&env->vmd_cfg.cfg_localprefix6, &h, sizeof(h)); + } + | LOCAL PREFIX STRING { struct address h; - /* The local prefix is IPv4-only */ if (host($3, &h) == -1 || h.ss.ss_family != AF_INET || h.prefixlen > 32 || h.prefixlen < 0) { @@ -747,6 +763,7 @@ lookup(char *s) { "group", GROUP }, { "id", VMID }, { "include", INCLUDE }, + { "inet6", INET6 }, { "instance", INSTANCE }, { "interface", INTERFACE }, { "interfaces", NIFS }, Index: usr.sbin/vmd/priv.c =================================================================== RCS file: /cvs/src/usr.sbin/vmd/priv.c,v retrieving revision 1.13 diff -u -p -u -p -r1.13 priv.c --- usr.sbin/vmd/priv.c 11 Nov 2017 02:50:07 -0000 1.13 +++ usr.sbin/vmd/priv.c 16 Nov 2018 15:53:10 -0000 @@ -27,6 +27,8 @@ #include <net/if.h> #include <netinet/in.h> #include <netinet/if_ether.h> +#include <netinet6/in6_var.h> +#include <netinet6/nd6.h> #include <net/if_bridge.h> #include <arpa/inet.h> @@ -70,6 +72,10 @@ priv_run(struct privsep *ps, struct priv /* Open our own socket for generic interface ioctls */ if ((env->vmd_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) fatal("socket"); + + /* But we need a different fd for IPv6 */ + if ((env->vmd_fd6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) + fatal("socket6"); } int @@ -83,6 +89,8 @@ priv_dispatch_parent(int fd, struct priv struct ifbreq ifbr; struct ifgroupreq ifgr; struct ifaliasreq ifra; + struct in6_aliasreq in6_ifra; + struct if_afreq ifar; char type[IF_NAMESIZE]; switch (imsg->hdr.type) { @@ -94,6 +102,7 @@ priv_dispatch_parent(int fd, struct priv case IMSG_VMDOP_PRIV_IFDOWN: case IMSG_VMDOP_PRIV_IFGROUP: case IMSG_VMDOP_PRIV_IFADDR: + case IMSG_VMDOP_PRIV_IFADDR6: IMSG_SIZE_CHECK(imsg, &vfr); memcpy(&vfr, imsg->data, sizeof(vfr)); @@ -177,22 +186,64 @@ priv_dispatch_parent(int fd, struct priv case IMSG_VMDOP_PRIV_IFADDR: memset(&ifra, 0, sizeof(ifra)); + if (vfr.vfr_addr.ss_family != AF_INET || + vfr.vfr_addr.ss_family != vfr.vfr_mask.ss_family) + fatalx("%s: invalid address family", __func__); + /* Set the interface address */ strlcpy(ifra.ifra_name, vfr.vfr_name, sizeof(ifra.ifra_name)); - memcpy(&ifra.ifra_addr, &vfr.vfr_ifra.ifra_addr, - sizeof(ifra.ifra_addr)); - ifra.ifra_addr.sa_family = AF_INET; - ifra.ifra_addr.sa_len = sizeof(struct sockaddr_in); - - memcpy(&ifra.ifra_mask, &vfr.vfr_ifra.ifra_mask, - sizeof(ifra.ifra_mask)); - ifra.ifra_mask.sa_family = AF_INET; - ifra.ifra_mask.sa_len = sizeof(struct sockaddr_in); + ifra.ifra_addr.sa_len = + ifra.ifra_mask.sa_len = + sizeof(struct sockaddr_in); + + memcpy(&ifra.ifra_addr, &vfr.vfr_addr, + ifra.ifra_addr.sa_len); + memcpy(&ifra.ifra_mask, &vfr.vfr_mask, + ifra.ifra_mask.sa_len); if (ioctl(env->vmd_fd, SIOCAIFADDR, &ifra) < 0) log_warn("SIOCAIFADDR"); break; + case IMSG_VMDOP_PRIV_IFADDR6: + memset(&ifar, 0, sizeof(ifar)); + memset(&in6_ifra, 0, sizeof(in6_ifra)); + + if (vfr.vfr_addr.ss_family != AF_INET6 || + vfr.vfr_addr.ss_family != vfr.vfr_mask.ss_family) + fatalx("%s: invalid address family", __func__); + + /* First enable IPv6 on this interface */ + strlcpy(ifar.ifar_name, vfr.vfr_name, + sizeof(ifar.ifar_name)); + ifar.ifar_af = AF_INET6; + if (ioctl(env->vmd_fd, SIOCIFAFATTACH, (caddr_t)&ifar) < 0) + log_warn("SIOCIFAFATTACH"); + + /* Set the interface address */ + strlcpy(in6_ifra.ifra_name, vfr.vfr_name, + sizeof(in6_ifra.ifra_name)); + + in6_ifra.ifra_addr.sin6_len = + in6_ifra.ifra_prefixmask.sin6_len = + sizeof(struct sockaddr_in6); + + memcpy(&in6_ifra.ifra_addr, &vfr.vfr_addr, + in6_ifra.ifra_addr.sin6_len); + memcpy(&in6_ifra.ifra_prefixmask, &vfr.vfr_mask, + in6_ifra.ifra_prefixmask.sin6_len); + in6_ifra.ifra_prefixmask.sin6_scope_id = 0; + + in6_ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + in6_ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + + if (ioctl(env->vmd_fd6, SIOCDIFADDR_IN6, &in6_ifra) < 0 && + errno != EADDRNOTAVAIL) + log_warn("SIOCDIFADDR_IN6"); + + if (ioctl(env->vmd_fd6, SIOCAIFADDR_IN6, &in6_ifra) < 0) + log_warn("SIOCAIFADDR_IN6"); + break; case IMSG_VMDOP_CONFIG: config_getconfig(env, imsg); break; @@ -270,6 +321,7 @@ priv_validgroup(const char *name) int vm_priv_ifconfig(struct privsep *ps, struct vmd_vm *vm) { + char name[64]; struct vmd *env = ps->ps_env; struct vm_create_params *vcp = &vm->vm_params.vmc_params; struct vmd_if *vif; @@ -277,6 +329,7 @@ vm_priv_ifconfig(struct privsep *ps, str unsigned int i; struct vmop_ifreq vfr, vfbr; struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) { vif = &vm->vm_ifs[i]; @@ -284,6 +337,7 @@ vm_priv_ifconfig(struct privsep *ps, str if (vif->vif_name == NULL) break; + memset(&vfr, 0, sizeof(vfr)); if (strlcpy(vfr.vfr_name, vif->vif_name, sizeof(vfr.vfr_name)) >= sizeof(vfr.vfr_name)) return (-1); @@ -378,26 +432,58 @@ vm_priv_ifconfig(struct privsep *ps, str /* Set interface address if it is a local interface */ if (vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) { - sin4 = (struct sockaddr_in *)&vfr.vfr_ifra.ifra_mask; + memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask)); + memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr)); + + /* IPv4 */ + sin4 = (struct sockaddr_in *)&vfr.vfr_mask; sin4->sin_family = AF_INET; sin4->sin_len = sizeof(*sin4); sin4->sin_addr.s_addr = htonl(0xfffffffe); - sin4 = (struct sockaddr_in *)&vfr.vfr_ifra.ifra_addr; + sin4 = (struct sockaddr_in *)&vfr.vfr_addr; sin4->sin_family = AF_INET; sin4->sin_len = sizeof(*sin4); if ((sin4->sin_addr.s_addr = - vm_priv_addr(&env->vmd_cfg.cfg_localprefix, + vm_priv_addr(&env->vmd_cfg, vm->vm_vmid, i, 0)) == 0) return (-1); + inet_ntop(AF_INET, &sin4->sin_addr, + name, sizeof(name)); log_debug("%s: interface %s address %s/31", - __func__, vfr.vfr_name, - inet_ntoa(sin4->sin_addr)); + __func__, vfr.vfr_name, name); proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR, &vfr, sizeof(vfr)); } + if ((vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) && + (env->vmd_cfg.cfg_flags & VMD_CFG_INET6)) { + memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask)); + memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr)); + + /* IPv6 */ + sin6 = ss2sin6(&vfr.vfr_mask); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + memset(&sin6->sin6_addr.s6_addr[0], 0xff, 12); + memset(&sin6->sin6_addr.s6_addr[12], 0, 4); + + sin6 = ss2sin6(&vfr.vfr_addr); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + if (vm_priv_addr6(&env->vmd_cfg, + vm->vm_vmid, i, 0, &sin6->sin6_addr) == -1) + return (-1); + + inet_ntop(AF_INET6, &sin6->sin6_addr, + name, sizeof(name)); + log_debug("%s: interface %s address %s/96", + __func__, vfr.vfr_name, name); + + proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR6, + &vfr, sizeof(vfr)); + } } return (0); @@ -458,9 +544,10 @@ vm_priv_brconfig(struct privsep *ps, str } uint32_t -vm_priv_addr(struct address *h, uint32_t vmid, int idx, int isvm) +vm_priv_addr(struct vmd_config *cfg, uint32_t vmid, int idx, int isvm) { - in_addr_t prefix, mask, addr; + struct address *h = &cfg->cfg_localprefix; + in_addr_t prefix, mask, addr; /* * 1. Set the address prefix and mask, 100.64.0.0/10 by default. @@ -500,4 +587,39 @@ vm_priv_addr(struct address *h, uint32_t } return (addr); +} + +int +vm_priv_addr6(struct vmd_config *cfg, uint32_t vmid, + int idx, int isvm, struct in6_addr *in6_addr) +{ + struct address *h = &cfg->cfg_localprefix6; + struct in6_addr addr, mask; + uint32_t addr4; + + /* 1. Set the address prefix and mask, fd00::/8 by default. */ + if (h->ss.ss_family != AF_INET6 || + h->prefixlen < 0 || h->prefixlen > 128) + fatal("local prefix6"); + addr = ss2sin6(&h->ss)->sin6_addr; + prefixlen2mask6(h->prefixlen, &mask); + + /* 2. Encode the VM IPv4 address as subnet, fd00::NN:NN:0:0/96. */ + if ((addr4 = vm_priv_addr(cfg, vmid, idx, 1)) == 0) + return (0); + memcpy(&addr.s6_addr[8], &addr4, sizeof(addr4)); + + /* + * 3. Set the last octet to 1 (host) or 2 (VM). + * The latter is currently not used inside vmd as we don't + * answer rtsol requests ourselves. + */ + if (!isvm) + addr.s6_addr[15] = 1; + else + addr.s6_addr[15] = 2; + + memcpy(in6_addr, &addr, sizeof(*in6_addr)); + + return (0); } Index: usr.sbin/vmd/vm.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/vmd/vm.conf.5,v retrieving revision 1.39 diff -u -p -u -p -r1.39 vm.conf.5 --- usr.sbin/vmd/vm.conf.5 30 Oct 2018 17:56:54 -0000 1.39 +++ usr.sbin/vmd/vm.conf.5 16 Nov 2018 15:53:10 -0000 @@ -100,6 +100,13 @@ in the section below. The default is .Ar 100.64.0.0/10 . +.It Ic local inet6 Op Ic prefix Ar address Ns Li / Ns Ar prefix +Enable IPv6 on local interfaces and allocate routable subnets. +If the prefix is not specified, +a random unique local address prefix +.Pq Ar fd00::/8 +will be generated on startup. +The prefix length must be /64 or smaller. .It Cm socket owner Ar user Ns Op : Ns Ar group Set the control socket owner to the specified user or group. Users with access to the control socket will be allowed to use @@ -217,6 +224,19 @@ interface will auto-generate an IPv4 sub configure a gateway address on the VM host side, and run a simple DHCP/BOOTP server for the VM. This option can be used for layer 3 mode without configuring a switch. +.Pp +If the global +.Cm local inet6 +option is enabled, a routable IPv6 gateway address will be generated +on the host side. +Unlike the IPv4 option, +.Nm vmd +does not respond to DHCPv6 or router solicitation messages itself. +Use +.Xr rad 8 +listening on the interface group, e.g. +.Ar interface tap +for auto-configuring the VMs accordingly. .It Cm interfaces Ar count Optional minimum number of network interfaces to add to the VM. If the Index: usr.sbin/vmd/vmd.c =================================================================== RCS file: /cvs/src/usr.sbin/vmd/vmd.c,v retrieving revision 1.104 diff -u -p -u -p -r1.104 vmd.c --- usr.sbin/vmd/vmd.c 15 Oct 2018 10:35:41 -0000 1.104 +++ usr.sbin/vmd/vmd.c 16 Nov 2018 15:53:12 -0000 @@ -1923,6 +1923,25 @@ prefixlen2mask(uint8_t prefixlen) } void +prefixlen2mask6(uint8_t prefixlen, struct in6_addr *mask) +{ + struct in6_addr s6; + int i; + + if (prefixlen > 128) + prefixlen = 128; + + memset(&s6, 0, sizeof(s6)); + for (i = 0; i < prefixlen / 8; i++) + s6.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + s6.s6_addr[prefixlen / 8] = 0xff00 >> i; + + memcpy(mask, &s6, sizeof(s6)); +} + +void getmonotime(struct timeval *tv) { struct timespec ts; Index: usr.sbin/vmd/vmd.h =================================================================== RCS file: /cvs/src/usr.sbin/vmd/vmd.h,v retrieving revision 1.84 diff -u -p -u -p -r1.84 vmd.h --- usr.sbin/vmd/vmd.h 19 Oct 2018 10:12:39 -0000 1.84 +++ usr.sbin/vmd/vmd.h 16 Nov 2018 15:53:12 -0000 @@ -25,6 +25,7 @@ #include <net/if.h> #include <netinet/in.h> #include <netinet/if_ether.h> +#include <netinet6/in6_var.h> #include <limits.h> #include <stdio.h> @@ -78,6 +79,9 @@ /* 100.64.0.0/10 from rfc6598 (IPv4 Prefix for Shared Address Space) */ #define VMD_DHCP_PREFIX "100.64.0.0/10" +/* Unique local address for IPv6 */ +#define VMD_ULA_PREFIX "fd00::/8" + enum imsg_type { IMSG_VMDOP_START_VM_REQUEST = IMSG_PROC_MAX, IMSG_VMDOP_START_VM_CDROM, @@ -109,6 +113,7 @@ enum imsg_type { IMSG_VMDOP_PRIV_IFDOWN, IMSG_VMDOP_PRIV_IFGROUP, IMSG_VMDOP_PRIV_IFADDR, + IMSG_VMDOP_PRIV_IFADDR6, IMSG_VMDOP_PRIV_IFRDOMAIN, IMSG_VMDOP_VM_SHUTDOWN, IMSG_VMDOP_VM_REBOOT, @@ -140,10 +145,11 @@ struct vmop_id { }; struct vmop_ifreq { - uint32_t vfr_id; - char vfr_name[IF_NAMESIZE]; - char vfr_value[VM_NAME_MAX]; - struct ifaliasreq vfr_ifra; + uint32_t vfr_id; + char vfr_name[IF_NAMESIZE]; + char vfr_value[VM_NAME_MAX]; + struct sockaddr_storage vfr_addr; + struct sockaddr_storage vfr_mask; }; struct vmop_owner { @@ -292,7 +298,11 @@ struct address { TAILQ_HEAD(addresslist, address); struct vmd_config { + unsigned int cfg_flags; +#define VMD_CFG_INET6 0x1 + struct address cfg_localprefix; + struct address cfg_localprefix6; }; struct vmd { @@ -313,6 +323,7 @@ struct vmd { struct userlist *vmd_users; int vmd_fd; + int vmd_fd6; int vmd_ptmfd; }; @@ -372,6 +383,7 @@ void user_inc(struct vm_create_params * int user_checklimit(struct vmd_user *, struct vm_create_params *); char *get_string(uint8_t *, size_t); uint32_t prefixlen2mask(uint8_t); +void prefixlen2mask6(u_int8_t, struct in6_addr *); void getmonotime(struct timeval *); /* priv.c */ @@ -381,7 +393,9 @@ int priv_findname(const char *, const c int priv_validgroup(const char *); int vm_priv_ifconfig(struct privsep *, struct vmd_vm *); int vm_priv_brconfig(struct privsep *, struct vmd_switch *); -uint32_t vm_priv_addr(struct address *, uint32_t, int, int); +uint32_t vm_priv_addr(struct vmd_config *, uint32_t, int, int); +int vm_priv_addr6(struct vmd_config *, uint32_t, int, int, + struct in6_addr *); /* vmm.c */ struct iovec;