diff --git a/manifest b/manifest index 6229682f499c..89975ce51172 100644 --- a/manifest +++ b/manifest @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright 2017 ASS-Einrichtungssysteme GmbH # # This lists all the files that illumos-joyent delivers as part of a build. @@ -10933,6 +10933,7 @@ f usr/sbin/nscd 0555 root bin f usr/sbin/nvmeadm 0555 root bin f usr/sbin/nwamadm 0555 root bin f usr/sbin/nwamcfg 0555 root bin +f usr/sbin/ovroute 0555 root bin f usr/sbin/pbind 0555 root sys f usr/sbin/pcitool 0555 root bin f usr/sbin/ping 4555 root bin @@ -12585,6 +12586,7 @@ f usr/share/man/man1m/nlsadmin.1m 0444 root bin f usr/share/man/man1m/nscd.1m 0444 root bin f usr/share/man/man1m/nvmeadm.1m 0444 root bin f usr/share/man/man1m/nwamd.1m 0444 root bin +f usr/share/man/man1m/ovroute.1m 0444 root bin f usr/share/man/man1m/passmgmt.1m 0444 root bin f usr/share/man/man1m/pbind.1m 0444 root bin f usr/share/man/man1m/pcitool.1m 0444 root bin diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index 32329de0e900..a4ba591213e9 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -21,7 +21,7 @@ # # Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright (c) 2012, 2015 by Delphix. All rights reserved. # Copyright (c) 2013 DEY Storage Systems, Inc. All rights reserved. # Copyright 2014 Garrett D'Amore @@ -300,6 +300,7 @@ COMMON_SUBDIRS= \ oamuser \ oawk \ od \ + ovroute \ pack \ pagesize \ passmgmt \ diff --git a/usr/src/cmd/mdb/common/modules/genunix/Makefile.files b/usr/src/cmd/mdb/common/modules/genunix/Makefile.files index d371cf70fe6a..2be2d6ed7c3a 100644 --- a/usr/src/cmd/mdb/common/modules/genunix/Makefile.files +++ b/usr/src/cmd/mdb/common/modules/genunix/Makefile.files @@ -21,7 +21,7 @@ # # Copyright 2011 Nexenta Systems, Inc. All rights reserved. # Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. -# Copyright 2019 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright (c) 2013 by Delphix. All rights reserved. # @@ -71,6 +71,7 @@ GENUNIX_SRCS = \ nvpair.c \ pci.c \ pg.c \ + qqcache.c \ rctl.c \ refhash.c \ refstr.c \ diff --git a/usr/src/cmd/mdb/common/modules/genunix/genunix.c b/usr/src/cmd/mdb/common/modules/genunix/genunix.c index 32370ba7e193..fdbd0f0d583e 100644 --- a/usr/src/cmd/mdb/common/modules/genunix/genunix.c +++ b/usr/src/cmd/mdb/common/modules/genunix/genunix.c @@ -21,7 +21,7 @@ /* * Copyright 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright 2019 Joyent, Inc. + * Copyright 2021 Joyent, Inc. * Copyright (c) 2013 by Delphix. All rights reserved. */ @@ -98,6 +98,7 @@ #include "nvpair.h" #include "pci.h" #include "pg.h" +#include "qqcache.h" #include "rctl.h" #include "refhash.h" #include "sobj.h" @@ -4788,6 +4789,12 @@ static const mdb_walker_t walkers[] = { { "pcie_bus", "walk all pcie_bus_t's", pcie_bus_walk_init, pcie_bus_walk_step, NULL }, + /* from qqcache.c */ + { QQCACHE_WALK_NAME, QQCACHE_WALK_DESC, + qqcache_walk_init_cache, qqcache_walk_step, qqcache_walk_fini }, + { QQCACHE_HASH_WALK_NAME, QQCACHE_HASH_WALK_DESC, + qqcache_walk_init_hash, qqcache_walk_step, qqcache_walk_fini }, + /* from rctl.c */ { "rctl_dict_list", "walk all rctl_dict_entry_t's from rctl_lists", rctl_dict_walk_init, rctl_dict_walk_step, NULL }, diff --git a/usr/src/cmd/mdb/common/modules/genunix/qqcache.c b/usr/src/cmd/mdb/common/modules/genunix/qqcache.c new file mode 100644 index 000000000000..b278d3e0b814 --- /dev/null +++ b/usr/src/cmd/mdb/common/modules/genunix/qqcache.c @@ -0,0 +1,117 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#include +#include + +#include +#include + +#include "qqcache.h" + +typedef struct qqcache_walk_data { + size_t qwd_link_off; +} qqcache_walk_data_t; + +typedef struct mdb_qqcache { + size_t qqc_link_off; + size_t qqc_nbuckets; +} mdb_qqcache_t; + +static int +qqcache_walk_init(mdb_walk_state_t *wsp, boolean_t use_hash) +{ + qqcache_walk_data_t *qwd; + uintptr_t base; + size_t i, n, qqc_list_sz; + int cache_off, bucket_off, list_off; + mdb_qqcache_t qc; + + /* mdb_ctf_offsetof_by_name will print any errors */ + cache_off = mdb_ctf_offsetof_by_name("qqcache_t", "qqc_lists"); + if (cache_off == -1) + return (WALK_ERR); + + bucket_off = mdb_ctf_offsetof_by_name("qqcache_t", "qqc_buckets"); + if (bucket_off == -1) + return (WALK_ERR); + + list_off = mdb_ctf_offsetof_by_name("qqcache_list_t", "qqcl_list"); + if (list_off == -1) + return (WALK_ERR); + + /* mdb_ctf_sizeof_by_name will print any errors */ + qqc_list_sz = mdb_ctf_sizeof_by_name("qqcache_list_t"); + if (qqc_list_sz == -1) + return (WALK_ERR); + + if (mdb_ctf_vread(&qc, "qqcache_t", "mdb_qqcache_t", wsp->walk_addr, + 0) == -1) { + mdb_warn("failed to read qqcache_t at %#lx", wsp->walk_addr); + return (WALK_ERR); + } + + qwd = wsp->walk_data = mdb_zalloc(sizeof (*qwd), UM_SLEEP); + qwd->qwd_link_off = qc.qqc_link_off; + + if (use_hash) { + base = wsp->walk_addr + bucket_off; + n = qc.qqc_nbuckets; + } else { + base = wsp->walk_addr + cache_off; + n = QQCACHE_NUM_LISTS; + } + + for (i = 0; i < n; i++) { + wsp->walk_addr = base + i * qqc_list_sz + list_off; + + if (mdb_layered_walk("list", wsp) == -1) { + mdb_warn("can't walk qqcache_t"); + mdb_free(qwd, sizeof (*qwd)); + return (WALK_ERR); + } + } + + return (WALK_NEXT); +} + +int +qqcache_walk_init_cache(mdb_walk_state_t *wsp) +{ + return (qqcache_walk_init(wsp, B_FALSE)); +} + +int +qqcache_walk_init_hash(mdb_walk_state_t *wsp) +{ + return (qqcache_walk_init(wsp, B_TRUE)); +} + +int +qqcache_walk_step(mdb_walk_state_t *wsp) +{ + qqcache_walk_data_t *qwd = wsp->walk_data; + uintptr_t addr = wsp->walk_addr - qwd->qwd_link_off; + + return (wsp->walk_callback(addr, wsp->walk_layer, wsp->walk_cbdata)); +} + +void +qqcache_walk_fini(mdb_walk_state_t *wsp) +{ + qqcache_walk_data_t *qwd = wsp->walk_data; + + mdb_free(qwd, sizeof (*qwd)); +} diff --git a/usr/src/cmd/mdb/common/modules/genunix/qqcache.h b/usr/src/cmd/mdb/common/modules/genunix/qqcache.h new file mode 100644 index 000000000000..216b1a5309f7 --- /dev/null +++ b/usr/src/cmd/mdb/common/modules/genunix/qqcache.h @@ -0,0 +1,40 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#ifndef _MDB_QQCACHE_H +#define _MDB_QQCACHE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define QQCACHE_WALK_NAME "qqcache" +#define QQCACHE_WALK_DESC "walk a qqcache (2Q cache)" + +#define QQCACHE_HASH_WALK_NAME "qqhash" +#define QQCACHE_HASH_WALK_DESC "walk a qqcache (2Q cache) via the hash buckets" + +struct mdb_walk_state; + +extern int qqcache_walk_init_cache(struct mdb_walk_state *); +extern int qqcache_walk_init_hash(struct mdb_walk_state *); +extern int qqcache_walk_step(struct mdb_walk_state *); +extern void qqcache_walk_fini(struct mdb_walk_state *); + +#ifdef __cplusplus +} +#endif + +#endif /* _MDB_QQCACHE_H */ diff --git a/usr/src/cmd/ovroute/Makefile b/usr/src/cmd/ovroute/Makefile new file mode 100644 index 000000000000..cd27d96d0d06 --- /dev/null +++ b/usr/src/cmd/ovroute/Makefile @@ -0,0 +1,52 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2021 Joyent, Inc. +# + +PROG= ovroute +OBJS= ovroute.o +SRCS= $(OBJS:%.o=../%.c) + +include ../Makefile.cmd +include ../Makefile.ctf + +CSTD = $(CSTD_GNU99) +CLEANFILES += $(OBJS) +CPPFLAGS += -D_REENTRANT +CFLAGS += $(CCVERBOSE) +LDLIBS += -lumem -lofmt -ldladm -lsocket +$(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG + +.KEEP_STATE: + +all: $(PROG) + +$(PROG): $(OBJS) + $(LINK64.c) -o $@ $(OBJS) $(LDLIBS) + $(POST_PROCESS) + +clean: + -$(RM) $(CLEANFILES) + +%.o: %.c + $(COMPILE64.c) $< + $(POST_PROCESS_O) + +clobber: clean + $(RM) $(PROG) + +install: $(PROG) $(ROOTUSRSBINPROG) + +FRC: + +include ../Makefile.targ diff --git a/usr/src/cmd/ovroute/ovroute.c b/usr/src/cmd/ovroute/ovroute.c new file mode 100644 index 000000000000..e8436ac06695 --- /dev/null +++ b/usr/src/cmd/ovroute/ovroute.c @@ -0,0 +1,1523 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * To facilitate potential future changes, we have an undocumented + * "_version" subcommand that net-agent can use to determine the cmdline + * options supported. This should follow semver semantics. + */ +#define OVROUTE_MAJOR 1 +#define OVROUTE_MINOR 0 + +typedef enum { + OVPARAM_NONE = 0x00, + OVPARAM_DEVICE = 0x01, + OVPARAM_VLAN = 0x02, + OVPARAM_ADDRESS_V4 = 0x04, + OVPARAM_ADDRESS_V6 = 0x08, + OVPARAM_MAC = 0x10, + OVPARAM_ROUTETBL = 0x20, +} ovroute_param_t; + +extern const char *__progname; + +static const char *overlay_dev = "/dev/overlay_router"; +static const char *id_re = "[A-Za-z0-9][A-Za-z#.-]*"; +static const char *empty = "-"; + +typedef struct dispatch_tbl { + const char *dt_subcmd; + int (*dt_cmd)(int, char **); +} dispatch_tbl_t; + +typedef enum net_members { + NM_ID = 0, + NM_NET, + NM_NETV6, + NM_VLAN, + NM_ROUTERADDR, + NM_ROUTERADDRV6, + NM_MAC, + NM_ROUTETBL +} net_members_t; + +static boolean_t net_print(ofmt_arg_t *, char *, uint_t); + +static const ofmt_field_t net_fields[] = { + { + .of_name = "ID", + .of_width = OVERLAY_ID_MAX, + .of_id = NM_ID, + .of_cb = net_print, + }, + { + .of_name = "NETWORK", + .of_width = INET_ADDRSTRLEN + 3, /* +3 for /prefixlen */ + .of_id = NM_NET, + .of_cb = net_print, + }, + { + .of_name = "NETWORK-V6", + .of_width = INET6_ADDRSTRLEN + 4, /* +4 for /prefixlen */ + .of_id = NM_NETV6, + .of_cb = net_print, + }, + { + .of_name = "VLAN", + .of_width = 4, + .of_id = NM_VLAN, + .of_cb = net_print, + }, + { + .of_name = "ROUTER", + .of_width = 32, + .of_id = NM_ROUTERADDR, + .of_cb = net_print, + }, + { + .of_name = "MAC", + .of_width = ETHERADDRSTRL, + .of_id = NM_MAC, + .of_cb = net_print, + }, + { + .of_name = "RTABLE", + .of_width = OVERLAY_ID_MAX, + .of_id = NM_ROUTETBL, + .of_cb = net_print, + }, + { NULL, 0, 0, NULL } +}; + +typedef enum routetbl_members { + RM_ID = 0, + RM_NENTS +} routetbl_member_t; + +static boolean_t routetbl_print(ofmt_arg_t *, char *, uint_t); + +static const ofmt_field_t routetbl_fields[] = { + { + .of_name = "ID", + .of_width = OVERLAY_ID_MAX, + .of_id = RM_ID, + .of_cb = routetbl_print, + }, + { + .of_name = "ENTRIES", + .of_width = 5, + .of_id = RM_NENTS, + .of_cb = routetbl_print, + }, + { NULL, 0, 0, NULL } +}; + +typedef struct routetbl_print { + char rpt_id[OVERLAY_ID_MAX]; + const overlay_route_ent_t *rpt_ent; +} routetbl_print_t; + +typedef enum routetbl_ent_memb { + REM_ID = 0, + REM_DEST, + REM_TARGET +} routetbl_ent_memb_t; + +static boolean_t routeent_print(ofmt_arg_t *, char *, uint_t); + +static const ofmt_field_t routeent_fields[] = { + { + .of_name = "ID", + .of_width = OVERLAY_ID_MAX, + .of_id = REM_ID, + .of_cb = routeent_print, + }, + { + .of_name = "DESTINATION", + .of_width = INET6_ADDRSTRLEN + 4, + .of_id = REM_DEST, + .of_cb = routeent_print, + }, + { + .of_name = "TARGET", + .of_width = INET6_ADDRSTRLEN, + .of_id = REM_TARGET, + .of_cb = routeent_print, + }, + { NULL, 0, 0, NULL } +}; + +static void get_linkid(const char *, datalink_id_t *); +static int open_overlay(const char *, boolean_t); +static int do_ioctl(int, void *, boolean_t); + +static int do_help(int, char **); + +static int do_version(int, char **); + +static int do_router(int, char **); +static int do_router_create(int, char **); +static int do_router_delete(int, char **); +static int do_router_set_routing_table(int, char **); +static int do_router_get(int, char **); + +static int do_route_table(int, char **); +static int do_routetbl_create(int, char **); +static int do_routetbl_delete(int, char **); +static int do_routetbl_set_default(int, char **); +static int do_routetbl_get(int, char **); +static int do_routetbl_addent(int, char **); +static int do_routetbl_delent(int, char **); + +static void parse_vlan(const char *, uint16_t *); +static void parse_mac(const char *, uint8_t *); +static void parse_id(const char *, char *, size_t); +static void parse_addr(const char *, struct in6_addr *, uint8_t *); +static void parse_addr_port(const char *, struct sockaddr_in6 *); + +static dispatch_tbl_t main_tbl[] = { + { "_version", do_version }, + { "router", do_router }, + { "route-table", do_route_table }, + { "help", do_help }, +}; + +static dispatch_tbl_t router_tbl[] = { + { "create", do_router_create }, + { "delete", do_router_delete }, + { "set-routing-table", do_router_set_routing_table }, + { "get", do_router_get }, + { "help", do_help }, +}; + +static dispatch_tbl_t route_tbl_tbl[] = { + { "create", do_routetbl_create }, + { "delete", do_routetbl_delete }, + { "set-default", do_routetbl_set_default }, + { "get", do_routetbl_get }, + { "add", do_routetbl_addent }, + { "del", do_routetbl_delent } +}; + +static inline void +net_addr(in_addr_t *dest, const in_addr_t *src, uint8_t prefixlen) +{ + const in_addr_t mask = htonl(((in_addr_t)1 << (32 - prefixlen)) - 1); + *dest = *src & ~mask; +} + +static inline void +net_addr6(struct in6_addr *dest, const struct in6_addr *src, uint8_t prefixlen) +{ + struct in6_addr maskv6; + + /* There's probably a better way to do this, but for now... */ + maskv6._S6_un._S6_u32[0] = IN6_MASK_FROM_PREFIX(0, prefixlen); + maskv6._S6_un._S6_u32[1] = IN6_MASK_FROM_PREFIX(1, prefixlen); + maskv6._S6_un._S6_u32[2] = IN6_MASK_FROM_PREFIX(2, prefixlen); + maskv6._S6_un._S6_u32[3] = IN6_MASK_FROM_PREFIX(3, prefixlen); + + for (uint_t i = 0; i < 4; i++) { + uint32_t sval = ntohl(src->_S6_un._S6_u32[i]); + uint32_t mask = maskv6._S6_un._S6_u32[i]; + + dest->_S6_un._S6_u32[i] = htonl(sval & mask); + } +} + +static void __NORETURN +usage(void) +{ + /* BEGIN CSTYLED */ + (void) fprintf(stderr, +"Usage: %1$s router create -d overlay -m macaddr -v vlan -r route_table \n" +"\t-a address/mask [-a address/mask] router_id\n" +" %1$s router delete -d overlay router_id\n" +" %1$s router get -d overlay [router_id...]\n" +" %1$s router set-routing-table -d overlay -r route_table router_id\n" +" %1$s route-table create -d overlay routetbl_id\n" +" %1$s route-table delete -d overlay routetbl_id\n" +" %1$s route-table get -d overlay [routetbl_id...]\n" +" %1$s route-table set-default -d overlay routetbl_id\n" +" %1$s route-table add -d overlay -i routetbl_id destination target\n" +" %1$s route-table del -d overlay -i routetbl_id destination target\n", + __progname); + /* END CSTYLED */ + + exit(2); +} + +static int +dispatch(int argc, char **argv, const dispatch_tbl_t *tbl, size_t ntbl) +{ + if (argc < 2) { + (void) fprintf(stderr, "Missing subcommand\n"); + usage(); + } + + for (size_t i = 0; i < ntbl; i++) { + /* strcmp() is safe due to db_subcmd being bounded */ + if (strcmp(argv[1], tbl[i].dt_subcmd) == 0) + return (tbl[i].dt_cmd(argc - 1, argv + 1)); + } + + (void) fprintf(stderr, "Unknown subcommand '%s'\n", argv[1]); + usage(); +} + +static int +do_help(int argc __unused, char **argv __unused) +{ + usage(); +} + +static int +do_version(int argc __unused, char **argv __unused) +{ + (void) printf("%d.%d\n", OVROUTE_MAJOR, OVROUTE_MINOR); + return (0); +} + +static int +nomem_cb(void) +{ + (void) fprintf(stderr, "%s: Out of memory\n", __progname); + abort(); +} + +int +main(int argc, char **argv) +{ + /* Treat all alloc failures as fatal. */ + umem_nofail_callback(nomem_cb); + + return (dispatch(argc, argv, main_tbl, ARRAY_SIZE(main_tbl))); +} + +static int +do_router(int argc, char **argv) +{ + return (dispatch(argc, argv, router_tbl, ARRAY_SIZE(router_tbl))); +} + +static int +do_router_create(int argc, char **argv) +{ + const char *ovname = NULL; + int c, ret; + overlay_ioc_net_t orn = { 0 }; + ovroute_param_t params = OVPARAM_NONE; + const ovroute_param_t req_params = + (OVPARAM_DEVICE|OVPARAM_VLAN|OVPARAM_MAC); + struct in6_addr addr; + uint8_t prefixlen; + + while ((c = getopt(argc, argv, "a:d:m:r:v:")) != -1) { + switch (c) { + case 'a': + parse_addr(optarg, &addr, &prefixlen); + if (IN6_IS_ADDR_V4MAPPED(&addr)) { + if ((params & OVPARAM_ADDRESS_V4) != 0) { + (void) fprintf(stderr, "Can only " + "specify one IPv4 address\n"); + usage(); + } + params |= OVPARAM_ADDRESS_V4; + + IN6_V4MAPPED_TO_IPADDR(&addr, + orn.oin_routeraddr); + orn.oin_prefixlen = 32 - (128 - prefixlen); + } else { + if ((params & OVPARAM_ADDRESS_V6) != 0) { + (void) fprintf(stderr, "Can only " + "specify one IPv6 address\n"); + usage(); + } + params |= OVPARAM_ADDRESS_V6; + bcopy(&addr, &orn.oin_routeraddrv6, + sizeof (addr)); + orn.oin_prefixlenv6 = prefixlen; + } + break; + case 'd': + ovname = optarg; + params |= OVPARAM_DEVICE; + break; + case 'm': + parse_mac(optarg, orn.oin_mac); + params |= OVPARAM_MAC; + break; + case 'r': + parse_id(optarg, orn.oin_routetbl, + sizeof (orn.oin_routetbl)); + params |= OVPARAM_ROUTETBL; + break; + case 'v': + parse_vlan(optarg, &orn.oin_vlan); + params |= OVPARAM_VLAN; + break; + case '?': + (void) fprintf(stderr, "Unknown option -%c\n", optopt); + usage(); + } + } + + if (argc < optind) + errx(EXIT_FAILURE, "Router network id missing"); + + parse_id(argv[optind], orn.oin_id, sizeof (orn.oin_id)); + get_linkid(ovname, &orn.oin_hdr.orih_linkid); + + /* + * We require at least one address, as well as the parameters in + * req_params. + * + * XXX: Error message could probably be made better. + */ + if ((params & req_params) != req_params || + (params & (OVPARAM_ADDRESS_V4|OVPARAM_ADDRESS_V6)) == 0) + errx(EXIT_FAILURE, "required parameters missing"); + + ret = do_ioctl(OVERLAY_ROUTER_NET_CREATE, &orn, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, "failed to create router net %s on %s", + orn.oin_id, ovname); + } + + return (0); +} + +static int +do_router_delete(int argc, char **argv) +{ + const char *ovname = NULL; + int c, ret; + overlay_ioc_net_t orn = { 0 }; + + while ((c = getopt(argc, argv, "d:")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + } + } + + if (argc < optind) { + (void) fprintf(stderr, "Router network id missing\n"); + usage(); + } + + if (ovname == NULL) { + (void) fprintf(stderr, "Overlay name missing\n"); + usage(); + } + + parse_id(argv[optind], orn.oin_id, sizeof (orn.oin_id)); + get_linkid(ovname, &orn.oin_hdr.orih_linkid); + + ret = do_ioctl(OVERLAY_ROUTER_NET_DELETE, &orn, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, "failed to delete router net %s", + argv[1]); + } + + return (0); +} + +static int +do_router_set_routing_table(int argc, char **argv) +{ + const char *ovname = NULL; + overlay_ioc_net_t orn = { 0 }; + ovroute_param_t params = OVPARAM_NONE; + int c, ret; + + while ((c = getopt(argc, argv, "d:r:")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + params |= OVPARAM_DEVICE; + break; + case 'r': + parse_id(optarg, orn.oin_routetbl, + sizeof (orn.oin_routetbl)); + params |= OVPARAM_ROUTETBL; + break; + } + } + + if ((params & (OVPARAM_DEVICE|OVPARAM_ROUTETBL)) == 0) { + (void) fprintf(stderr, + "Missing overlay device or route table id\n"); + usage(); + } + + if (argc < optind) { + (void) fprintf(stderr, "Router network id missing\n"); + usage(); + } + + parse_id(argv[optind], orn.oin_id, sizeof (orn.oin_id)); + get_linkid(ovname, &orn.oin_hdr.orih_linkid); + + ret = do_ioctl(OVERLAY_ROUTER_NET_SET_ROUTETBL, &orn, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, "failed to set route table"); + } + + return (0); +} + +static boolean_t +netaddr_print(int af, const void *addr, char *buf, size_t buflen) +{ + if (af == AF_INET) { + if (af == INADDR_ANY) { + (void) strlcpy(buf, empty, buflen); + return (B_FALSE); + } + } else if (af == AF_INET6) { + const struct in6_addr *a6 = addr; + if (IN6_IS_ADDR_UNSPECIFIED(a6)) { + (void) strlcpy(buf, empty, buflen); + return (B_FALSE); + } + } + + (void) inet_ntop(af, addr, buf, buflen); + return (B_TRUE); +} + +static void +netprefix_print(int af, const void *addr, uint8_t prefixlen, char *buf, + size_t buflen) +{ + if (!netaddr_print(af, addr, buf, buflen)) + return; + + char prefixbuf[5]; + + (void) snprintf(prefixbuf, sizeof (prefixbuf), "/%" PRIu8, prefixlen); + (void) strlcat(buf, prefixbuf, buflen); +} + +static void +sockaddr_print(const struct sockaddr_in6 *addr, char *buf, size_t buflen) +{ + const void *addrp; + in_addr_t v4; + int af; + + if (IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr)) { + af = AF_INET; + IN6_V4MAPPED_TO_IPADDR(&addr->sin6_addr, v4); + addrp = &v4; + } else { + af = AF_INET6; + addrp = addr; + } + + if (!netaddr_print(af, addrp, buf, buflen) || addr->sin6_port == 0) + return; + + char portstr[7]; /* ':' + 5 digit port */ + + (void) snprintf(portstr, sizeof (portstr), ":%hhu", + ntohs(addr->sin6_port)); + (void) strlcat(buf, portstr, buflen); +} + +static boolean_t +net_print(ofmt_arg_t *ofmt, char *buf, uint_t buflen) +{ + overlay_ioc_net_t *net = ofmt->ofmt_cbarg; + in_addr_t addr; + struct in6_addr addr6; + + /* + * ofmt_id is cast to net_members_t so any additional fields + * added in the future will trigger a compile error if not + * handled here. + */ + switch ((net_members_t)ofmt->ofmt_id) { + case NM_ID: + (void) strlcpy(buf, net->oin_id, buflen); + break; + case NM_NET: + net_addr(&addr, &net->oin_routeraddr, net->oin_prefixlen); + netprefix_print(AF_INET, &addr, net->oin_prefixlen, buf, + buflen); + break; + case NM_NETV6: + net_addr6(&addr6, &net->oin_routeraddrv6, net->oin_prefixlenv6); + netprefix_print(AF_INET6, &addr6, net->oin_prefixlenv6, buf, + buflen); + break; + case NM_VLAN: + (void) snprintf(buf, buflen, "%" PRIu16, net->oin_vlan); + break; + case NM_ROUTERADDR: + (void) netaddr_print(AF_INET, &net->oin_routeraddr, buf, + buflen); + break; + case NM_ROUTERADDRV6: + (void) netaddr_print(AF_INET6, &net->oin_routeraddrv6, buf, + buflen); + break; + case NM_MAC: + (void) strlcpy(buf, + ether_ntoa((const struct ether_addr *)net->oin_mac), + buflen); + break; + case NM_ROUTETBL: + if (strlen(net->oin_routetbl) == 0) + (void) strlcpy(buf, empty, buflen); + else + (void) strlcpy(buf, net->oin_routetbl, buflen); + break; + } + + return (B_TRUE); +} + +static size_t +router_net_iter_size(size_t nent) +{ + VERIFY3U(nent, <=, OVERLAY_ROUTER_ITER_MAX); + + size_t sz = sizeof (overlay_ioc_net_iter_t); + size_t entsz = nent * sizeof (overlay_ioc_net_t); + size_t tot = 0; + + /* This shouldn't happen, but force a core if it does */ + if (nent != 0 && entsz < nent) + goto overflow; + + tot = sz + entsz; + if (tot < sz) + goto overflow; + + return (tot); + +overflow: + (void) fprintf(stderr, "%s: nent = %zu caused overflow " + "(entsz = %zu, tot = %zu)\n", __func__, nent, entsz, tot); + abort(); +} + +static overlay_ioc_net_iter_t * +net_iter_alloc(const char *ovname, size_t nents) +{ + overlay_ioc_net_iter_t *iter; + + iter = umem_zalloc(router_net_iter_size(nents), UMEM_NOFAIL); + + get_linkid(ovname, &iter->oini_hdr.orih_linkid); + bzero(iter->oini_ents, nents * sizeof (overlay_ioc_net_t)); + iter->oini_count = nents; + return (iter); +} + +static void +net_iter_free(overlay_ioc_net_iter_t *iter, size_t nents) +{ + if (iter == NULL) + return; + umem_free(iter, router_net_iter_size(nents)); +} + +static int +do_router_iter(const char *ovname, const char *ofields, uint_t flags) +{ + const size_t nents = 64; + overlay_ioc_net_iter_t *iter = net_iter_alloc(ovname, nents); + ofmt_handle_t ofmt = NULL; + ofmt_status_t oferr; + int fd = open_overlay(overlay_dev, B_TRUE); + int ret; + + oferr = ofmt_open(ofields, net_fields, flags, 0, &ofmt); + if (oferr != OFMT_SUCCESS) { + char ebuf[OFMT_BUFSIZE]; + + errx(EXIT_FAILURE, "%s: ofmt_open failed: %s", __func__, + ofmt_strerror(ofmt, oferr, ebuf, sizeof (ebuf))); + } + + for (;;) { + ret = ioctl(fd, OVERLAY_ROUTER_NET_ITER, iter); + + if (ret != 0) + err(EXIT_FAILURE, "error iterating router nets"); + + if (iter->oini_count == 0) + break; + + for (uint_t i = 0; i < iter->oini_count; i++) + ofmt_print(ofmt, &iter->oini_ents[i]); + + bzero(iter->oini_ents, nents * sizeof (overlay_ioc_net_t)); + iter->oini_count = nents; + } + + ofmt_close(ofmt); + VERIFY0(close(fd)); + net_iter_free(iter, nents); + return (0); +} + +static int +do_router_get(int argc, char **argv) +{ + const char *ovname = NULL; + const char *ofields = "all"; + uint_t flags = 0; + int c; + + while ((c = getopt(argc, argv, "d:o:p")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + case 'o': + ofields = optarg; + break; + case 'p': + flags |= OFMT_PARSABLE; + break; + } + } + + if (ovname == NULL) { + (void) fprintf(stderr, "Missing overlay device name\n"); + usage(); + } + + if (optind == argc) + return (do_router_iter(ovname, ofields, flags)); + + datalink_id_t dlid; + int fd; + ofmt_handle_t ofmt = NULL; + ofmt_status_t oferr; + + oferr = ofmt_open(ofields, net_fields, flags, 0, &ofmt); + if (oferr != OFMT_SUCCESS) { + char ebuf[OFMT_BUFSIZE]; + + errx(EXIT_FAILURE, "%s: ofmt_open failed: %s", __func__, + ofmt_strerror(ofmt, oferr, ebuf, sizeof (ebuf))); + } + + get_linkid(ovname, &dlid); + fd = open_overlay(overlay_dev, B_FALSE); + + for (int i = optind; i < argc; i++) { + overlay_ioc_net_t net = { + .oin_hdr.orih_linkid = dlid + }; + int ret; + + (void) strlcpy(net.oin_id, argv[i], sizeof (net.oin_id)); + ret = ioctl(fd, OVERLAY_ROUTER_NET_GET, &net); + if (ret != 0) { + err(EXIT_FAILURE, "Failed to get info on router id %s", + argv[i]); + } + + ofmt_print(ofmt, &net); + } + + ofmt_close(ofmt); + return (0); +} + +static int +do_route_table(int argc, char **argv) +{ + return (dispatch(argc, argv, route_tbl_tbl, ARRAY_SIZE(route_tbl_tbl))); +} + +static int +do_routetbl_create(int argc, char **argv) +{ + const char *ovname = NULL; + overlay_ioc_routetab_t rtbl = { 0 }; + int c, ret; + + while ((c = getopt(argc, argv, "d:")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + } + } + + if (argc == optind) { + (void) fprintf(stderr, "Missing router table id\n"); + usage(); + } + + if (ovname == NULL) { + (void) fprintf(stderr, "Missing overlay device name\n"); + usage(); + } + + parse_id(argv[optind], rtbl.oir_id, sizeof (rtbl.oir_id)); + get_linkid(ovname, &rtbl.oir_hdr.orih_linkid); + + ret = do_ioctl(OVERLAY_ROUTETBL_SET, &rtbl, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, + "failed to create overlay routing table %s on overlay %s", + argv[optind], ovname); + } + + return (0); +} + +static int +do_routetbl_delete(int argc, char **argv) +{ + const char *ovname = NULL; + overlay_ioc_routetab_t rtbl = { 0 }; + int c, ret; + + while ((c = getopt(argc, argv, "d:")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + } + } + + if (argc == optind) { + (void) fprintf(stderr, "Missing router table id\n"); + usage(); + } + + if (ovname == NULL) { + (void) fprintf(stderr, "Missing overlay device name\n"); + usage(); + } + + parse_id(argv[optind], rtbl.oir_id, sizeof (rtbl.oir_id)); + get_linkid(ovname, &rtbl.oir_hdr.orih_linkid); + + ret = do_ioctl(OVERLAY_ROUTETBL_REMOVE, &rtbl, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, + "failed to create overlay routing table %s on overlay %s", + argv[optind], ovname); + } + + return (0); +} + +static int +do_routetbl_set_default(int argc, char **argv) +{ + const char *ovname = NULL; + int c, ret; + overlay_ioc_routetab_t rtbl = { 0 }; + + while ((c = getopt(argc, argv, "d:")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + } + } + + if (ovname == NULL) { + (void) fprintf(stderr, "Overlay device name missing\n"); + usage(); + } + + if (argc < optind) { + (void) fprintf(stderr, "Missing router table name\n"); + usage(); + } + + parse_id(argv[optind], rtbl.oir_id, sizeof (rtbl.oir_id)); + get_linkid(ovname, &rtbl.oir_hdr.orih_linkid); + + ret = do_ioctl(OVERLAY_ROUTETBL_SET_DEFAULT, &rtbl, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, + "failed to set default routing table on %s to %s\n", + ovname, argv[optind]); + } + + return (0); +} + +static boolean_t +routetbl_print(ofmt_arg_t *ofmt, char *buf, uint_t buflen) +{ + overlay_ioc_routetab_t *tbl = ofmt->ofmt_cbarg; + + switch ((routetbl_member_t)ofmt->ofmt_id) { + case RM_ID: + (void) strlcpy(buf, tbl->oir_id, buflen); + break; + case RM_NENTS: + (void) snprintf(buf, buflen, "%" PRIu16, tbl->oir_count); + break; + } + + return (B_TRUE); +} + +static boolean_t +routeent_print(ofmt_arg_t *ofmt, char *buf, uint_t buflen) +{ + const routetbl_print_t *prt = ofmt->ofmt_cbarg; + const overlay_route_ent_t *ent = prt->rpt_ent; + const void *addr; + in_addr_t v4; + int af; + uint8_t pfxlen = ent->ore_prefixlen; + + switch ((routetbl_ent_memb_t)ofmt->ofmt_id) { + case REM_ID: + (void) strlcpy(buf, prt->rpt_id, buflen); + break; + case REM_DEST: + if (IN6_IS_ADDR_V4MAPPED(&ent->ore_dest)) { + af = AF_INET; + IN6_V4MAPPED_TO_IPADDR(&ent->ore_dest, v4); + addr = &v4; + pfxlen -= 96; + } else { + af = AF_INET6; + addr = &ent->ore_dest; + } + + netprefix_print(af, addr, pfxlen, buf, buflen); + break; + case REM_TARGET: + (void) sockaddr_print(&ent->ore_target, buf, buflen); + break; + } + + return (B_TRUE); +} + +static size_t +route_tbl_size(size_t nents) +{ + size_t hdr = sizeof (overlay_ioc_routetab_t); + size_t entsz = nents * sizeof (overlay_route_ent_t); + size_t tot = hdr + entsz; + + if (nents > 0 && entsz < nents) + goto overflow; + if (tot < hdr) + goto overflow; + + return (tot); + +overflow: + (void) fprintf(stderr, "%s: %s: overflow nents=%zu entsz=%zu tot=%zu\n", + __progname, __func__, nents, entsz, tot); + abort(); +} + +static overlay_ioc_routetab_t * +route_tbl_alloc(const char *ovname, size_t nents) +{ + overlay_ioc_routetab_t *tbl; + + tbl = umem_zalloc(route_tbl_size(nents), UMEM_NOFAIL); + tbl->oir_count = nents; + get_linkid(ovname, &tbl->oir_hdr.orih_linkid); + + return (tbl); +} + +static void +route_tbl_free(overlay_ioc_routetab_t *tbl, size_t nents) +{ + if (tbl == NULL) + return; + + umem_free(tbl, route_tbl_size(nents)); +} + +static overlay_ioc_rtab_iter_t * +route_tbl_iter_alloc(const char *ovname, size_t nents) +{ + overlay_ioc_rtab_iter_t *iter; + size_t hdr = sizeof (overlay_ioc_rtab_iter_t); + size_t entsz = nents * sizeof (overlay_ioc_routetab_t); + size_t totsz = hdr + entsz; + + VERIFY3U(nents, <=, OVERLAY_ROUTER_ITER_MAX); + + if (nents > 0 && entsz < nents) + goto overflow; + if (totsz < hdr) + goto overflow; + + iter = umem_zalloc(totsz, UMEM_NOFAIL); + iter->oiri_count = nents; + get_linkid(ovname, &iter->oiri_hdr.orih_linkid); + + return (iter); + +overflow: + (void) fprintf(stderr, + "%s: %s: overflow nents=%zu entsz=%zu totsz=%zu\n", __progname, + __func__, nents, entsz, totsz); + abort(); +} + +static void +route_tbl_iter_free(overlay_ioc_rtab_iter_t *iter, size_t nents) +{ + if (iter == NULL) + return; + + size_t len = sizeof (overlay_ioc_rtab_iter_t) + + nents * sizeof (overlay_ioc_routetab_t); + + umem_free(iter, len); +} + +static int +do_routetbl_iter(const char *ovname, const char *ofields, uint_t flags) +{ + const size_t nents = 64; + overlay_ioc_rtab_iter_t *iter; + ofmt_handle_t ofmt; + ofmt_status_t oferr; + int fd; + + iter = route_tbl_iter_alloc(ovname, nents); + fd = open_overlay(overlay_dev, B_TRUE); + + oferr = ofmt_open(ofields, routetbl_fields, flags, 0, &ofmt); + if (oferr != OFMT_SUCCESS) { + char ebuf[OFMT_BUFSIZE]; + + errx(EXIT_FAILURE, "%s: ofmt_open failed: %s", __func__, + ofmt_strerror(ofmt, oferr, ebuf, sizeof (ebuf))); + } + + for (;;) { + int ret; + + ret = ioctl(fd, OVERLAY_ROUTETBL_ITER, iter); + if (ret != 0) + err(EXIT_FAILURE, "error iterating routing tables"); + + if (iter->oiri_count == 0) + break; + + for (uint_t i = 0; i < iter->oiri_count; i++) + ofmt_print(ofmt, &iter->oiri_rtabs[i]); + + bzero(iter->oiri_rtabs, + nents * sizeof (overlay_ioc_routetab_t)); + iter->oiri_count = nents; + } + + ofmt_close(ofmt); + VERIFY0(close(fd)); + route_tbl_iter_free(iter, nents); + return (0); +} + +static int +do_routetbl_get(int argc, char **argv) +{ + const char *ovname = NULL; + const char *ofields = "all"; + uint_t flags = 0; + int c; + + while ((c = getopt(argc, argv, "d:o:p")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + case 'o': + ofields = optarg; + break; + case 'p': + flags |= OFMT_PARSABLE; + break; + } + } + + if (ovname == NULL) { + (void) fprintf(stderr, "Missing overlay device name\n"); + usage(); + } + + if (optind == argc) + return (do_routetbl_iter(ovname, ofields, flags)); + + int fd; + ofmt_handle_t ofmt = NULL; + ofmt_status_t oferr; + overlay_ioc_routetab_t *tbl = NULL; + size_t nents = 64; + + oferr = ofmt_open(ofields, routeent_fields, flags, 0, &ofmt); + if (oferr != OFMT_SUCCESS) { + char ebuf[OFMT_BUFSIZE]; + + errx(EXIT_FAILURE, "%s: ofmt_open failed: %s", __func__, + ofmt_strerror(ofmt, oferr, ebuf, sizeof (ebuf))); + } + + fd = open_overlay(overlay_dev, B_FALSE); + + tbl = route_tbl_alloc(ovname, nents); + + for (int i = optind; i < argc; i++) { + routetbl_print_t prt = { 0 }; + datalink_id_t linkid = tbl->oir_hdr.orih_linkid; + int ret; + + (void) strlcpy(prt.rpt_id, argv[i], sizeof (prt.rpt_id)); + + /* + * Clear out tbl to be used for the next route table. + * We need to re-populate the linkid and route table id + * though. + */ + bzero(tbl, sizeof (*tbl)); + tbl->oir_hdr.orih_linkid = linkid; + (void) strlcpy(tbl->oir_id, argv[i], sizeof (tbl->oir_id)); + + for (;;) { + tbl->oir_count = nents; + ret = ioctl(fd, OVERLAY_ROUTETBL_GET, tbl); + if (ret != 0) { + err(EXIT_FAILURE, + "failed to get routing table %s in %s", + argv[i], ovname); + } + + if (tbl->oir_count == 0) + break; + + for (size_t j = 0; j < tbl->oir_count; j++) { + prt.rpt_ent = &tbl->oir_ents[j]; + ofmt_print(ofmt, &prt); + } + } + } + + route_tbl_free(tbl, nents); + ofmt_close(ofmt); + VERIFY0(close(fd)); + return (0); +} + +static int +do_routetbl_ent_common(int argc, char **argv, boolean_t add) +{ + const int cmd = add ? OVERLAY_ROUTETBL_ADDENT : OVERLAY_ROUTETBL_DELENT; + const char *opstr = add ? "add" : "remove"; + + const char *ovname = NULL; + const char *rtbl_id = NULL; + int c, ret; + overlay_ioc_routetab_t *tbl = NULL; + overlay_route_ent_t *ent = NULL; + + while ((c = getopt(argc, argv, "d:i:")) != -1) { + switch (c) { + case 'd': + ovname = optarg; + break; + case 'i': + rtbl_id = optarg; + break; + } + } + + if (argc - optind < 2) { + (void) fprintf(stderr, "missing destination and/or target\n"); + usage(); + } + + if (ovname == NULL) { + (void) fprintf(stderr, "missing overlay device name\n"); + usage(); + } + + if (rtbl_id == NULL) { + (void) fprintf(stderr, "Missing routing table id\n"); + usage(); + } + + tbl = route_tbl_alloc(ovname, 1); + ent = tbl->oir_ents; + + parse_id(rtbl_id, tbl->oir_id, sizeof (tbl->oir_id)); + get_linkid(ovname, &tbl->oir_hdr.orih_linkid); + + parse_addr(argv[optind], &ent->ore_dest, &ent->ore_prefixlen); + parse_addr_port(argv[optind + 1], &ent->ore_target); + + ret = do_ioctl(cmd, tbl, B_FALSE); + if (ret != 0) { + err(EXIT_FAILURE, "failed to %s entry", opstr); + } + + route_tbl_free(tbl, 1); + return (0); +} + +static int +do_routetbl_addent(int argc, char **argv) +{ + return (do_routetbl_ent_common(argc, argv, B_TRUE)); +} + +static int +do_routetbl_delent(int argc, char **argv) +{ + return (do_routetbl_ent_common(argc, argv, B_FALSE)); +} + +static int +do_ioctl(int cmd, void *arg, boolean_t ro) +{ + int fd = open_overlay(overlay_dev, ro); + int ret = ioctl(fd, cmd, arg); + int errsave = errno; + VERIFY0(close(fd)); + errno = errsave; + return (ret); +} + +static void +get_linkid(const char *name, datalink_id_t *linkp) +{ + dladm_handle_t handle = NULL; + dladm_status_t status; + char errmsg[DLADM_STRSIZE] = { 0 }; + + status = dladm_open(&handle); + if (status != DLADM_STATUS_OK) { + errx(EXIT_FAILURE, "could not open /dev/dld: %s", + dladm_status2str(status, errmsg)); + } + + status = dladm_name2info(handle, name, linkp, NULL, NULL, NULL); + if (status != DLADM_STATUS_OK) { + errx(EXIT_FAILURE, "failed to find %s: %s", name, + dladm_status2str(status, errmsg)); + } + + dladm_close(handle); +} + +static int +open_overlay(const char *ovname, boolean_t ro) +{ + int fd; + + fd = open(overlay_dev, ro ? O_RDONLY : O_RDWR); + if (fd == -1) + err(EXIT_FAILURE, "failed to open %s", overlay_dev); + + return (fd); +} + +static void +parse_vlan(const char *str, uint16_t *valp) +{ + unsigned long uval; + + errno = 0; + uval = strtoul(str, NULL, 0); + if (errno != 0) + err(EXIT_FAILURE, "Failed to parse '%s' as a vlan id", str); + + if (uval < VLAN_ID_MIN || uval > VLAN_ID_MAX) { + errx(EXIT_FAILURE, "Vlan id %lu out of range (%d-%d)\n", uval, + VLAN_ID_MIN, VLAN_ID_MAX); + } + + *valp = uval; +} + +static void +parse_mac(const char *str, uint8_t *macp) +{ + if (ether_aton_r(str, (struct ether_addr *)macp) == NULL) + errx(EXIT_FAILURE, "Invalid mac address '%s'", str); +} + +static void +parse_id(const char *str, char *id, size_t idlen) +{ + static regex_t re = { 0 }; + static boolean_t re_compiled = B_FALSE; + int ret; + + if (!re_compiled) { + VERIFY0(regcomp(&re, id_re, REG_EXTENDED)); + re_compiled = B_TRUE; + } + + ret = regexec(&re, str, 0, NULL, 0); + if (ret != 0) + errx(EXIT_FAILURE, "Invalid id '%s'", str); + + /* + * The regex also validates the length of the id, so truncation + * here should not be a concern (but stlcpy() is still used as a + * precaution). + */ + (void) strlcpy(id, str, idlen); +} + +/* + * To parse a lone address, prefixlenp should be NULL. If prefixlenp is + * non-NULL, attempt to parse '/nnn' after the address as the + * prefix length (if missing from the string, assume /128 -- i.e. single + * address). + */ +static void +parse_addr(const char *str, struct in6_addr *addrp, uint8_t *prefixlenp) +{ + char *astr = strdup(str); + char *pfxstr = astr; + int af; + + if (astr == NULL) { + (void) fprintf(stderr, "Out of memory\n"); + abort(); + } + + /* + * This should break things up into two strings -- the address (astr) + * and the prefix (pfxstr). If no prefix present, pfxstr should be + * NULL. + */ + (void) strsep(&pfxstr, "/"); + + /* + * If the string contains a '.', we assume it's IPv4, otherwise + * assume IPv6. XXX: is there a better way here? + */ + if (strchr(astr, '.') != NULL) { + in_addr_t v4; + + if (inet_pton(AF_INET, astr, &v4) != 1) + err(EXIT_FAILURE, "Invalid IPv4 address '%s'", astr); + + IN6_IPADDR_TO_V4MAPPED(v4, addrp); + af = AF_INET; + } else { + if (inet_pton(AF_INET6, astr, addrp) != 1) + err(EXIT_FAILURE, "Invalid IPv6 address '%s'", astr); + af = AF_INET6; + } + + if (prefixlenp == NULL) { + if (pfxstr != NULL) + errx(EXIT_FAILURE, "Invalid address '%s'", str); + + free(astr); + return; + } + + if (pfxstr == NULL) { + *prefixlenp = 128; + free(astr); + return; + } + + unsigned long pval; + const unsigned long max = (af == AF_INET) ? 32 : 128; + + errno = 0; + pval = strtoul(pfxstr, NULL, 10); + if (errno != 0) { + err(EXIT_FAILURE, "Failed to parse '%s' as a prefix length", + pfxstr); + } + + if (pval > max) { + err(EXIT_FAILURE, "Invalid prefix length '%lu'", pval); + } + + *prefixlenp = (af == AF_INET) ? 128 - (32 - pval) : pval; + free(astr); +} + +/* + * Parse address + port. Supported strings: + * + * :port + * + * []:port + * + * The latter is required for IPv6 (but optional for IPv4) to disambiguate + * between the IPv6 address and port. + */ +static void +parse_addr_port(const char *str, struct sockaddr_in6 *addrp) +{ + char *astr = strdup(str); + char *portstr = astr; + unsigned long pval; + + if (astr == NULL) { + (void) fprintf(stderr, "Out of memory\n"); + abort(); + } + + bzero(addrp, sizeof (struct sockaddr_in6)); + + /* + * Since IPv4 addresses are mapped to IPv6, we always set this + * to AF_INET6 for consistency. + */ + addrp->sin6_family = AF_INET6; + + if (str[0] != '[') { + if (strchr(astr, '.') != NULL) { + in_addr_t v4; + + (void) strsep(&portstr, ":"); + + if (inet_pton(AF_INET, astr, &v4) != 1) { + errx(EXIT_FAILURE, "Invalid IPv4 address '%s'", + astr); + } + + IN6_IPADDR_TO_V4MAPPED(v4, &addrp->sin6_addr); + } else { + /* + * If it's an unbracketed IPv6 address, there is no + * port, only zuu^Wan address. + */ + portstr = NULL; + + if (inet_pton(AF_INET6, astr, &addrp->sin6_addr) != 1) { + errx(EXIT_FAILURE, "Invalid IPv6 address '%s'", + astr); + } + } + } else { + char *p = astr + 1; + + /* Find the matching ']' */ + (void) strsep(&portstr, "]"); + if (portstr == NULL) + errx(EXIT_FAILURE, "Unmatched ']' in address"); + + /* + * If there's a port, the next character should be ':' + * e.g. '[1:2::3]:5678'. If not, *portstr should point + * to the original terminating NUL. + */ + if (*portstr == ':') { + portstr++; + } else { + if (*portstr != '\0') { + errx(EXIT_FAILURE, "Invalid address/port '%s'", + str); + } + portstr = NULL; + } + + if (strchr(p, '.') != NULL) { + in_addr_t v4; + + if (inet_pton(AF_INET, p, &v4) != 1) { + err(EXIT_FAILURE, "Invalid IPv4 address '%s'", + p); + } + + IN6_IPADDR_TO_V4MAPPED(v4, &addrp->sin6_addr); + } else { + if (inet_pton(AF_INET6, p, &addrp->sin6_addr) != 1) { + err(EXIT_FAILURE, "Invalid IPv6 address '%s'", + p); + } + } + } + + /* If no port given, just leave it as default (0) */ + if (portstr == NULL) { + free(astr); + return; + } + + errno = 0; + pval = strtoul(portstr, NULL, 10); + if (errno != 0) { + err(EXIT_FAILURE, "Failed to parse port value '%s'", + portstr); + } + + if (pval > UINT16_MAX) { + errx(EXIT_FAILURE, "Port value '%lu' out of range", + pval); + } + + addrp->sin6_port = htons(pval); + free(astr); +} + +/* + * Debug builds are automatically wired up for umem debugging. + */ +#ifdef DEBUG +const char * +_umem_debug_init() +{ + return ("default,verbose"); +} + +const char * +_umem_logging_init(void) +{ + return ("fail,contents"); +} +#endif /* DEBUG */ diff --git a/usr/src/common/util/bsearch.c b/usr/src/common/util/bsearch.c index ac16aaec2b9b..1a7bfb6398b5 100644 --- a/usr/src/common/util/bsearch.c +++ b/usr/src/common/util/bsearch.c @@ -27,17 +27,18 @@ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ -#pragma ident "%Z%%M% %I% %E% SMI" +/* Copyright 2020 Joyent, Inc. */ /* * Binary search algorithm, generalized from Knuth (6.2.1) Algorithm B. */ -#if !defined(_BOOT) && !defined(_KMDB) -#include "lint.h" -#endif /* !_BOOT && !_KMDB */ +#ifndef _KERNEL #include #include +#else +#include +#endif #include void * diff --git a/usr/src/lib/varpd/direct/common/libvarpd_direct.c b/usr/src/lib/varpd/direct/common/libvarpd_direct.c index 018cdf641c01..0e957ef4040b 100644 --- a/usr/src/lib/varpd/direct/common/libvarpd_direct.c +++ b/usr/src/lib/varpd/direct/common/libvarpd_direct.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2016 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -48,7 +48,7 @@ typedef struct varpd_direct { mutex_t vad_lock; /* Protects the rest */ boolean_t vad_hip; boolean_t vad_hport; - struct in6_addr vad_ip; + struct in6_addr vad_ip; uint16_t vad_port; } varpd_direct_t; diff --git a/usr/src/lib/varpd/files/common/libvarpd_files.c b/usr/src/lib/varpd/files/common/libvarpd_files.c index 812919a07d1f..0b35112f94e6 100644 --- a/usr/src/lib/varpd/files/common/libvarpd_files.c +++ b/usr/src/lib/varpd/files/common/libvarpd_files.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015, Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -28,7 +28,7 @@ * The plug-in only has a single property, which is the location of the JSON * file. The JSON file itself looks something like: * - * { + * { * "aa:bb:cc:dd:ee:ff": { * "arp": "10.23.69.1", * "ndp": "2600:3c00::f03c:91ff:fe96:a264", @@ -238,25 +238,26 @@ varpd_files_lookup(void *arg, varpd_query_handle_t *qh, return; } - if (otl->otl_sap == ETHERTYPE_ARP) { + if (otl->otl_u.otl_l2.otl2_sap == ETHERTYPE_ARP) { libvarpd_plugin_proxy_arp(vaf->vaf_hdl, qh, otl); return; } - if (otl->otl_sap == ETHERTYPE_IPV6 && - otl->otl_dstaddr[0] == 0x33 && - otl->otl_dstaddr[1] == 0x33) { + if (otl->otl_u.otl_l2.otl2_sap == ETHERTYPE_IPV6 && + otl->otl_u.otl_l2.otl2_dstaddr[0] == 0x33 && + otl->otl_u.otl_l2.otl2_dstaddr[1] == 0x33) { libvarpd_plugin_proxy_ndp(vaf->vaf_hdl, qh, otl); return; } - if (otl->otl_sap == ETHERTYPE_IP && - bcmp(otl->otl_dstaddr, bcast, ETHERADDRL) == 0) { + if (otl->otl_u.otl_l2.otl2_sap == ETHERTYPE_IP && + bcmp(otl->otl_u.otl_l2.otl2_dstaddr, bcast, ETHERADDRL) == 0) { char *mac; struct ether_addr a, *addr; addr = &a; - if (ether_ntoa_r((struct ether_addr *)otl->otl_srcaddr, + if (ether_ntoa_r( + (struct ether_addr *)otl->otl_u.otl_l2.otl2_srcaddr, macstr) == NULL) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; @@ -281,7 +282,7 @@ varpd_files_lookup(void *arg, varpd_query_handle_t *qh, return; } - if (ether_ntoa_r((struct ether_addr *)otl->otl_dstaddr, + if (ether_ntoa_r((struct ether_addr *)otl->otl_u.otl_l2.otl2_dstaddr, macstr) == NULL) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; @@ -543,7 +544,7 @@ varpd_files_proxy_dhcp(void *arg, varpd_dhcp_handle_t *vdh, int type, return; } - if (ether_ntoa_r((struct ether_addr *)otl->otl_srcaddr, + if (ether_ntoa_r((struct ether_addr *)otl->otl_u.otl_l2.otl2_srcaddr, macstr) == NULL) { libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP); return; diff --git a/usr/src/lib/varpd/libvarpd/common/libvarpd_arp.c b/usr/src/lib/varpd/libvarpd/common/libvarpd_arp.c index df69207fe0fb..f6d3d28ac82b 100644 --- a/usr/src/lib/varpd/libvarpd/common/libvarpd_arp.c +++ b/usr/src/lib/varpd/libvarpd/common/libvarpd_arp.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -43,7 +43,7 @@ typedef struct varpd_arp_query { varpd_query_handle_t *vaq_query; const overlay_targ_lookup_t *vaq_otl; ip6_t *vaq_ip6; - nd_neighbor_solicit_t *vaq_ns; + nd_neighbor_solicit_t *vaq_ns; } varpd_arp_query_t; typedef struct varpd_dhcp_query { @@ -75,7 +75,7 @@ libvarpd_plugin_proxy_arp(varpd_provider_handle_t *hdl, } vaq->vaq_bsize = sizeof (vaq->vaq_buf); - if (otl->otl_sap != ETHERTYPE_ARP) { + if (otl->otl_u.otl_l2.otl2_sap != ETHERTYPE_ARP) { libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); umem_free(vaq, sizeof (varpd_arp_query_t)); return; @@ -248,8 +248,8 @@ libvarpd_plugin_proxy_ndp(varpd_provider_handle_t *hdl, } vaq->vaq_bsize = sizeof (vaq->vaq_buf); - if (otl->otl_dstaddr[0] != 0x33 || - otl->otl_dstaddr[1] != 0x33) { + if (otl->otl_u.otl_l2.otl2_dstaddr[0] != 0x33 || + otl->otl_u.otl_l2.otl2_dstaddr[1] != 0x33) { libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); umem_free(vaq, sizeof (varpd_arp_query_t)); return; @@ -505,13 +505,14 @@ libvarpd_plugin_proxy_dhcp(varpd_provider_handle_t *hdl, } vdq->vdq_bsize = sizeof (vdq->vdq_buf); - if (otl->otl_sap != ETHERTYPE_IP) { + if (otl->otl_u.otl_l2.otl2_sap != ETHERTYPE_IP) { libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); umem_free(vdq, sizeof (varpd_dhcp_query_t)); return; } - if (bcmp(otl->otl_dstaddr, libvarpd_arp_bcast, ETHERADDRL) != 0) { + if (bcmp(otl->otl_u.otl_l2.otl2_dstaddr, libvarpd_arp_bcast, + ETHERADDRL) != 0) { libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); umem_free(vdq, sizeof (varpd_dhcp_query_t)); return; diff --git a/usr/src/lib/varpd/libvarpd/common/libvarpd_impl.h b/usr/src/lib/varpd/libvarpd/common/libvarpd_impl.h index 60f0dc5ffff9..28abd9d1c711 100644 --- a/usr/src/lib/varpd/libvarpd/common/libvarpd_impl.h +++ b/usr/src/lib/varpd/libvarpd/common/libvarpd_impl.h @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ #ifndef _LIBVARPD_IMPL_H @@ -234,7 +234,7 @@ extern int libvarpd_overlay_cache_get(varpd_instance_t *, const uint8_t *, varpd_client_cache_entry_t *); extern int libvarpd_overlay_cache_set(varpd_instance_t *, const uint8_t *, const varpd_client_cache_entry_t *); -extern int libvarpd_overlay_cache_walk_fill(varpd_instance_t *, uint64_t *, +extern int libvarpd_overlay_cache_walk_fill(varpd_instance_t *, void *, uint64_t *, overlay_targ_cache_entry_t *); extern void libvarpd_persist_init(varpd_impl_t *); diff --git a/usr/src/lib/varpd/libvarpd/common/libvarpd_overlay.c b/usr/src/lib/varpd/libvarpd/common/libvarpd_overlay.c index 124e3c5791b8..8bb29fac135d 100644 --- a/usr/src/lib/varpd/libvarpd/common/libvarpd_overlay.c +++ b/usr/src/lib/varpd/libvarpd/common/libvarpd_overlay.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -459,7 +459,7 @@ libvarpd_overlay_cache_set(varpd_instance_t *inst, const uint8_t *key, } int -libvarpd_overlay_cache_walk_fill(varpd_instance_t *inst, uint64_t *markerp, +libvarpd_overlay_cache_walk_fill(varpd_instance_t *inst, void *markerp, uint64_t *countp, overlay_targ_cache_entry_t *ents) { int ret; @@ -477,7 +477,7 @@ libvarpd_overlay_cache_walk_fill(varpd_instance_t *inst, uint64_t *markerp, return (ENOMEM); iter->otci_linkid = inst->vri_linkid; - iter->otci_marker = *markerp; + bcopy(markerp, &iter->otci_marker, sizeof (iter->otci_marker)); iter->otci_count = *countp; ret = ioctl(vip->vdi_overlayfd, OVERLAY_TARG_CACHE_ITER, iter); if (ret != 0 && errno == EFAULT) @@ -487,7 +487,7 @@ libvarpd_overlay_cache_walk_fill(varpd_instance_t *inst, uint64_t *markerp, goto out; } - *markerp = iter->otci_marker; + bcopy(&iter->otci_marker, markerp, sizeof (iter->otci_marker)); *countp = iter->otci_count; bcopy(iter->otci_ents, ents, *countp * sizeof (overlay_targ_cache_entry_t)); diff --git a/usr/src/lib/varpd/libvarpd/common/libvarpd_provider.h b/usr/src/lib/varpd/libvarpd/common/libvarpd_provider.h index 64fa99d3080e..c5adf397ee62 100644 --- a/usr/src/lib/varpd/libvarpd/common/libvarpd_provider.h +++ b/usr/src/lib/varpd/libvarpd/common/libvarpd_provider.h @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ #ifndef _LIBVARPD_PROVIDER_H @@ -86,158 +86,158 @@ * * varpd_plugin_create_f * - * Create a new instance of a plugin. Each instance refers to a different - * overlay device and thus a different overlay identifier. Each instance - * has its own property space and is unique. This function gives the chance - * for the plugin to create and provide any private data that it will - * require. + * Create a new instance of a plugin. Each instance refers to a different + * overlay device and thus a different overlay identifier. Each instance + * has its own property space and is unique. This function gives the chance + * for the plugin to create and provide any private data that it will + * require. * - * In addition, the plugin is given the type of destination that is - * required and it is its job to determine whether or not it supports it. + * In addition, the plugin is given the type of destination that is + * required and it is its job to determine whether or not it supports it. * * varpd_plugin_destory_f * - * This is the opposite of varpd_plugin_create_f. It is called to allow the - * plugin to reclaim any resources with the private argument that it passed - * out as part of the destroy function. + * This is the opposite of varpd_plugin_create_f. It is called to allow the + * plugin to reclaim any resources with the private argument that it passed + * out as part of the destroy function. * * varpd_plugin_start_f * - * This routine is called to indicate that an instance should be started. - * This is a plugin's chance to verify that it has all of its required - * properties set and to take care of any action that needs to be handled - * to begin the plugin. After this point it will be legal to have the - * varpd_plugin_default_f, varpd_plugin_lookup_f, varpd_plugin_arp_f and - * varpd_plugin_dhcp_f endpoints called. + * This routine is called to indicate that an instance should be started. + * This is a plugin's chance to verify that it has all of its required + * properties set and to take care of any action that needs to be handled + * to begin the plugin. After this point it will be legal to have the + * varpd_plugin_default_f, varpd_plugin_lookup_f, varpd_plugin_arp_f and + * varpd_plugin_dhcp_f endpoints called. * * varpd_plugin_stop_f * - * This routine is called to indicate that an instance is stopping, it is - * the opposite of varpd_plugin_start_f. This is a chance to clean up - * resources that are a side effect of having started the instance. + * This routine is called to indicate that an instance is stopping, it is + * the opposite of varpd_plugin_start_f. This is a chance to clean up + * resources that are a side effect of having started the instance. * * varpd_plugin_default_f * - * This routine is defined by plugins of type OVERLAY_TARGET_POINT. It is - * used to answer the question of where should all traffic for this - * instance be destined. Plugins of type OVERLAY_TARGET_DYNAMIC should - * leave this entry set to NULL. + * This routine is defined by plugins of type OVERLAY_TARGET_POINT. It is + * used to answer the question of where should all traffic for this + * instance be destined. Plugins of type OVERLAY_TARGET_DYNAMIC should + * leave this entry set to NULL. * - * On success, the default routine should return VARPD_LOOKUP_OK. On - * failure, it should return the macro VARPD_LOOKUP_DROP. + * On success, the default routine should return VARPD_LOOKUP_OK. On + * failure, it should return the macro VARPD_LOOKUP_DROP. * * varpd_plugin_lookup_f * - * This routine must be defined by plugins of type OVERLAY_TARGET_DYNAMIC. - * It is used to lookup the destination for a given request. Each request - * comes in with its own MAC address this allows a plugin to direct it to - * any remote location. - * - * This is designed as an asynchronous API. Once a lookup is completed it - * should call libvarpd_plugin_query_reply() and pass as the second - * argument either VARPD_LOOKUP_OK to indicate that it went alright or it - * should reply VARPD_LOOKUP_DROP to indicate that the packet should be - * dropped. - * - * In addition, there are several utility routines that can take care of - * various kinds of traffic automatically. For example, if an ARP, NDP, or - * DHCP packet comes in, there are utilities such as - * libvarpd_plugin_proxy_arp(), libvarpd_plugin_proxy_ndp() and - * libvarpd_plugin_proxy_dhcp(), which allows the system to do the heavy - * lifting of validating the packet once it finds that it matches certain - * properties. + * This routine must be defined by plugins of type OVERLAY_TARGET_DYNAMIC. + * It is used to lookup the destination for a given request. Each request + * comes in with its own MAC address this allows a plugin to direct it to + * any remote location. + * + * This is designed as an asynchronous API. Once a lookup is completed it + * should call libvarpd_plugin_query_reply() and pass as the second + * argument either VARPD_LOOKUP_OK to indicate that it went alright or it + * should reply VARPD_LOOKUP_DROP to indicate that the packet should be + * dropped. + * + * In addition, there are several utility routines that can take care of + * various kinds of traffic automatically. For example, if an ARP, NDP, or + * DHCP packet comes in, there are utilities such as + * libvarpd_plugin_proxy_arp(), libvarpd_plugin_proxy_ndp() and + * libvarpd_plugin_proxy_dhcp(), which allows the system to do the heavy + * lifting of validating the packet once it finds that it matches certain + * properties. * * varpd_plugin_arp_f * - * This is an optional entry for plugins of type OVERLAY_TARGET_DYNAMIC. - * This is called after a plugin calls libvarpd_plugin_proxy_arp() and is - * used to ask the plugin to perform an ARP or NDP query. The type of query - * is passed in in the third argument, the only valid value for which will - * be VARPD_QTYPE_ETHERNET, to indicate we're doing an Ethernet lookup. + * This is an optional entry for plugins of type OVERLAY_TARGET_DYNAMIC. + * This is called after a plugin calls libvarpd_plugin_proxy_arp() and is + * used to ask the plugin to perform an ARP or NDP query. The type of query + * is passed in in the third argument, the only valid value for which will + * be VARPD_QTYPE_ETHERNET, to indicate we're doing an Ethernet lookup. * - * The layer three IP address that is being looked up will be included in - * the struct sockaddr. The sockaddr(3SOCKET)'s sa_family will be set to - * indicate the type, eg. AF_INET or AF_INET6 and that will indicate the - * kind of sockaddr that will be used. For more information see - * sockaddr(3SOCKET). The implementation ensures that enough space for the - * link layer address will exist. + * The layer three IP address that is being looked up will be included in + * the struct sockaddr. The sockaddr(3SOCKET)'s sa_family will be set to + * indicate the type, eg. AF_INET or AF_INET6 and that will indicate the + * kind of sockaddr that will be used. For more information see + * sockaddr(3SOCKET). The implementation ensures that enough space for the + * link layer address will exist. * - * This is an asynchronous lookup. Once the answer has been written, a - * plugin should call libvarpd_plugin_arp_reply and if it was successful, - * VARPD_LOOKUP_OK should be passed in and if it failed, VARPD_LOOKUP_DROP - * should be passed in instead. + * This is an asynchronous lookup. Once the answer has been written, a + * plugin should call libvarpd_plugin_arp_reply and if it was successful, + * VARPD_LOOKUP_OK should be passed in and if it failed, VARPD_LOOKUP_DROP + * should be passed in instead. * * varpd_plugin_dhcp_f * - * This is an optional entry for plugins of type OVERLAY_TARGET_DYNAMIC. - * This is called after a plugin calls the libvarpd_plugin_proxy_dhcp() and - * is used to ask the plugin to determine where is the DHCP server that - * this packet should actually be sent to. What is happening here is that - * rather than broadcast the initial DHCP request, we instead unicast it to - * a specified DHCP server that this operation vector indicates. - * - * The plugin is given a type, the same as the ARP plugin which indicates - * the kind of link layer address, the only valid type is - * VARPD_QTYPE_ETHERNET, other types should be rejected. Then, like the arp - * entry point, the dhcp entry point should determine the link layer - * address of the DHCP server and write that out in the appropriate memory - * and call libvarpd_plugin_dhcp_reply() when done. Similar to the arp - * entry point, it should use VARPD_LOOKUP_OK to indicate that it was - * filled in and VARPD_LOOKUP_DROP to indicate that it was not. + * This is an optional entry for plugins of type OVERLAY_TARGET_DYNAMIC. + * This is called after a plugin calls the libvarpd_plugin_proxy_dhcp() and + * is used to ask the plugin to determine where is the DHCP server that + * this packet should actually be sent to. What is happening here is that + * rather than broadcast the initial DHCP request, we instead unicast it to + * a specified DHCP server that this operation vector indicates. + * + * The plugin is given a type, the same as the ARP plugin which indicates + * the kind of link layer address, the only valid type is + * VARPD_QTYPE_ETHERNET, other types should be rejected. Then, like the arp + * entry point, the dhcp entry point should determine the link layer + * address of the DHCP server and write that out in the appropriate memory + * and call libvarpd_plugin_dhcp_reply() when done. Similar to the arp + * entry point, it should use VARPD_LOOKUP_OK to indicate that it was + * filled in and VARPD_LOOKUP_DROP to indicate that it was not. * * varpd_plugin_nprops_f * - * This is used by a plugin to indicate the number of properties that - * should exist for this instance. Recall from the section that Plugin - * types and Destinations, that the number of entries here may vary. As - * such, the plugin should return the number that is appropriate for the - * instance. + * This is used by a plugin to indicate the number of properties that + * should exist for this instance. Recall from the section that Plugin + * types and Destinations, that the number of entries here may vary. As + * such, the plugin should return the number that is appropriate for the + * instance. * - * This number will be used to obtain information about a property via the - * propinfo functions. However, the getprop and setprop interfaces will - * always use names to indicate the property it is getting and setting. - * This difference is structured this way to deal with property discovery - * and to make the getprop and setprop interfaces slightly easier for other - * parts of the broader varpd/dladm infrastructure. + * This number will be used to obtain information about a property via the + * propinfo functions. However, the getprop and setprop interfaces will + * always use names to indicate the property it is getting and setting. + * This difference is structured this way to deal with property discovery + * and to make the getprop and setprop interfaces slightly easier for other + * parts of the broader varpd/dladm infrastructure. * * varpd_plugin_propinfo_f * - * This interface is used to get information about a property, the property - * that information is being requested for is being passed in via the - * second argument. Here, callers should set properties such as the name, - * the protection, whether or not the property is required, set any default - * value, if it exist, and if relevant, set the valid range of values. + * This interface is used to get information about a property, the property + * that information is being requested for is being passed in via the + * second argument. Here, callers should set properties such as the name, + * the protection, whether or not the property is required, set any default + * value, if it exist, and if relevant, set the valid range of values. * * varpd_plugin_getprop_f * - * This is used to get the value of a property, if it is set. The passed in - * length indicates the length of the buffer that is used for updating - * properties. If it is not of sufficient size, the function should return - * an error and not update the buffer. Otherwise, it should update the size - * pointer with the valid size. + * This is used to get the value of a property, if it is set. The passed in + * length indicates the length of the buffer that is used for updating + * properties. If it is not of sufficient size, the function should return + * an error and not update the buffer. Otherwise, it should update the size + * pointer with the valid size. * * varpd_plugin_setprop_f * - * This is used to set the value of a property. An endpoint should validate - * that the property is valid before updating it. In addition, it should - * update its state as appropriate. + * This is used to set the value of a property. An endpoint should validate + * that the property is valid before updating it. In addition, it should + * update its state as appropriate. * * varpd_plugin_save_f * - * This is used to serialize the state of a given instance of a plugin such - * that if varpd crashes, it can be recovered. The plugin should write all - * state into the nvlist that it is passed in, it may use any keys and - * values that it wants. The only consumer of that nvlist will be the - * plugin itself when the restore endpoint is called. + * This is used to serialize the state of a given instance of a plugin such + * that if varpd crashes, it can be recovered. The plugin should write all + * state into the nvlist that it is passed in, it may use any keys and + * values that it wants. The only consumer of that nvlist will be the + * plugin itself when the restore endpoint is called. * * varpd_plugin_restore_f * - * This is called by the server to restore an instance that used to exist, - * but was lost due to a crash. This is a combination of calling create and - * setting properties. The plugin should restore any private state that it - * can find recorded from the nvlist. The only items in the nvlist will be - * those that were written out during a previous call to - * varpd_plugin_save_f. + * This is called by the server to restore an instance that used to exist, + * but was lost due to a crash. This is a combination of calling create and + * setting properties. The plugin should restore any private state that it + * can find recorded from the nvlist. The only items in the nvlist will be + * those that were written out during a previous call to + * varpd_plugin_save_f. * * * Once all of these interfaces are implemented, the plugin should define the @@ -245,28 +245,28 @@ * * vpr_version * - * This indicates the version of the plugin. Plugins should set this to the - * macro VARPD_CURRENT_VERSION. + * This indicates the version of the plugin. Plugins should set this to the + * macro VARPD_CURRENT_VERSION. * * vpr_mode * - * This indicates the mode of the plugin. The plugin's mode should be one - * of OVERLAY_TARGET_POINT and OVERLAY_TARGET_DYNAMIC. For more discussion - * of these types and the differences, see the section on Plugin Types and - * Destinations. + * This indicates the mode of the plugin. The plugin's mode should be one + * of OVERLAY_TARGET_POINT and OVERLAY_TARGET_DYNAMIC. For more discussion + * of these types and the differences, see the section on Plugin Types and + * Destinations. * * vpr_name * - * This is the name of the plugin. This is how users will refer to it in - * the context of running dladm(1M) commands. Note, this name must be - * unique across the different plugins, as it will cause others with the - * same name not to be registered. + * This is the name of the plugin. This is how users will refer to it in + * the context of running dladm(1M) commands. Note, this name must be + * unique across the different plugins, as it will cause others with the + * same name not to be registered. * * vpr_ops * - * This is the operations vector as described above. Importantly, the - * member vpo_callbacks must be set to zero, this is being used for future - * expansion of the structure. + * This is the operations vector as described above. Importantly, the + * member vpo_callbacks must be set to zero, this is being used for future + * expansion of the structure. * * * -------------------------------------------------- diff --git a/usr/src/lib/varpd/svp/common/libvarpd_svp.c b/usr/src/lib/varpd/svp/common/libvarpd_svp.c index 58828065a142..943ca2b47162 100644 --- a/usr/src/lib/varpd/svp/common/libvarpd_svp.c +++ b/usr/src/lib/varpd/svp/common/libvarpd_svp.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015, Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -31,22 +31,22 @@ * * VL3 * - * A VL3 address, or virtual layer 3, refers to the layer three addreses - * that are used by entities on an overlay network. As far as we're - * concerned that means that this is the IP address of an interface on an - * overlay network. + * A VL3 address, or virtual layer 3, refers to the layer three addreses + * that are used by entities on an overlay network. As far as we're + * concerned that means that this is the IP address of an interface on an + * overlay network. * * VL2 * - * A VL2 address, or a virtual layer 2, referes to the link-layer addresses - * that are used by entities on an overlay network. As far as we're - * concerned that means that this is the MAC addresses of an interface on - * an overlay network. + * A VL2 address, or a virtual layer 2, referes to the link-layer addresses + * that are used by entities on an overlay network. As far as we're + * concerned that means that this is the MAC addresses of an interface on + * an overlay network. * * UL3 * - * A UL3, or underlay layer 3, refers to the layer three (IP) address on - * the underlay network. + * A UL3, or underlay layer 3, refers to the layer three (IP) address on + * the underlay network. * * The svp plugin provides lookups from VL3->VL2, eg. the equivalent of an ARP * or NDP query, and then also provides VL2->UL3 lookups. @@ -184,11 +184,11 @@ * backoff for each peer and will attempt to reconect immediately before backing * off. The following are the valid states that a connection can be in: * - * SVP_CS_ERROR An OS error has occurred on this connection, - * such as failure to create a socket or associate - * the socket with an event port. We also - * transition all connections to this state before - * we destroy them. + * SVP_CS_ERROR An OS error has occurred on this connection, + * such as failure to create a socket or associate + * the socket with an event port. We also + * transition all connections to this state before + * we destroy them. * * SVP_CS_INITIAL This is the initial state of a connection, all * that should exist is an unbound socket. @@ -360,7 +360,8 @@ static umem_cache_t *svp_lookup_cache; typedef enum svp_lookup_type { SVP_L_UNKNOWN = 0x0, SVP_L_VL2 = 0x1, - SVP_L_VL3 = 0x2 + SVP_L_ARP = 0x2, + SVP_L_VL3 = 0x3, } svp_lookup_type_t; typedef struct svp_lookup { @@ -370,9 +371,13 @@ typedef struct svp_lookup { varpd_query_handle_t *svl_handle; overlay_target_point_t *svl_point; } svl_vl2; - struct svl_lookup_vl3 { + struct svl_lookup_arp { varpd_arp_handle_t *svl_vah; uint8_t *svl_out; + } svl_arp; + struct svl_lookup_vl3 { + varpd_query_handle_t *svl_handle; + overlay_target_point_t *svl_point; } svl_vl3; } svl_u; svp_query_t svl_query; @@ -426,7 +431,7 @@ svp_vl2_lookup_cb(svp_t *svp, svp_status_t status, const struct in6_addr *uip, } static void -svp_vl3_lookup_cb(svp_t *svp, svp_status_t status, const uint8_t *vl2mac, +svp_arp_lookup_cb(svp_t *svp, svp_status_t status, const uint8_t *vl2mac, const struct in6_addr *uip, const uint16_t uport, void *arg) { overlay_target_point_t point; @@ -436,7 +441,7 @@ svp_vl3_lookup_cb(svp_t *svp, svp_status_t status, const uint8_t *vl2mac, assert(svl != NULL); if (status != SVP_S_OK) { - libvarpd_plugin_arp_reply(svl->svl_u.svl_vl3.svl_vah, + libvarpd_plugin_arp_reply(svl->svl_u.svl_arp.svl_vah, VARPD_LOOKUP_DROP); umem_cache_free(svp_lookup_cache, svl); return; @@ -447,9 +452,40 @@ svp_vl3_lookup_cb(svp_t *svp, svp_status_t status, const uint8_t *vl2mac, point.otp_port = uport; libvarpd_inject_varp(svp->svp_hdl, vl2mac, &point); - bcopy(vl2mac, svl->svl_u.svl_vl3.svl_out, ETHERADDRL); - libvarpd_plugin_arp_reply(svl->svl_u.svl_vl3.svl_vah, + bcopy(vl2mac, svl->svl_u.svl_arp.svl_out, ETHERADDRL); + libvarpd_plugin_arp_reply(svl->svl_u.svl_arp.svl_vah, + VARPD_LOOKUP_OK); + umem_cache_free(svp_lookup_cache, svl); +} + +static void +svp_vl3_lookup_cb(svp_t *svp, svp_status_t status, const uint8_t *vl2mac, + const struct in6_addr *uip, const uint16_t uport, void *arg) +{ + svp_lookup_t *svl = arg; + overlay_target_point_t *otp; + overlay_target_point_t l2_point = { 0 }; + + assert(svp != NULL); + assert(svl != NULL); + + if (status != SVP_S_OK) { + libvarpd_plugin_query_reply(svl->svl_u.svl_vl3.svl_handle, + VARPD_LOOKUP_DROP); + umem_cache_free(svp_lookup_cache, svl); + return; + } + + /* Inject the L2 mapping before the L3 */ + bcopy(uip, &l2_point.otp_ip, sizeof (struct in6_addr)); + l2_point.otp_port = uport; + libvarpd_inject_varp(svp->svp_hdl, vl2mac, &l2_point); + + otp = svl->svl_u.svl_vl3.svl_point; + bcopy(vl2mac, otp->otp_mac, ETHERADDRL); + libvarpd_plugin_query_reply(svl->svl_u.svl_vl3.svl_handle, VARPD_LOOKUP_OK); + umem_cache_free(svp_lookup_cache, svl); } @@ -486,13 +522,14 @@ svp_shootdown_cb(svp_t *svp, const uint8_t *vl2mac, const struct in6_addr *uip, const uint16_t uport) { /* - * We should probably do a conditional invlaidation here. + * We should probably do a conditional invalidation here. */ libvarpd_inject_varp(svp->svp_hdl, vl2mac, NULL); } static svp_cb_t svp_defops = { svp_vl2_lookup_cb, + svp_arp_lookup_cb, svp_vl3_lookup_cb, svp_vl2_invalidate_cb, svp_vl3_inject_cb, @@ -587,23 +624,22 @@ varpd_svp_destroy(void *arg) } static void -varpd_svp_lookup(void *arg, varpd_query_handle_t *vqh, +varpd_svp_l2lookup(svp_t *svp, varpd_query_handle_t *vqh, const overlay_targ_lookup_t *otl, overlay_target_point_t *otp) { svp_lookup_t *slp; - svp_t *svp = arg; /* * Check if this is something that we need to proxy, eg. arp or ndp. */ - if (otl->otl_sap == ETHERTYPE_ARP) { + if (otl->otl_u.otl_l2.otl2_sap == ETHERTYPE_ARP) { libvarpd_plugin_proxy_arp(svp->svp_hdl, vqh, otl); return; } - if (otl->otl_dstaddr[0] == 0x33 && - otl->otl_dstaddr[1] == 0x33) { - if (otl->otl_sap == ETHERTYPE_IPV6) { + if (otl->otl_u.otl_l2.otl2_dstaddr[0] == 0x33 && + otl->otl_u.otl_l2.otl2_dstaddr[1] == 0x33) { + if (otl->otl_u.otl_l2.otl2_sap == ETHERTYPE_IPV6) { libvarpd_plugin_proxy_ndp(svp->svp_hdl, vqh, otl); } else { libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); @@ -617,8 +653,8 @@ varpd_svp_lookup(void *arg, varpd_query_handle_t *vqh, * handle broadcast and if the multicast bit is set, lowest bit of the * first octet of the MAC, then we drop it now. */ - if (bcmp(otl->otl_dstaddr, svp_bcast, ETHERADDRL) == 0 || - (otl->otl_dstaddr[0] & 0x01) == 0x01) { + if (bcmp(otl->otl_u.otl_l2.otl2_dstaddr, svp_bcast, ETHERADDRL) == 0 || + (otl->otl_u.otl_l2.otl2_dstaddr[0] & 0x01) == 0x01) { libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); return; } @@ -639,7 +675,66 @@ varpd_svp_lookup(void *arg, varpd_query_handle_t *vqh, slp->svl_u.svl_vl2.svl_handle = vqh; slp->svl_u.svl_vl2.svl_point = otp; - svp_remote_vl2_lookup(svp, &slp->svl_query, otl->otl_dstaddr, slp); + svp_remote_vl2_lookup(svp, &slp->svl_query, + otl->otl_u.otl_l2.otl2_dstaddr, slp); +} + +static void +varpd_svp_l3lookup(svp_t *svp, varpd_query_handle_t *vqh, + const overlay_targ_lookup_t *otl, overlay_target_point_t *otp) +{ + svp_lookup_t *svl; + struct sockaddr_storage saddr = { 0 }; + const struct sockaddr *addrp = (const struct sockaddr *)&saddr; + + /* + * If we have a failure to allocate memory for this, that's not good. + * However, telling the kernel to just drop this packet is much better + * than the alternative at this moment. At least we'll try again and we + * may have something more available to us in a little bit. + */ + svl = umem_cache_alloc(svp_lookup_cache, UMEM_DEFAULT); + if (svl == NULL) { + libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP); + return; + } + + svl->svl_type = SVP_L_VL3; + svl->svl_u.svl_vl3.svl_handle = vqh; + svl->svl_u.svl_vl3.svl_point = otp; + if (IN6_IS_ADDR_V4MAPPED(&otl->otl_u.otl_l3.otl3_dest)) { + struct sockaddr_in *sin = (struct sockaddr_in *)&saddr; + + sin->sin_family = AF_INET; + IN6_V4MAPPED_TO_INADDR(&otl->otl_u.otl_l3.otl3_dest, + &sin->sin_addr); + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&saddr; + + sin6->sin6_family = AF_INET6; + bcopy(&otl->otl_u.otl_l3.otl3_dest, &sin6->sin6_addr, + sizeof (struct in6_addr)); + } + + /* + * svp_remove_vl3_lookup copies the address (i.e. doesn't keep a + * copy of the pointer, so we can pass a pointer to the address on + * the stack. + */ + svp_remote_vl3_lookup(svp, &svl->svl_query, addrp, svl); +} + +static void +varpd_svp_lookup(void *arg, varpd_query_handle_t *vqh, + const overlay_targ_lookup_t *otl, overlay_target_point_t *otp) +{ + svp_t *svp = arg; + + if (otl->otl_l3lookup) { + return (varpd_svp_l3lookup(svp, vqh, otl, otp)); + } else { + return (varpd_svp_l2lookup(svp, vqh, otl, otp)); + } } /* ARGSUSED */ @@ -1000,10 +1095,10 @@ varpd_svp_arp(void *arg, varpd_arp_handle_t *vah, int type, return; } - svl->svl_type = SVP_L_VL3; - svl->svl_u.svl_vl3.svl_vah = vah; - svl->svl_u.svl_vl3.svl_out = out; - svp_remote_vl3_lookup(svp, &svl->svl_query, sock, svl); + svl->svl_type = SVP_L_ARP; + svl->svl_u.svl_arp.svl_vah = vah; + svl->svl_u.svl_arp.svl_out = out; + svp_remote_arp_lookup(svp, &svl->svl_query, sock, svl); } static const varpd_plugin_ops_t varpd_svp_ops = { diff --git a/usr/src/lib/varpd/svp/common/libvarpd_svp.h b/usr/src/lib/varpd/svp/common/libvarpd_svp.h index 8192b842ce5e..b944d88dee60 100644 --- a/usr/src/lib/varpd/svp/common/libvarpd_svp.h +++ b/usr/src/lib/varpd/svp/common/libvarpd_svp.h @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ #ifndef _LIBVARPD_SVP_H @@ -141,7 +141,7 @@ typedef struct svp_conn_out { } svp_conn_out_t; typedef struct svp_conn_in { - svp_query_t *sci_query; + svp_query_t *sci_query; svp_req_t sci_req; size_t sci_offset; } svp_conn_in_t; @@ -168,7 +168,7 @@ struct svp_conn { typedef enum svp_remote_state { SVP_RS_LOOKUP_SCHEDULED = 0x01, /* On the DNS Queue */ - SVP_RS_LOOKUP_INPROGRESS = 0x02, /* Doing a DNS lookup */ + SVP_RS_LOOKUP_INPROGRESS = 0x02, /* Doing a DNS lookup */ SVP_RS_LOOKUP_VALID = 0x04 /* addrinfo valid */ } svp_remote_state_t; @@ -216,7 +216,7 @@ struct svp_remote { cond_t sr_cond; svp_remote_state_t sr_state; svp_degrade_state_t sr_degrade; - struct addrinfo *sr_addrinfo; + struct addrinfo *sr_addrinfo; avl_tree_t sr_tree; uint_t sr_count; /* active count */ uint_t sr_gen; @@ -248,6 +248,7 @@ typedef void (*svp_shootdown_f)(svp_t *, const uint8_t *, typedef struct svp_cb { svp_vl2_lookup_f scb_vl2_lookup; + svp_vl3_lookup_f scb_arp_lookup; svp_vl3_lookup_f scb_vl3_lookup; svp_vl2_invalidation_f scb_vl2_invalidate; svp_vl3_inject_f scb_vl3_inject; @@ -262,7 +263,7 @@ struct svp { varpd_provider_handle_t *svp_hdl; /* RO */ svp_cb_t svp_cb; /* RO */ uint64_t svp_vid; /* RO */ - avl_node_t svp_rlink; /* Owned by svp_remote */ + avl_node_t svp_rlink; /* Owned by svp_remote */ svp_remote_t *svp_remote; /* RO iff started */ mutex_t svp_lock; char *svp_host; /* svp_lock */ @@ -279,6 +280,8 @@ extern int svp_remote_find(char *, uint16_t, struct in6_addr *, extern int svp_remote_attach(svp_remote_t *, svp_t *); extern void svp_remote_detach(svp_t *); extern void svp_remote_release(svp_remote_t *); +extern void svp_remote_arp_lookup(svp_t *, svp_query_t *, + const struct sockaddr *, void *); extern void svp_remote_vl3_lookup(svp_t *, svp_query_t *, const struct sockaddr *, void *); extern void svp_remote_vl2_lookup(svp_t *, svp_query_t *, const uint8_t *, diff --git a/usr/src/lib/varpd/svp/common/libvarpd_svp_remote.c b/usr/src/lib/varpd/svp/common/libvarpd_svp_remote.c index 99775f93c0a8..dd43de18711b 100644 --- a/usr/src/lib/varpd/svp/common/libvarpd_svp_remote.c +++ b/usr/src/lib/varpd/svp/common/libvarpd_svp_remote.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2019 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -38,7 +38,7 @@ typedef struct svp_shoot_vl3 { svp_query_t ssv_query; struct sockaddr_in6 ssv_sock; - svp_log_vl3_t *ssv_vl3; + svp_log_vl3_t *ssv_vl3; svp_sdlog_t *ssv_log; } svp_shoot_vl3_t; @@ -239,8 +239,8 @@ svp_remote_attach(svp_remote_t *srp, svp_t *svp) */ if (svp->svp_cb.scb_vl2_lookup == NULL) libvarpd_panic("missing callback scb_vl2_lookup"); - if (svp->svp_cb.scb_vl3_lookup == NULL) - libvarpd_panic("missing callback scb_vl3_lookup"); + if (svp->svp_cb.scb_arp_lookup == NULL) + libvarpd_panic("missing callback scb_arp_lookup"); if (svp->svp_cb.scb_vl2_invalidate == NULL) libvarpd_panic("missing callback scb_vl2_invalidate"); if (svp->svp_cb.scb_vl3_inject == NULL) @@ -351,6 +351,21 @@ svp_remote_vl2_lookup(svp_t *svp, svp_query_t *sqp, const uint8_t *mac, mutex_exit(&srp->sr_lock); } +static void +svp_remote_arp_lookup_cb(svp_query_t *sqp, void *arg) +{ + svp_t *svp = sqp->sq_svp; + svp_vl3_ack_t *vl3a = (svp_vl3_ack_t *)sqp->sq_wdata; + + if (sqp->sq_status == SVP_S_OK) + svp->svp_cb.scb_arp_lookup(svp, sqp->sq_status, vl3a->sl3a_mac, + (struct in6_addr *)vl3a->sl3a_uip, ntohs(vl3a->sl3a_uport), + arg); + else + svp->svp_cb.scb_arp_lookup(svp, sqp->sq_status, NULL, NULL, 0, + arg); +} + static void svp_remote_vl3_lookup_cb(svp_query_t *sqp, void *arg) { @@ -368,7 +383,7 @@ svp_remote_vl3_lookup_cb(svp_query_t *sqp, void *arg) static void svp_remote_vl3_common(svp_remote_t *srp, svp_query_t *sqp, - const struct sockaddr *addr, svp_query_f func, void *arg, uint32_t vid) + const struct sockaddr *addr, svp_query_f func, void *arg, uint32_t vid) { svp_vl3_req_t *vl3r = &sqp->sq_rdun.sdq_vl3r; @@ -426,6 +441,17 @@ svp_remote_vl3_logreq(svp_remote_t *srp, svp_query_t *sqp, uint32_t vid, svp_remote_vl3_common(srp, sqp, addr, func, arg, vid); } +void +svp_remote_arp_lookup(svp_t *svp, svp_query_t *sqp, + const struct sockaddr *addr, void *arg) +{ + svp_remote_t *srp = svp->svp_remote; + + sqp->sq_svp = svp; + svp_remote_vl3_common(srp, sqp, addr, svp_remote_arp_lookup_cb, + arg, svp->svp_vid); +} + void svp_remote_vl3_lookup(svp_t *svp, svp_query_t *sqp, const struct sockaddr *addr, void *arg) diff --git a/usr/src/man/man1m/Makefile b/usr/src/man/man1m/Makefile index 1eaa441df34c..cf5c8de09a9e 100644 --- a/usr/src/man/man1m/Makefile +++ b/usr/src/man/man1m/Makefile @@ -11,7 +11,7 @@ # # Copyright 2011, Richard Lowe -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright 2015 Nexenta Systems, Inc. All rights reserved. # Copyright (c) 2013, 2016 by Delphix. All rights reserved. # Copyright 2016 Toomas Soome @@ -333,6 +333,7 @@ _MANFILES= 6to4relay.1m \ nlsadmin.1m \ nscd.1m \ nwamd.1m \ + ovroute.1m \ passmgmt.1m \ pbind.1m \ pcitool.1m \ diff --git a/usr/src/man/man1m/ovroute.1m b/usr/src/man/man1m/ovroute.1m new file mode 100644 index 000000000000..a06377031c01 --- /dev/null +++ b/usr/src/man/man1m/ovroute.1m @@ -0,0 +1,582 @@ +.\" +.\" This file and its contents are supplied under the terms of the +.\" Common Development and Distribution License ("CDDL"), version 1.0. +.\" You may only use this file in accordance with the terms of version +.\" 1.0 of the CDDL. +.\" +.\" A full copy of the text of the CDDL should have accompanied this +.\" source. A copy of the CDDL is also available via the Internet at +.\" http://www.illumos.org/license/CDDL. +.\" +.\" +.\" Copyright 2021 Joyent, Inc. +.\" +.Dd February 9, 2021 +.Dt OVROUTE 1M +.Os +.Sh NAME +.Nm ovroute +.Nd overlay router management +.Sh SYNOPSIS +.\" +.\" ovroute router create ... +.\" +.Nm +.Cm router create +.Fl d Ar overlay +.Fl m Ar macaddr +.Fl v Ar vlan +.Fl a Ar address/mask +.Oo Fl a Ar address/mask Oc +.Oo Fl r Ar route_table_id Oc +.Ar router_id +.\" +.\" router delete +.\" +.Nm +.Cm router delete +.Fl d Ar overlay +.Ar router_id +.\" +.\" router get +.\" +.Nm +.Cm router get +.Fl d Ar overlay +.Oo Fl o Ar field Ns Oo ,... Oc Oo Fl p Oc Oc +.Oo Ar router_id Ns ... Oc +.\" +.\" set-routing-table +.\" +.Nm +.Cm router set-routing-table +.Fl d Ar overlay +.Fl r Ar route_table_id +.Ar router_id +.\" +.\" route-table create +.\" +.Nm +.Cm route-table create +.Fl d Ar overlay +.Ar route_table_id +.\" +.\" route-table delete +.\" +.Nm +.Cm route-table delete +.Fl d Ar overlay +.Ar route_table_id +.\" +.\" route-table get +.\" +.Nm +.Cm route-table get +.Fl d Ar overlay +.Ar route_table_id +.Oo Fl o Ar field Ns Oo ,... Oc Oo Fl p Oc Oc +.Oo Ar route_table_id Ns ... Oc +.\" +.\" route-table set-default +.\" +.Nm +.Cm route-table set-default +.Fl d Ar overlay +.Ar route_table_id +.\" +.\" route-table add +.\" +.Nm +.Cm route-table add +.Fl d Ar overlay +.Fl i Ar route_table_id +.Ar destination +.Ar target +.\" +.\" route-table del +.\" +.Nm +.Cm route-table del +.Fl d Ar overlay +.Fl i Ar route_table_id +.Ar destination +.Ar target +.\" +.\" ================================ +.Sh DESCRIPTION +.\" ================================ +The +.Nm +utility is used to manage routers within an +.Xr overlay 5 +network. +.Xr overlay 5 +routers are used to move packets within an overlay network off the virtual +layer two (VL2) network. +.Pp +For example, two VL2 subnets may wish to route packets to each other, +regardless of which physical system instances using those VL2 networks reside. +As long as the underlay IPs +.Po +the UL3 address aka the IP address of the underlying interface the overlay +instance sits on, e.g. the IP address of +.Ql sdc_underlay0 +.Pc of the source and destination systems are reachable to each other, +and the virtual layer three (VL3) \(-> virtual layer two (VL2) mappings +for the source and destination VL3 IPs, the +.Nm +command can be run to setup routing between these two subnets. +Normally, these entries are managed automatically \(em e.g. by the Triton +.Ql net-agent +process, and should normally not need to be altered by an operator. +.Pp +To route packets between VL2 subnets, an +.Xr overlay 5 +router instance is created for each VL2 network where packets are intended to +be routed. +An +.Xr overlay 5 +router instance should be created on any physical machine where the +corresponding VL2 subnet is in use. +.Pp +For example, if a subnet with a vlan id of +.Ql 2112 +and an address range of +.Ql 192.168.1.0/24 +on overlay +.Ql sdc_underlay42 +exists, then any machine that houses an instance with an IP address within +.Ql 192.168.1.0/24 +should have an overlay router instance for the +.Ql 192.168.1.0/24 +subnet created on it. +The +.Ar address/mask +and +.Ar macaddr +parameters should be the same for all such systems. +It is also recommended, but not required, that the router id used also +match. +It is not necessary to create an +.Xr overlay 5 +router instance on a machine if the corresponding subnet is not in use +(no instances have IP assigned to the subnet) on that machine. +.Pp +Additionally, an +.Xr overlay 5 +route table must be associated with each +.Xr overlay 5 +router instance before any routing will occur. +Multiple router instances in a given +.Xr overlay 5 +instance can share the same route table. +Both the source and destination systems must be configured with +.Xr overlay 5 +router instances for any VL2 networks that wish to route packets between each +other. +Failure to do so will result in dropped packets. +.Pp +Once the router instances and router tables have been created, any +packets sent to the VL3 router IP by the instance (using the instance's routing +table), will then have the destination VL3 IP matched in the +.Xr overlay 5 +routing table for the router instance that received the packet. +The match is a longest prefix match, similar to how +.Xr route 7P +works for traditional IP forwarding. +If the target address is the +.Dq any +.Po +.Ql 0.0.0.0 +or +.Ql ::0 +.Pc +address, the packets are routed to the destination subnet and the destionation +machine. +If another address in the +.Xr overlay 5 +routing table is the longest prefix match for the VL3 destination IP, the +corresponding target IP:port value is the underlay IP address where the +encapsulated packet is sent. +This is normally used for features such as EIP, NAT or ELB. +If there is no matching destination, the packet is dropped. +.\" +.\" ===================================== +.Sh OPTIONS +.\" ===================================== +The following options are supported: +.Bl -tag -width overlay +.\" +.It Xo Nm Cm router create +.\" ---------------------- +.Fl d Ar overlay +.Fl m Ar macaddr +.Fl v Ar vlan +.Fl a Ar address/mask +.Oo Fl a Ar address/mask Oc +.Oo Fl r route_table_id Oc +.Ar router_id +.Xc +.Pp +Create an +.Xr overlay 5 +router instance on the overlay device +.Ar overlay +with an id of +.Ar router_id . +The +.Ar router_id +must be a unique identifer across all the +.Xr overlay 5 +router instances for the given +.Xr overlay 5 +instance. +Valid id values consists of characters from the set [A-Za-z0-9.-]. +The MAC address for the router instance is specified by +.Ar macaddr +and the vlan id of the network for the router is given by +.Ar vlan . +The IP address and subnet mask the router instance listens on is given by +.Ar address/mask . +If the subnet has both an IPv4 and IPv6 address, an additional +.Fl a Ar address/mask +option may be specified, but at most only 1 IPv4 and 1 IPv6 address may be +given (and at least 1 address must be specified). +.Pp +Optionally, if an +.Xr overlay 5 +routing table has already been defined, the +.Fl r Ar route_table_id +option can be specified to associate the VL2 subnet to the given +.Xr overlay 5 +route table. +If no route table is specified, the default route table for the +.Xr overlay 5 +device (if set) will be used. +.\" +.It Xo Nm Cm router delete +.\" ---------------------- +.Fl d Ar overlay +.Ar router_id +.Xc +Delete the +.Xr overlay 5 +router instance denoted by +.Ar router_id . +.\" +.It Xo Nm Cm router get +.\" ------------------- +.Fl d Ar overlay +.Oo Fl o Ar field Ns Oo ,... Oc Oo Fl p Oc Oc +.Oo Ar router_id Ns ... Oc +.Xc +Get the information about +.Xr overlay 5 +router +.Ar router_id . +If no +.Ar router_id +is specified, all +.Xr overlay 5 +router instances defined for +.Ar overlay +are printed. +If the +.Fl o +option is specified, the output is limited to the comma separated list of +fields given by the +.Ar field +arguments(s): +.Bl -tag -offset indent -width "NETWORK-V6" +.It id +The +.Ar router_id +as passed in to the +.Nm Cm router create +command. +.It network +The overlay IPv4 network and CIDR mask the router listens on, e\.g\. +.Ql 192.168.1.0/24 . +.It network-v6 +The overlay IPv6 network and CIDR mask the router listens on. +.It vlan +The VLAN id of the subnet the overlay router instance is listening on. +.It mac +The MAC address the overlay router instance uses for the given subnet. +.It rtable +The overlay routing table associated with this subnet (if any). +.El +.Pp +Additionally, the +.Fl p +option may be passed with the +.Fl o +option to indicate the output should be in a machine parsable (colon delimited) +format. +The +.Fl p +option requires specifying the +.Fl o +option to indicate the fields to output. +.\" +.It Xo Nm Cm router set-routing-table +.\" --------------------------------- +.Fl d Ar overlay +.Fl r Ar route_table_id +.Ar router_id +.Xc +Set the routing table for +.Ar router_id +on overlay +.Ar overlay +to +.Ar route_table_id . +.Ar route_table_id +must already exist, or the command will return an error. +.\" +.It Xo Nm Cm route-table create +.\" --------------------------- +.Fl d Ar overlay +.Fl r Ar route_table_id +.Ar router_id +.Xc +Creates an empty +.Xr overlay 5 +router table with an id +Ar route_table_id +for overlay +.Ar overlay . +If the route table already exists, an error is returned. +.\" +.It Xo Nm Cm route-table delete +.\" --------------------------- +.Fl d Ar overlay +.Ar route_table_id +.Xc +Deletes the route table +.Ar route_table_id +on overlay +.Ar overlay . +.\" +.It Xo Nm Cm route-table get +.\" ------------------------ +.Fl d Ar overlay +.Oo Fl o Ar field Ns Oo ,... Oc Oo Fl p Oc Oc +.Oo Ar route_table_id Ns ... Oc +.Xc +Display the routing table +.Ar route_table_id +on overlay +.Ar overlay . +If more than one +.Ar route_table_id +is specified, each route table is displayed. +If no route tables are specified, all route tables on +.Ar overlay +are displayed. +If the +.Fl o +option is specified, a comma separated list of fields must be specified to +indicate the fields to display. +The +.Ar field +values may be any combination of: +.Bl -tag -offset indent -width "destination" +.It id +The overlay route table id as is given in the +.Nm Cm route-table create +command. +.It destination +The virtual layer three (VL3) address of the destination in CIDR format. +In other words, the destination subnet and CIDR mask as seen by instances in +the overlay network. +.It target +The underlay layer three (UL3) address and port to send the matching traffic. +In other words, the destination address of the encapsulated packet. +.El +.Pp +Additionally, the +.Fl p +option may be given with the +.Fl o +option to generate output in machine parsable (colon delimited) format. +.\" +.It Xo Nm Cm route-table set-default +.\" -------------------------------- +.Fl d Ar overlay +.Ar route_table_id +.Xc +Set the default routing table for +.Ar overlay +to +.Ar route_table_id . +If +.Ar route_table_id +doesn't exist, an error is returned. +.\" +.It Xo Nm Cm route-table add +.\" ------------------------ +.Fl d Ar overlay +.Fl i Ar route_table_id +.Ar destination +.Ar target +.Xc +Adds an route table entry to the route table +.Ar route_table_id +in overlay +.Ar overlay . +.It Xo Nm Cm route-table del +.Fl d Ar overlay +.Fl i Ar route_table_id +.Ar destination +.Ar target +.Xc +Removes a route table entry from the route table +.Ar route_table_id +in overlay +.Ar overlay . +.El +.\" +.\" ================================ +.Sh OPERANDS +.\" ================================ +The following operands are supported: +.Bl -tag -width Ar +.It Ar overlay +.\" ---------- +The overlay device to operate on. +For example +.Ql sdc_underlay1234 . +.It Ar macaddr +.\" ---------- +A MAC address such as +.Ql 90:01:02:03:04:05 . +.It Ar vlan +.\" ------- +A VLAN id. +Valid VLAN ids range from 1 - 4096. +.It Ar address/mask +An IPv4 or IPv6 address and CIDR mask length, e.g. +.Ql 192.168.1.0/24 . +.It Ar route_table_id +.\" ----------------- +A routing table id. +.It Ar router_id +.\" ------------ +An +.Xr overlay 5 +router id. +.It Ar destination +.\" -------------- +The virtual layer three (VL3) destination CIDR block for an +.Xr overlay 5 +route table entry. +For example, +.Ql 10.1.2.0/24 +or +.Ql 2001::1/96 . +.It Ar target +.\" --------- +The underlay layer three (UL3) address and port that receives the encapsulated +virtual packet. +For IPv4 addresses this in the form of +.Ql IP addr:port . +For IPv6 addresses, to disambiguate the address from the port, the address +must be in the form of +.Ql [IPv6 addr]:port . +The IPv4 address can optionally be given in this form as well, but it is not +required. +An +.Dq any +.Po +e.g. +.Ql 0.0.0.0/0 +or +.Ql ::0/0 +.Pc +address (with no port) indicates the encapsulated packet should be routed and +delivered to the machine that contains the VL3 destination address of the +packet. +.El +.\" ================================ +.Sh EXIT STATUS +.\" ================================ +.Ex -std +.\" ================================ +.Sh EXAMPLES +.\" ================================ +.Bl -inset +.It Example 1 +.Pp +In this setup, we have the following hosts, all using an overlay instance +.Ql sdc_overlay7224243 +with a vnet ID of +.Ql 7224243 : +.TS +box tab (@); +c | c | c +l | l | l. +HOST@VLAN@SUBNET +_ +hostA@42@192.168.1.0/24 +_ +hostB@2112@192.168.2.0/24 +_ +hostC@42@192.168.1.0/24 +@2112@192.168.2.0/24 +.TE +.Pp +The router instances will be as follows: +.TS +box tab (@); +c | c | c +l | l | l. +SUBNET@ROUTER IP@ROUTER MAC +_ +192.168.1.0/24@192.168.1.1@90:b8:d0:17:f7:be +_ +192.168.2.0/24@192.168.2.2@90:b8:d0:7f:36:ac +.TE +.Pp +To enable all three vnics to route traffic between the two subnets, the +appropriate router instances must be created on each host. +Additionally, a routing table must be created to indicate that the traffic sent +to the router IP should be routed to the other subnets. +.Pp +The following commands are run on the respective hosts to enable this: +.Bd -literal +hostA# ovroute router create -d sdc_overlay7224243 -m 90:b8:d0:17:f7:be -v 42 -a 192.168.1.1/24 router-42 +hostA# ovroute route-table create -d sdc_overlay7224243 rtab-default +hostA# ovroute route-table set-default -d sdc_overlay7224243 rtab-default +hostA# ovroute route-table add -d sdc_overlay7224243 -i rtab-default 0.0.0.0/0 0.0.0.0 + +hostB# ovroute router create -d sdc_overlay7224243 -m 90:b8:d0:7f:36:ac -v 2112 -a 192.168.2.1/24 router-2112 +hostB# ovroute route-table create -d sdc_overlay7224243 rtab-default +hostB# ovroute route-table set-default -d sdc_overlay7224243 rtab-default +hostB# ovroute route-table add -d sdc_overlay7224243 -i rtab-default 0.0.0.0/0 0.0.0.0 + +hostC# ovroute router create -d sdc_overlay7224243 -m 90:b8:d0:17:f7:be -v 42 -a 192.168.1.1/24 router-42 +hostC# ovroute router create -d sdc_overlay7224243 -m 90:b8:d0:7f:36:ac -v 2112 -a 192.168.2.1/24 router-2112 +hostC# ovroute route-table create -d sdc_overlay7224243 rtab-default +hostC# ovroute route-table set-default -d sdc_overlay7224243 rtab-default +hostC# ovroute route-table add -d sdc_overlay7224243 -i rtab-default 0.0.0.0/0 0.0.0.0 +.Ed +.Pp +Note that +.Ql 192.168.0.0/16 0.0.0.0 +or similar could also have been used in the routing table to route the +traffic. +As long as the destination network and mask match the traffic. +.El +.\" ================================ +.Sh INTERFACE STABILITY +.\" ================================ +Private +.Pp +This is intented to be an internal Triton utility, and its command line +interface may change without notice. +.\" ================================ +.Sh SEE ALSO +.\" ================================ +.Xr overlay 5 , +.Xr route 7P diff --git a/usr/src/test/os-tests/runfiles/default.run b/usr/src/test/os-tests/runfiles/default.run index 15dae9c74538..1c66d0ab3f9a 100644 --- a/usr/src/test/os-tests/runfiles/default.run +++ b/usr/src/test/os-tests/runfiles/default.run @@ -11,7 +11,7 @@ # # Copyright (c) 2012 by Delphix. All rights reserved. -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright 2020 OmniOS Community Edition (OmniOSce) Association. # @@ -86,6 +86,7 @@ tests = ['acquire-compare', 'kmc-update'] [/opt/os-tests/tests/OS-6097.32] [/opt/os-tests/tests/OS-6097.64] +[/opt/os-tests/tests/qqcache] [/opt/os-tests/tests/ddi_ufm] user = root diff --git a/usr/src/test/os-tests/tests/Makefile b/usr/src/test/os-tests/tests/Makefile index 190c61e13407..e3366dcce4d6 100644 --- a/usr/src/test/os-tests/tests/Makefile +++ b/usr/src/test/os-tests/tests/Makefile @@ -11,7 +11,7 @@ # # Copyright (c) 2012, 2016 by Delphix. All rights reserved. -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # SUBDIRS_i386 = i386 imc @@ -23,6 +23,7 @@ SUBDIRS = \ libtopo \ pf_key \ poll \ + qqcache \ sdevfs \ secflags \ sigqueue \ diff --git a/usr/src/test/os-tests/tests/qqcache/Makefile b/usr/src/test/os-tests/tests/qqcache/Makefile new file mode 100644 index 000000000000..1cd54c462cce --- /dev/null +++ b/usr/src/test/os-tests/tests/qqcache/Makefile @@ -0,0 +1,58 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2021 Joyent, Inc. +# + +include $(SRC)/cmd/Makefile.cmd +include $(SRC)/test/Makefile.com + +PROG = qqcache + +LOCAL_OBJS = qqcache-test.o +COMMON_OBJS = qqcache.o +COMMON_DIR = $(SRC)/uts/common/qqcache +OBJS = $(LOCAL_OBJS) $(COMMON_OBJS) + +ROOTOPTPKG = $(ROOT)/opt/os-tests +TESTDIR = $(ROOTOPTPKG)/tests/qqcache + +CMDS = $(PROG:%=$(TESTDIR)/%) +$(CMDS) := FILEMODE = 0555 + +CSTD = $(CSTD_GNU99) +LDLIBS += -lumem -lcmdutils + +all: $(PROG) + +install: all $(CMDS) + +clobber: clean + -$(RM) $(PROG) $(OBJS) + +clean: + -$(RM) $(CLEANFILES) + +$(CMDS): $(TESTDIR) $(PROG) + +$(TESTDIR): + $(INS.dir) + +$(TESTDIR)/%: % + $(INS.file) + +$(PROG): $(OBJS) + $(LINK.c) -o $@ $(OBJS) $(LDLIBS) + +%.o: $(COMMON_DIR)/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) diff --git a/usr/src/test/os-tests/tests/qqcache/qqcache-test.c b/usr/src/test/os-tests/tests/qqcache/qqcache-test.c new file mode 100644 index 000000000000..c8157f4eaea5 --- /dev/null +++ b/usr/src/test/os-tests/tests/qqcache/qqcache-test.c @@ -0,0 +1,389 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Some arbitrary sizes */ +#define INITIAL_CACHE_SIZE 12 +#define INITIAL_CACHE_A 25 +#define CACHE_HSIZE 11 + +#define OUTPUT_WIDTH 80 + +/* + * If we extend the implementation to use more lists, the test code will need + * to be updated accordingly + */ +CTASSERT(QQCACHE_NUM_LISTS == 2); + +typedef struct entry { + uint_t e_val; + qqcache_link_t e_link; +} entry_t; + +enum { + ITER_ERROR = -1, + ITER_OK = 0, + ITER_STOP = 1 +}; + +static uint64_t entry_hash(const void *); +static int entry_cmp(const void *, const void *); +static void entry_dtor(void *); +static entry_t *entry_new(uint_t val); + +static void expect(qqcache_t *, uint_t *, size_t, uint_t *, size_t, int); +static void expect_val(qqcache_t *, const entry_t *, uint_t); +static void dump_cache(qqcache_t *); +static int iter_list(qqcache_t *, size_t, int (*)(void *, void *), void *); +static int xprintf(FILE *, const char *, ...); + +int +main(void) +{ + qqcache_t *qc; + uint_t val; + + VERIFY0(qqcache_create(&qc, INITIAL_CACHE_SIZE, INITIAL_CACHE_A, + CACHE_HSIZE, entry_hash, entry_cmp, entry_dtor, sizeof (entry_t), + offsetof(entry_t, e_link), offsetof(entry_t, e_val), UMEM_DEFAULT)); + + /* Create a few entries */ + VERIFY0(qqcache_insert(qc, entry_new(5))); + VERIFY0(qqcache_insert(qc, entry_new(4))); + VERIFY0(qqcache_insert(qc, entry_new(3))); + VERIFY0(qqcache_insert(qc, entry_new(2))); + VERIFY0(qqcache_insert(qc, entry_new(1))); + /*CSTYLED*/ + expect(qc, NULL, 0, (uint_t[]){1, 2, 3, 4, 5}, 5, __LINE__); + + /* Adding a duplicate should fail */ + { + entry_t *e = entry_new(3); + VERIFY3S(qqcache_insert(qc, e), ==, EEXIST); + entry_dtor(e); + } + + /* + * cstyle currently cannot handle compound literals, so just + * quiet it for now. + */ + + /* BEGIN CSTYLED */ + VERIFY0(qqcache_insert(qc, entry_new(10))); + VERIFY0(qqcache_insert(qc, entry_new(9))); + VERIFY0(qqcache_insert(qc, entry_new(8))); + VERIFY0(qqcache_insert(qc, entry_new(7))); + /* This should bump the LRU entry (5) from the list */ + VERIFY0(qqcache_insert(qc, entry_new(6))); + expect(qc, NULL, 0, + (uint_t[]){6, 7, 8, 9, 10, 1, 2, 3, 4}, 9, __LINE__); + + /* Lookup a few entries to move them to the MFU list */ + val = 3; + expect_val(qc, qqcache_lookup(qc, &val), 3); + expect(qc, (uint_t[]) {3}, 1, + (uint_t[]){6, 7, 8, 9, 10, 1, 2, 4}, 8, __LINE__); + + val = 8; + expect_val(qc, qqcache_lookup(qc, &val), 8); + expect(qc, (uint_t[]) {8, 3}, 2, + (uint_t[]){6, 7, 9, 10, 1, 2, 4}, 7, __LINE__); + + /* Now move 3 back to the head of list 0 */ + val = 3; + expect_val(qc, qqcache_lookup(qc, &val), 3); + expect(qc, (uint_t[]) {3, 8}, 2, + (uint_t[]){6, 7, 9, 10, 1, 2, 4}, 7, __LINE__); + + val = 7; + expect_val(qc, qqcache_lookup(qc, &val), 7); + expect(qc, (uint_t[]) {7, 3, 8}, 3, + (uint_t[]){6, 9, 10, 1, 2, 4}, 6, __LINE__); + + /* This should push 8 from the MFU back onto the MRU */ + val = 10; + expect_val(qc, qqcache_lookup(qc, &val), 10); + expect(qc, (uint_t[]) {10, 7, 3}, 3, + (uint_t[]){8, 6, 9, 1, 2, 4}, 6, __LINE__); + + /* Add some more values */ + VERIFY0(qqcache_insert(qc, entry_new(11))); + VERIFY0(qqcache_insert(qc, entry_new(12))); + VERIFY0(qqcache_insert(qc, entry_new(13))); + VERIFY0(qqcache_insert(qc, entry_new(14))); + VERIFY0(qqcache_insert(qc, entry_new(15))); + expect(qc, (uint_t[]) {10, 7, 3}, 3, + (uint_t[]){15, 14, 13, 12, 11, 8, 6, 9, 1, 2}, 9, __LINE__); + + VERIFY0(qqcache_adjust_size(qc, INITIAL_CACHE_SIZE + 4)); + expect(qc, (uint_t[]) {10, 7, 3}, 3, + (uint_t[]){15, 14, 13, 12, 11, 8, 6, 9, 1, 2}, 9, __LINE__); + + VERIFY0(qqcache_insert(qc, entry_new(16))); + VERIFY0(qqcache_insert(qc, entry_new(17))); + VERIFY0(qqcache_insert(qc, entry_new(18))); + VERIFY0(qqcache_insert(qc, entry_new(19))); + expect(qc, (uint_t[]) {10, 7, 3}, 3, + (uint_t[]) {19, 18, 17, 16, 15, 14, 13, 12, 11, 8, 6, 9}, 12, + __LINE__); + + VERIFY0(qqcache_adjust_size(qc, INITIAL_CACHE_SIZE - 2)); + expect(qc, (uint_t[]) {10, 7}, 2, + (uint_t[]){3, 19, 18, 17, 16, 15, 14, 13}, 8, __LINE__); + + VERIFY3S(qqcache_adjust_size(qc, 2), ==, EINVAL); + + VERIFY0(qqcache_adjust_a(qc, 50)); + expect(qc, (uint_t[]) {10, 7}, 2, + (uint_t[]){3, 19, 18, 17, 16}, 5, __LINE__); + + /* END CSTYLED */ + + qqcache_destroy(qc); + return (0); +} + +struct cmp_arg { + qqcache_t *qc; + uint_t *vals; + size_t i; + size_t listnum; + int linenum; +}; + +static int +cmp_cb(void *op, void *arg) +{ + entry_t *e = op; + struct cmp_arg *ca = arg; + uint_t val = ca->vals[ca->i++]; + + if (e->e_val == val) + return (ITER_OK); + + (void) xprintf(stderr, "Line %d: Unexpected value in list %zu.\n", + ca->linenum, ca->listnum); + (void) xprintf(stderr, " Expected: %u\n Actual: %u\n", val, + e->e_val); + + return (ITER_ERROR); +} + +static void +cmp_list(qqcache_t *qc, size_t listnum, uint_t *vals, size_t n, int linenum) +{ + qqcache_list_t *l = &qc->qqc_lists[listnum]; + struct cmp_arg arg = { + .qc = qc, + .vals = vals, + .i = 0, + .listnum = listnum, + .linenum = linenum + }; + + if (l->qqcl_len != n) { + (void) xprintf(stderr, + "Line %d: Unexpected length for list %zu.\n" + " Length: %zu\n" + " Expected: %zu\n\n", linenum, listnum, l->qqcl_len, n); + dump_cache(qc); + } + + if (iter_list(qc, listnum, cmp_cb, &arg) != ITER_OK) { + dump_cache(qc); + exit(1); + } +} + +static void +expect(qqcache_t *qc, uint_t *l0, size_t l0sz, uint_t *l1, size_t l1sz, + int linenum) +{ + cmp_list(qc, 0, l0, l0sz, linenum); + cmp_list(qc, 1, l1, l1sz, linenum); +} + +static void +expect_val(qqcache_t *qc, const entry_t *e, uint_t val) +{ + char buf[2][64]; + if (e == NULL && val == UINT_MAX) + return; + + if (e != NULL && e->e_val == val) + return; + + if (e != NULL) + (void) snprintf(buf[0], sizeof (buf[0]), "%u", e->e_val); + else + (void) strlcpy(buf[0], "", sizeof (buf[0])); + + if (val != UINT_MAX) + (void) snprintf(buf[1], sizeof (buf[1]), "%u", val); + else + (void) strlcpy(buf[1], "", sizeof (buf[1])); + + (void) xprintf(stderr, "Unexpected value in list:\n"); + (void) xprintf(stderr, " Found: %s\n Expected: %s\n", + buf[0], buf[1]); + dump_cache(qc); + exit(1); +} + +struct dump_args { + int prefixlen; + int col; + boolean_t nl; +}; + +static int +dump_entry(void *ep, void *arg) +{ + entry_t *e = ep; + struct dump_args *da = arg; + char buf[64] = { 0 }; + int n; + + n = snprintf(buf, sizeof (buf), "%u", e->e_val); + /* buf should be large enough to hold an unsigned val */ + VERIFY3S(n, >, 0); + VERIFY3S(n, <, sizeof (buf)); + + if (da->col + n + 2 > OUTPUT_WIDTH) { + da->col = xprintf(stderr, "\n%*s", da->prefixlen, "") - 1; + da->nl = B_TRUE; + } else if (!da->nl) { + da->col += xprintf(stderr, ", "); + } + + da->col += xprintf(stderr, "%s", buf); + da->nl = B_FALSE; + + return (ITER_OK); +} + +static void +dump_cache(qqcache_t *qc) +{ + (void) xprintf(stderr, "Cache contents:\n"); + + for (size_t i = 0; i < QQCACHE_NUM_LISTS; i++) { + qqcache_list_t *l = &qc->qqc_lists[i]; + struct dump_args args = { + .nl = B_TRUE + }; + + args.col = args.prefixlen = + xprintf(stderr, "List %zu (%zu/%zu): ", i, l->qqcl_len, + qc->qqc_max[i]); + + (void) iter_list(qc, i, dump_entry, &args); + VERIFY(fputc('\n', stderr)); + } +} + +static int +iter_list(qqcache_t *qc, size_t listnum, int (*cb)(void *, void *), + void *arg) +{ + qqcache_list_t *l = &qc->qqc_lists[listnum]; + void *lp; + int ret; + + for (lp = list_head(&l->qqcl_list); lp != NULL; + lp = list_next(&l->qqcl_list, lp)) { + if ((ret = cb(link_to_obj(qc, lp), arg)) != ITER_OK) + return (ret); + } + + return (ITER_OK); +} + +/* + * A small wrapper around vfprintf(3C) so caller doesn't need to deal with + * errors or negative return values. + */ +static int +xprintf(FILE *f, const char *fmt, ...) +{ + int n; + va_list ap; + + va_start(ap, fmt); + n = vfprintf(f, fmt, ap); + va_end(ap); + + if (n < 0 || ferror(f)) + err(EXIT_FAILURE, "\nUnable to write output"); + + return (n); +} + +static entry_t * +entry_new(uint_t val) +{ + entry_t *e = calloc(1, sizeof (*e)); + + VERIFY3P(e, !=, NULL); + e->e_val = val; + return (e); +} + +static uint64_t +entry_hash(const void *p) +{ + const uint_t *vp = p; + uint64_t val = *vp; + return (val); +} + +static int +entry_cmp(const void *a, const void *b) +{ + const uint_t *l = a; + const uint_t *r = b; + return ((*l == *r) ? 0 : 1); +} + +static void +entry_dtor(void *arg) +{ + free(arg); +} + +const char * +_umem_debug_init(void) +{ + return ("default,verbose"); +} + +const char * +_umem_logging_init(void) +{ + return ("fail,contents"); +} diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files index 752fe56100f0..9f66e59cce7f 100644 --- a/usr/src/uts/common/Makefile.files +++ b/usr/src/uts/common/Makefile.files @@ -25,7 +25,7 @@ # Copyright (c) 2013 by Saso Kiselkov. All rights reserved. # Copyright 2018 Nexenta Systems, Inc. # Copyright 2016 Garrett D'Amore -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright 2016 OmniTI Computer Consulting, Inc. All rights reserved. # Copyright 2016 Hans Rosenfeld # Copyright 2020 RackTop Systems, Inc. @@ -113,6 +113,7 @@ GENUNIX_OBJS += \ blabel.o \ bootbanner.o \ brandsys.o \ + bsearch.o \ bz2blocksort.o \ bz2compress.o \ bz2decompress.o \ @@ -299,6 +300,7 @@ GENUNIX_OBJS += \ resolvepath.o \ retire_store.o \ process.o \ + qqcache.o \ rlimit.o \ rmap.o \ rw.o \ @@ -712,7 +714,7 @@ NET80211_OBJS += net80211.o net80211_proto.o net80211_input.o \ VNIC_OBJS += vnic_ctl.o vnic_dev.o OVERLAY_OBJS += overlay.o overlay_fm.o overlay_mux.o overlay_plugin.o \ - overlay_prop.o overlay_target.o + overlay_prop.o overlay_router.o overlay_target.o OVERLAY_VXLAN_OBJS += overlay_vxlan.o diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules index f038a4d91a7d..4112eea2dcff 100644 --- a/usr/src/uts/common/Makefile.rules +++ b/usr/src/uts/common/Makefile.rules @@ -23,7 +23,7 @@ # Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2016 Garrett D'Amore # Copyright 2013 Saso Kiselkov. All rights reserved. -# Copyright 2020 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright 2018 Nexenta Systems, Inc. # Copyright (c) 2017 by Delphix. All rights reserved. # Copyright 2020 Oxide Computer Company @@ -1711,6 +1711,10 @@ $(OBJS_DIR)/bz2%.o: $(COMMONBASE)/bzip2/%.c $(COMPILE.c) -o $@ -I$(COMMONBASE)/bzip2 $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(UTSBASE)/common/qqcache/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + BZ2LINT = -erroff=%all -I$(UTSBASE)/common/bzip2 $(LINTS_DIR)/bz2%.ln: $(COMMONBASE)/bzip2/%.c diff --git a/usr/src/uts/common/dtrace/sdt_subr.c b/usr/src/uts/common/dtrace/sdt_subr.c index a3ccaa6ff581..1ac19fb9bceb 100644 --- a/usr/src/uts/common/dtrace/sdt_subr.c +++ b/usr/src/uts/common/dtrace/sdt_subr.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, Joyent, Inc. All rights reserved. + * Copyright 2021 Joyent, Inc. * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ @@ -141,6 +141,7 @@ sdt_provider_t sdt_providers[] = { { "srp", "__srp_", &fc_attr }, { "sysevent", "__sysevent_", &stab_attr }, { "vnd", "__vnd_", &stab_attr }, + { "overlay", "__overlay_", &stab_attr }, { "sdt", NULL, &sdt_attr }, { NULL } }; @@ -1500,6 +1501,24 @@ sdt_argdesc_t sdt_args[] = { { "vnd", "recv", 3, 3, "vnd_str_t *", "ifinfo_t *" }, { "vnd", "recv", 4, 4, "mblk_t *", "etherinfo_t *" }, + { "overlay", "freemsg", 0, 0, "mblk_t *", "pktinfo_t *" }, + { "overlay", "freemsg", 1, 1, "const char *", "const char *" }, + { "overlay", "pullupmsg", 0, 0, "mblk_t *", "pkginfo_t *" }, + { "overlay", "pullupmsg", 1, 1, "const char *", "const char *" }, + { "overlay", "vl2-lookup-req", 0, 0, "uintptr_t", "uintptr_t" }, + { "overlay", "vl2-lookup-req", 1, 1, "uint64_t", "uint64_t" }, + { "overlay", "vl2-lookup-req", 2, 2, "uint8_t *", "uint8_t *" }, + { "overlay", "vl3-lookup-req", 0, 0, "uintptr_t", "uintptr_t" }, + { "overlay", "vl3-lookup-req", 1, 1, "uint64_t", "uint64_t" }, + { "overlay", "vl3-lookup-req", 2, 2, "struct in6_addr *", + "struct in6_addr *" }, + { "overlay", "vl2-lookup-resp", 0, 0, "uintptr_t", "uintptr_t" }, + { "overlay", "vl2-lookup-req", 1, 1, "struct in6_addr *", + "struct in6_addr *" }, + { "overlay", "vl2-lookup-resp", 2, 2, "uint16_t", "uint16_t" }, + { "overlay", "vl3-lookup-resp", 0, 0, "uintptr_t", "uintptr_t" }, + { "overlay", "vl3-lookup-resp", 1, 1, "uint8_t *", "uint8_t *" }, + { NULL } }; diff --git a/usr/src/uts/common/io/overlay/overlay.c b/usr/src/uts/common/io/overlay/overlay.c index e837b15c9383..1196b3e16df7 100644 --- a/usr/src/uts/common/io/overlay/overlay.c +++ b/usr/src/uts/common/io/overlay/overlay.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2016 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -185,46 +185,46 @@ * * The following are the supported types of properties: * - * OVERLAY_PROP_T_INT + * OVERLAY_PROP_T_INT * - * A signed integer, its length is 8 bytes, corresponding to a - * int64_t. + * A signed integer, its length is 8 bytes, corresponding to a + * int64_t. * - * OVERLAY_PROP_T_UINT + * OVERLAY_PROP_T_UINT * - * An unsigned integer, its length is 8 bytes, corresponding to a - * uint64_t. + * An unsigned integer, its length is 8 bytes, corresponding to a + * uint64_t. * - * OVERLAY_PROP_T_IP + * OVERLAY_PROP_T_IP * - * A struct in6_addr, it has a fixed size. + * A struct in6_addr, it has a fixed size. * - * OVERLAY_PROP_T_STRING + * OVERLAY_PROP_T_STRING * - * A null-terminated character string encoded in either ASCII or - * UTF-8. Note that the size of the string includes the null - * terminator. + * A null-terminated character string encoded in either ASCII or + * UTF-8. Note that the size of the string includes the null + * terminator. * * The next thing that we apply to a property is its permission. The permissions * are put together by the bitwise or of the following flags and values. * - * OVERLAY_PROP_PERM_REQ + * OVERLAY_PROP_PERM_REQ * - * This indicates a required property. A property that is required - * must be set by a consumer before the device can be created. If a - * required property has a default property, this constraint is - * loosened because the default property defines the value. + * This indicates a required property. A property that is required + * must be set by a consumer before the device can be created. If a + * required property has a default property, this constraint is + * loosened because the default property defines the value. * - * OVERLAY_PORP_PERM_READ + * OVERLAY_PORP_PERM_READ * - * This indicates that a property can be read. All properties will - * have this value set. + * This indicates that a property can be read. All properties will + * have this value set. * - * OVERLAY_PROP_PERM_WRITE + * OVERLAY_PROP_PERM_WRITE * - * This indicates that a property can be written to and thus - * updated by userland. Properties that are only intended to - * display information, will not have OVERLAY_PROP_PERM_WRITE set. + * This indicates that a property can be written to and thus + * updated by userland. Properties that are only intended to + * display information, will not have OVERLAY_PROP_PERM_WRITE set. * * In addition, a few additional values are defined as a convenience to * consumers. The first, OVERLAY_PROP_PERM_RW, is a combination of @@ -260,19 +260,19 @@ * summarized in the type overlay_point_t. Any combination of these is * supported. * - * OVERLAY_PLUGIN_D_ETHERNET + * OVERLAY_PLUGIN_D_ETHERNET * - * An Ethernet MAC address is required. + * An Ethernet MAC address is required. * - * OVERLAY_PLUGIN_D_IP + * OVERLAY_PLUGIN_D_IP * - * An IP address is required. All IP addresses used by the overlay - * system are transmitted as IPv6 addresses. IPv4 addresses can be - * represented by using IPv4-mapped IPv6 addresses. + * An IP address is required. All IP addresses used by the overlay + * system are transmitted as IPv6 addresses. IPv4 addresses can be + * represented by using IPv4-mapped IPv6 addresses. * - * OVERLAY_PLUGIN_D_PORT + * OVERLAY_PLUGIN_D_PORT * - * A TCP/UDP port is required. + * A TCP/UDP port is required. * * A kernel encapsulation plugin declares which of these that it requires, it's * a static set. On the other hand, a userland lookup plugin can be built to @@ -287,20 +287,20 @@ * determines how they interact with the broader system and how look ups are * performed. These types are: * - * OVERLAY_TARGET_POINT + * OVERLAY_TARGET_POINT * - * A point to point plugin has a single static definition for where - * to send all traffic. Every packet in the system always gets sent - * to the exact same destination which is programmed into the - * kernel when the general device is activated. + * A point to point plugin has a single static definition for where + * to send all traffic. Every packet in the system always gets sent + * to the exact same destination which is programmed into the + * kernel when the general device is activated. * - * OVERLAY_TARGET_DYNAMIC + * OVERLAY_TARGET_DYNAMIC * - * A dynamic plugin does not have a single static definition. - * Instead, for each destination, the kernel makes an asynchronous - * request to varpd to determine where the packet should be routed, - * and if a specific destination is found, then that destination is - * cached in the overlay device's target cache. + * A dynamic plugin does not have a single static definition. + * Instead, for each destination, the kernel makes an asynchronous + * request to varpd to determine where the packet should be routed, + * and if a specific destination is found, then that destination is + * cached in the overlay device's target cache. * * This distinction, while important for the general overlay device's operation, * is not important to the encapsulation plugins. They don't need to know about @@ -312,7 +312,7 @@ * always sends encapsulated packets to that address. When the target type is of * OVERLAY_TARGET_DYNAMIC, then the kernel maintains a cache of all such * destinations. These destinations are kept around in an instance of a - * reference hash that is specific to the given overlay device. Entries in the + * 2Q hash that is specific to the given overlay device. Entries in the * cache can be invalidated and replaced by varpd and its lookup plugins. * * ---------------------------------- @@ -346,59 +346,78 @@ * | void * -----+---> plugin private data * | overlay_target_t * ---+---------------------+ * | overlay_plugin_t * ---+---------+ | - * +-----------------------+ | | - * ^ | | - * +--------------------+ | | | - * | Kernel Socket | | | | - * | Multiplexor | | | | - * | overlay_mux_t | | | | - * | | | | | - * | avl_tree_t -+--+ | | - * | uint_t -+--> socket family | | - * | uint_t -+--> socket type | | - * | uint_t -+--> socket protocol | | - * | ksocket_t -+--> I/O socket | | - * | struct sockaddr * -+--> ksocket address | | - * | overlay_plugin_t --+--------+ | | - * +--------------------+ | | | - * | | | - * +-------------------------+ | | | - * | Encap Plugin |<--+-----------+ | - * | overlay_plugin_t | | - * | | | - * | char * ---+--> plugin name | - * | overlay_plugin_ops_t * -+--> plugin downcalls | - * | char ** (props) ---+--> property list | - * | uint_t ---+--> id length | - * | overlay_plugin_flags_t -+--> plugin flags | - * | overlay_plugin_dest_t --+--> destination type v - * +-------------------------+ +-------------------------+ - * | Target Cache | - * | overlay_target_t | - * | | - * cache mode <--+- overlay_target_mode_t | - * dest type <--+- overlay_plugin_dest_t | - * cache flags <--+- overlay_target_flag_t | - * varpd id <--+- uint64_t | - * outstanding varpd reqs. <--+- uint_t | - * OVERLAY_TARGET_POINT state <--+- overlay_target_point_t | - * OVERLAY_TARGET_DYNAMIC state <-+---+- overlay_target_dyn_t | - * | +-------------------------+ - * +-----------------------+ - * | - * v - * +-------------------------------+ +------------------------+ - * | Target Entry |-->| Target Entry |--> ... - * | overlay_target_entry_t | | overlay_target_entry_t | - * | | +------------------------+ - * | | - * | overlay_target_entry_flags_t -+--> Entry flags - * | uint8_t[ETHERADDRL] ---+--> Target MAC address - * | overlay_target_point_t ---+--> Target underlay address - * | mblk_t * ---+--> outstanding mblk head - * | mblk_t * ---+--> outstanding mblk tail - * | size_t ---+--> outstanding mblk size - * +-------------------------------+ + * | overlay_router_t * ---+--------------------------------------+ + * +-----------------------+ | | | + * ^ | | | + * +--------------------+ | | | | + * | Kernel Socket | | | | | + * | Multiplexor | | | | | + * | overlay_mux_t | | | | | + * | | | | | | + * | avl_tree_t -+--+ | | | + * | uint_t -+--> socket family | | | + * | uint_t -+--> socket type | | | + * | uint_t -+--> socket protocol | | | + * | ksocket_t -+--> I/O socket | | | + * | struct sockaddr * -+--> ksocket address | | | + * | overlay_plugin_t --+--------+ | | | + * +--------------------+ | | | | + * | | | | + * +-------------------------+ | | | | + * | Encap Plugin |<--+-----------+ | | + * | overlay_plugin_t | | | + * | | | | + * | char * ---+--> plugin name | | + * | overlay_plugin_ops_t * -+--> plugin downcalls | | + * | char ** (props) ---+--> property list | | + * | uint_t ---+--> id length | | + * | overlay_plugin_flags_t -+--> plugin flags | | + * | overlay_plugin_dest_t --+--> destination type | | + * +-------------------------+ | | + * | | + * v | + * +-------------------------+ | + * | Target Cache | | + * | overlay_target_t | | + * | | | + * cache mode <--+- overlay_target_mode_t | | + * dest type <--+- overlay_plugin_dest_t | | + * cache flags <--+- overlay_target_flag_t | | + * varpd id <--+- uint64_t | | + * outstanding varpd reqs. <--+- uint_t | | + * OVERLAY_TARGET_POINT state <--+- overlay_target_point_t | | + * OVERLAY_TARGET_DYNAMIC state <-+---+- overlay_target_dyn_t | | + * | +-------------------------+ | + * +-----------------------+ | + * | | + * v | + * +-------------------------------+ +------------------------+ | + * | Target Entry |-->| Target Entry |--> ... | + * | overlay_target_entry_t | | overlay_target_entry_t | | + * | | +------------------------+ | + * | | | + * | overlay_target_entry_flags_t -+--> Entry flags | + * | uint8_t[ETHERADDRL] ---+--> Target MAC address | + * | overlay_target_point_t ---+--> Target underlay address | + * | mblk_t * ---+--> outstanding mblk head | + * | mblk_t * ---+--> outstanding mblk tail | + * | size_t ---+--> outstanding mblk size | + * +-------------------------------+ | + * | + * +-------------------------------------------------------+ + * | + * v + * +------------------------+ + * | Router | + * | overlay_router_t | + * | | + * | list_t ---+--> Overlay route tables for this overlay inst + * | avl_tree_t ---+--> Local subnets (indexed by router MAC) + * | avl_tree_t ---+--> Local subnets (indexed by router IPv4 addr) + * | avl_tree_t ---+--> Local subnets (indexed by router IPv6 addr) + * | overlay_routetab_t ---+--> Default overlay route table for overlay inst + * +------------------------+ + * * * The primary entries that we care about are the overlay_dev_t, which * correspond to each overlay device that is created with dladm(1M). Globally, @@ -451,7 +470,7 @@ * cache is to cache where we should send a packet on the underlay network, * given its mac address. The target cache operates in two modes depending on * whether the lookup module was declared to OVERLAY_TARGET_POINT or - * OVERLAY_TARGET_DYANMIC. + * OVERLAY_TARGET_DYNAMIC. * * In the case where the target cache has been programmed to be * OVERLAY_TARGET_POINT, then we only maintain a single overlay_target_point_t @@ -461,12 +480,14 @@ * On the other hand, when we have an instance of OVERLAY_TARGET_DYNAMIC, things * are much more interesting and as a result, more complicated. We primarily * store lists of overlay_target_entry_t's which are stored in both an avl tree - * and a refhash_t. The primary look up path uses the refhash_t and the avl tree + * and a qqcache_t. The primary look up path uses the qqcache_t and the avl tree * is only used for a few of the target ioctls used to dump data such that we * can get a consistent iteration order for things like dladm show-overlay -t. * The key that we use for the reference hashtable is based on the mac address * in the cache and currently we just do a simple CRC32 to transform it into a - * hash. + * hash. The qqcache allows us to set a limit on the number of entries that + * are stored in the kernel, using the 2Q algorithm to age out old entries. + * The size of the cache can be adjusted dynamically using dladm. * * Each entry maintains a set of flags to indicate the current status of the * request. The flags may indicate one of three states: that current cache entry @@ -486,6 +507,84 @@ * any outstanding data to that place. For the full story on how we look that up * will be discussed in the section on the Target Cache Lifecycle. * + * To support additional overlay services such as routing between subnets, EIP, + * etc. we extend the 'layer 2' operation above. In addition to sending packets + * between hosts on the same VL2 (i.e. the apparent layer two network seen + * by instances created over an overlay instance), an optional 'router' IP + * and MAC can be created for a VL2 network. This is designed to mimic the + * appearance of a traditional layer 3 router as much as is reasonable. + * + * When an overlay is configured for dynamic target mode, an overlay_router_t + * instance is also allocated. Any local overlay subnets (i.e. the subnets + * that contain the vnic instances created on top of an overlay instance) + * participating in these additional services have an overlay_net_t instance + * that defines a router MAC, router IP (IPv4 and/or IPv6), and a vlan ID for + * the subnet. + * + * Additionally, a 'overlay route table' needs to be present. Overlay route + * tables determine the handling of 'non-local' (same subnet) packets. + * They work similar to a traditional route table in that the destination + * address (the VL3 destination) is used to lookup the longest prefix match + * destination entry(ies). The corresponding target is then the UL3 (underlay) + * address/port where the encapsulated packet should be sent. An 'any' + * target (0.0.0.0/::0) indicates the packet should be routed to the + * destination network. + * + * Routing occurs in three steps. First, the packets destination to 'non-local' + * destinations are identified. This looks very much like the traditional layer + * three routing seen with physical machines. The routing table inside the + * instance sends non-local packets to its local subnet's router IP. The usual + * ARP resolution process is used to resolve the routers MAC address (that is, + * varpd will respond with the subnet's router MAC when a query for who has + * the router's IP is send). The overlay device checks the target MAC of each + * packet against its router MACs. If a match is found, the corresponding + * overlay_net_t is used to locate the appropriate overlay routing table. If + * the overlay_net_t does not have an overlay routing table associated with it, + * the default routing table for the overlay instance is used (if none is + * defined, the packet is dropped). If the longest prefix match is the ANY + * target, a lookup is performed in the overlay_target_t for the destination + * MAC address using the destination IP address. + * + * Second, a VL3->VL2 lookup is performed. The overlay target maintains a + * qqcache_t of VL3->VL2 mappings along with the VL2->UL3 mappings (a future + * enhancement could be to utilize the VL3->VL2 map to answer ARP and NDP + * queries for known destinations, leaving varpd to handle unknown entries and + * invalidation of entries). If no matching entry is found, a VL3->VL2 query is + * performed by varpd. This VL3 query looks a lot like the varpd handling an + * ARP request, except once varpd has a response, instead of injecting an ARP + * reply packet and instructing the overlay device to drop the queued ARP + * request packet, varpd replies to the overlay device with the MAC + * corresponding to the destination VL3 IP address. As part of this, varpd + * also injects the VL2->UL3 entry prior to responding to the VL3->VL2 query + * (since it's almost certainly going to be required in the immediate future). + * With the VL3->VL2 mapping available, the VL2 destination MAC is changed + * from the router MAC to the destination VL2 MAC, and the packet is sent + * back through the target lookup/send framework to be sent to the destination. + * Non-IPv4/IPv6 packets are never routed, and non IPv4/IPv6 packets sent to + * the router MAC are dropped. + * + * Third, upon reception on the destination system, the destination VL3 address + * (IPv4/IPv6) is used to locate the corresponding overlay_net_t entry that + * contains the address. If none is found, the packet is dropped unless + * there are no overlay_net_ts defined at all for the overlay device + * (indicating that no routing or additional features are enabled for the + * overlay device). If the VL2 VNET id of the packet doesn't match the VLAN + * id from the overlay_net_t, the source VL2 MAC address is changed to the + * MAC address of the MAC router for that overlay_net_t, and the VLAN id + * in the VL2 ethernet header is changed to the VLAN id of the overlay_net_t + * before sending the packet upstack for processing. + * + * Splitting the routing handling between the source and destination allows + * observation within any instances to look like routing on a physical network. + * Packets from off-subnet appear on the (V)L2 network from the router. + * Additionally, each physical host only needs knowledge of the subnets being + * used by the instances on the local machine, and don't require knowledge + * of all of the subnets defined for the virtual network (vnet). Since both + * VL3->VL2 and VL2->UL3 mappings are contained in qqcache_ts, the number of + * entries are capped, and will age out for large networks, preventing + * large virtual networks from consuming excessive kernel memory. + * + * * ------------------------ * FMA and Degraded Devices * ------------------------ @@ -583,7 +682,7 @@ * | de:ad:be:ef:00:00 | and only exists in the target cache. * +-------------------+ * - * ~~~~ + * ~~~~ * * +---------------------+ * | Global list_t | A mblk_t comes in for an entry. We @@ -596,7 +695,7 @@ * | 42:5e:1a:10:d6:2d | | de:ad:be:ef:00:00 | * +-------------------+ +-------------------+ * - * ~~~~ + * ~~~~ * * +--------------------------+ * | /dev/overlay minor state | User land said that it would look up an @@ -610,7 +709,7 @@ * | 90:b8:d0:79:02:dd | | de:ad:be:ef:00:00 | * +-------------------+ +-------------------+ * - * ~~~~ + * ~~~~ * * +-------------------+ * | Valid Entry | varpd returned an answer with @@ -823,28 +922,56 @@ dev_info_t *overlay_dip; static kmutex_t overlay_dev_lock; static list_t overlay_dev_list; -static uint8_t overlay_macaddr[ETHERADDRL] = +uint8_t overlay_macaddr[ETHERADDRL] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +uint8_t overlay_bcast[ETHERADDRL] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + typedef enum overlay_dev_prop { OVERLAY_DEV_P_MTU = 0, OVERLAY_DEV_P_VNETID, OVERLAY_DEV_P_ENCAP, - OVERLAY_DEV_P_VARPDID + OVERLAY_DEV_P_VARPDID, + OVERLAY_DEV_P_CACHE_SIZE, + OVERLAY_DEV_P_CACHE_A, } overlay_dev_prop_t; -#define OVERLAY_DEV_NPROPS 4 static const char *overlay_dev_props[] = { "mtu", "vnetid", "encap", - "varpd/id" + "varpd/id", + "cache_size", + "cache_a", }; +#define OVERLAY_DEV_NPROPS ARRAY_SIZE(overlay_dev_props) #define OVERLAY_MTU_MIN 576 #define OVERLAY_MTU_DEF 1400 #define OVERLAY_MTU_MAX 8900 +/* + * The qqcache 'a' parameter is a percentage of the cache size -- specifically + * the percentage of the cache that holds MFU entries. We only support + * integral values of a (that should be granular enough). By default + * we bias the cache towards holding more MFU entries, though this is a + * arbitrary number, and might need to be adjusted based on real-world + * experience. + */ +#define OVERLAY_CACHE_A_MAX 100 +#define OVERLAY_CACHE_A_DEF 75 +#define OVERLAY_CACHE_A_MIN 0 + +/* + * The cache limits are also somewhat arbitrary. 1 million entries would + * result in around 200Mb of kernel memory, and a single fabric with + * 1 million entries is well beyond the expected size of any fabric. + */ +#define OVERLAY_CACHE_MIN 0 +#define OVERLAY_CACHE_MAX 10000000 +#define OVERLAY_CACHE_DEF 1024 + overlay_dev_t * overlay_hold_by_dlid(datalink_id_t id) { @@ -1044,10 +1171,51 @@ overlay_m_unicast(void *arg, const uint8_t *macaddr) return (0); } +static int +overlay_tx(overlay_dev_t *odd, overlay_net_t *ont, overlay_pkt_t *pkt, + struct sockaddr *addr, socklen_t *slenp) +{ + const uint8_t *daddr = pkt->op_mhi.mhi_daddr; + + /* + * No router instance defined for this network, so just perform VL2 + * processing. + */ + if (ont == NULL) + return (overlay_target_lookup(odd, pkt, B_FALSE, addr, slenp)); + + /* + * If we handle an ARP request or a neighbor request for our + * router IP, drop the original message. + */ + switch (OPKT_ETYPE(pkt)) { + case ETHERTYPE_ARP: + if (overlay_router_arp(odd, ont, pkt)) { + return (OVERLAY_TARGET_DROP); + } + break; + case ETHERTYPE_IPV6: + if (daddr[0] == 0x33 && daddr[1] == 0x33 && + pkt->op_l4proto == IPPROTO_ICMPV6 && + overlay_router_ndp(odd, ont, pkt)) { + return (OVERLAY_TARGET_DROP); + } + break; + } + + /* If sent to our router MAC, do routing processing */ + if (bcmp(daddr, ont->ont_mac, ETHERADDRL) == 0) + return (overlay_route(odd, ont, pkt, addr, slenp)); + + /* Otherwise, just do VL2 processing */ + return (overlay_target_lookup(odd, pkt, B_FALSE, addr, slenp)); +} + mblk_t * overlay_m_tx(void *arg, mblk_t *mp_chain) { overlay_dev_t *odd = arg; + overlay_router_t *orr = odd->odd_router; mblk_t *mp, *ep; int ret; ovep_encap_info_t einfo; @@ -1069,6 +1237,9 @@ overlay_m_tx(void *arg, mblk_t *mp_chain) einfo.ovdi_id = odd->odd_vid; mp = mp_chain; while (mp != NULL) { + const char *reason; + overlay_net_t *ont; + overlay_pkt_t pkt; socklen_t slen; struct sockaddr_storage storage; @@ -1076,11 +1247,27 @@ overlay_m_tx(void *arg, mblk_t *mp_chain) mp->b_next = NULL; ep = NULL; - ret = overlay_target_lookup(odd, mp, - (struct sockaddr *)&storage, &slen); + ret = overlay_pkt_init(&pkt, odd->odd_mh, mp, &reason); + if (ret != 0) { + OVERLAY_FREEMSG(mp, reason); + freemsg(mp); + mp = mp_chain; + continue; + } + + ont = overlay_hold_net_by_vlan(orr, + VLAN_ID(pkt.op_mhi.mhi_tci)); + + ret = overlay_tx(odd, ont, &pkt, (struct sockaddr *)&storage, + &slen); + + if (ont != NULL) + overlay_net_rele(ont); + if (ret != OVERLAY_TARGET_OK) { if (ret == OVERLAY_TARGET_DROP) - freemsg(mp); + freemsg(pkt.op_mblk); + mp = mp_chain; continue; } @@ -1088,10 +1275,12 @@ overlay_m_tx(void *arg, mblk_t *mp_chain) hdr.msg_name = &storage; hdr.msg_namelen = slen; - ret = odd->odd_plugin->ovp_ops->ovpo_encap(odd->odd_mh, mp, - &einfo, &ep); + ret = odd->odd_plugin->ovp_ops->ovpo_encap(odd->odd_mh, + pkt.op_mblk, &einfo, &ep); if (ret != 0 || ep == NULL) { - freemsg(mp); + OVERLAY_FREEMSG(pkt.op_mblk, + "overlay encapsulation failed"); + freemsg(pkt.op_mblk); goto out; } @@ -1261,6 +1450,9 @@ overlay_i_create(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) } odd->odd_vid = oicp->oic_vnetid; + odd->odd_cachesz = OVERLAY_CACHE_DEF; + odd->odd_cachea = OVERLAY_CACHE_A_DEF; + mac = mac_alloc(MAC_VERSION); if (mac == NULL) { mutex_exit(&overlay_dev_lock); @@ -1523,6 +1715,7 @@ overlay_i_delete(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) cv_destroy(&odd->odd_iowait); mutex_destroy(&odd->odd_lock); overlay_target_free(odd); + overlay_router_free(odd->odd_router); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); @@ -1616,6 +1809,7 @@ overlay_i_propinfo(void *karg, intptr_t arg, int mode, cred_t *cred, uint_t propid = UINT_MAX; overlay_ioc_propinfo_t *oip = karg; overlay_prop_handle_t phdl = (overlay_prop_handle_t)oip; + uint32_t def = 0; odd = overlay_hold_by_dlid(oip->oipi_linkid); if (odd == NULL) @@ -1696,6 +1890,22 @@ overlay_i_propinfo(void *karg, intptr_t arg, int mode, cred_t *cred, overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); overlay_prop_set_nodefault(phdl); break; + case OVERLAY_DEV_P_CACHE_SIZE: + def = OVERLAY_CACHE_DEF; + overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_RW); + overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); + overlay_prop_set_default(phdl, &def, sizeof (def)); + overlay_prop_set_range_uint32(phdl, OVERLAY_CACHE_MIN, + OVERLAY_CACHE_MAX); + break; + case OVERLAY_DEV_P_CACHE_A: + def = OVERLAY_CACHE_A_DEF; + overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_RW); + overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); + overlay_prop_set_default(phdl, &def, sizeof (def)); + overlay_prop_set_range_uint32(phdl, OVERLAY_CACHE_A_MIN, + OVERLAY_CACHE_A_MAX); + break; default: overlay_hold_rele(odd); mac_perim_exit(mph); @@ -1805,6 +2015,20 @@ overlay_i_getprop(void *karg, intptr_t arg, int mode, cred_t *cred, } mutex_exit(&odd->odd_lock); break; + case OVERLAY_DEV_P_CACHE_SIZE: + mutex_enter(&odd->odd_lock); + bcopy(&odd->odd_cachesz, oip->oip_value, + sizeof (odd->odd_cachesz)); + mutex_exit(&odd->odd_lock); + oip->oip_size = sizeof (odd->odd_cachesz); + break; + case OVERLAY_DEV_P_CACHE_A: + mutex_enter(&odd->odd_lock); + bcopy(&odd->odd_cachea, oip->oip_value, + sizeof (odd->odd_cachea)); + mutex_exit(&odd->odd_lock); + oip->oip_size = sizeof (odd->odd_cachea); + break; default: ret = ENOENT; } @@ -1846,6 +2070,83 @@ overlay_setprop_vnetid(overlay_dev_t *odd, uint64_t vnetid) mutex_exit(&odd->odd_lock); } +static int +overlay_setprop_cache_size(overlay_dev_t *odd, uint32_t size) +{ + overlay_target_t *ott; + int ret = 0; + + if (size == 0) + size = OVERLAY_CACHE_DEF; + + /* + * Since the minimum cache size is currently 0, some compilers will + * complain about a comparison of 'size < 0' for an unsigned type + * since it's always true. As a result, we only check the max size. + * If the min value is changed to a non-zero value, a check should be + * added for the minimum bound as well. + */ + if (size > OVERLAY_CACHE_MAX) + return (EINVAL); + + mutex_enter(&odd->odd_lock); + ott = odd->odd_target; + + /* + * If ott != NULL, ott->ott_mode is read only, and we don't + * need to acquire the lock to check the value of ott_mode. + */ + if (ott != NULL && ott->ott_mode == OVERLAY_TARGET_DYNAMIC) { + mutex_enter(&ott->ott_lock); + ret = qqcache_adjust_size(ott->ott_u.ott_dyn.ott_cache, size); + mutex_exit(&ott->ott_lock); + } + + if (ret == 0) + odd->odd_cachesz = size; + + mutex_exit(&odd->odd_lock); + + return (ret); +} + +static int +overlay_setprop_cache_a(overlay_dev_t *odd, uint32_t a) +{ + overlay_target_t *ott; + int ret = 0; + + /* + * Similar to the cache size, since the range of a is an integral + * percent (0-100), we omit a 'a < 0' comparison when validating + * the value (and only validate the upper bound) of a since some + * compilers will complain about the tautology. + */ + if (a > OVERLAY_CACHE_A_MAX) + return (EINVAL); + + mutex_enter(&odd->odd_lock); + ott = odd->odd_target; + + /* + * If ott != NULL, ott->ott_mode is read only, and we don't + * need to acquire the lock to check the value of ott_mode. + */ + if (ott != NULL && ott->ott_mode == OVERLAY_TARGET_DYNAMIC) { + mutex_enter(&ott->ott_lock); + ret = qqcache_adjust_a(ott->ott_u.ott_dyn.ott_cache, a); + mutex_exit(&ott->ott_lock); + } + + if (ret == 0) + odd->odd_cachea = a; + + mutex_exit(&odd->odd_lock); + + return (ret); + +} + /* ARGSUSED */ static int overlay_i_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, @@ -1856,7 +2157,7 @@ overlay_i_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, overlay_ioc_prop_t *oip = karg; uint_t propid = UINT_MAX; mac_perim_handle_t mph; - uint64_t maxid, *vidp; + uint64_t maxid, *vidp, *sizep, *ap; if (oip->oip_size > OVERLAY_PROP_SIZEMAX) return (EINVAL); @@ -1878,8 +2179,9 @@ overlay_i_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, int i; for (i = 0; i < OVERLAY_DEV_NPROPS; i++) { - if (strcmp(overlay_dev_props[i], oip->oip_name) == 0) + if (strcmp(overlay_dev_props[i], oip->oip_name) == 0) { break; + } if (i == OVERLAY_DEV_NPROPS) { ret = odd->odd_plugin->ovp_ops->ovpo_setprop( odd->odd_pvoid, oip->oip_name, @@ -1942,6 +2244,22 @@ overlay_i_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, case OVERLAY_DEV_P_VARPDID: ret = EPERM; break; + case OVERLAY_DEV_P_CACHE_SIZE: + if (oip->oip_size != sizeof (uint64_t)) { + ret = EINVAL; + break; + } + sizep = (uint64_t *)oip->oip_value; + ret = overlay_setprop_cache_size(odd, *sizep); + break; + case OVERLAY_DEV_P_CACHE_A: + if (oip->oip_size != sizeof (uint64_t)) { + ret = EINVAL; + break; + } + ap = (uint64_t *)oip->oip_value; + ret = overlay_setprop_cache_a(odd, *ap); + break; default: ret = ENOENT; } @@ -2020,11 +2338,18 @@ overlay_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) ddi_fm_init(dip, &fmcap, NULL); if (ddi_create_minor_node(dip, OVERLAY_CTL, S_IFCHR, - ddi_get_instance(dip), DDI_PSEUDO, 0) == DDI_FAILURE) + OVERLAY_TARGET_MINOR, DDI_PSEUDO, 0) == DDI_FAILURE) return (DDI_FAILURE); + if (ddi_create_minor_node(dip, OVERLAY_ROUTER_CTL, S_IFCHR, + OVERLAY_ROUTER_MINOR, DDI_PSEUDO, 0) == DDI_FAILURE) { + ddi_remove_minor_node(dip, OVERLAY_CTL); + return (DDI_FAILURE); + } + if (dld_ioc_register(OVERLAY_IOC, overlay_ioc_list, DLDIOCCNT(overlay_ioc_list)) != 0) { + ddi_remove_minor_node(dip, OVERLAY_ROUTER_CTL); ddi_remove_minor_node(dip, OVERLAY_CTL); return (DDI_FAILURE); } @@ -2071,21 +2396,76 @@ overlay_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) dld_ioc_unregister(OVERLAY_IOC); + ddi_remove_minor_node(dip, OVERLAY_ROUTER_CTL); ddi_remove_minor_node(dip, OVERLAY_CTL); ddi_fm_fini(dip); overlay_dip = NULL; return (DDI_SUCCESS); } +/* + * For minor numbers, minor 0 (OVERLAY_TARGET_MINOR) is used as a + * control device, and used to instantiate additional minor instances for + * varpd. minor 1 (OVERLAY_ROUTER_MINOR) is used to manage the + * overlay router data. It's likely we'll add additional minor numbers + * (e.g. overlay nat/flow) in the future. + */ +static int +overlay_open(dev_t *devp, int flags, int otype, cred_t *credp) +{ + /* + * XXX: It might make sense in the future to separate this + * out from PRIV_DL_CONFIG, but for now we use it. + */ + if (secpolicy_dl_config(credp) != 0) + return (EPERM); + + switch (getminor(*devp)) { + case OVERLAY_TARGET_MINOR: + return (overlay_target_open(devp, flags, otype, credp)); + case OVERLAY_ROUTER_MINOR: + return (overlay_router_open(devp, flags, otype, credp)); + default: + return (ENXIO); + } +} + +static int +overlay_close(dev_t dev, int flags, int otype, cred_t *credp) +{ + switch (getminor(dev)) { + case OVERLAY_ROUTER_MINOR: + return (overlay_router_close(dev, flags, otype, credp)); + case OVERLAY_TARGET_MINOR: + default: + return (overlay_target_close(dev, flags, otype, credp)); + } +} + +static int +overlay_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + switch (getminor(dev)) { + case OVERLAY_ROUTER_MINOR: + return (overlay_router_ioctl(dev, cmd, arg, mode, credp, + rvalp)); + case OVERLAY_TARGET_MINOR: + default: + return (overlay_target_ioctl(dev, cmd, arg, mode, credp, + rvalp)); + } +} + static struct cb_ops overlay_cbops = { - overlay_target_open, /* cb_open */ - overlay_target_close, /* cb_close */ + overlay_open, /* cb_open */ + overlay_close, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ - overlay_target_ioctl, /* cb_ioctl */ + overlay_ioctl, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ @@ -2133,6 +2513,7 @@ overlay_init(void) overlay_mux_init(); overlay_plugin_init(); overlay_target_init(); + overlay_router_init(); return (DDI_SUCCESS); } @@ -2140,6 +2521,7 @@ overlay_init(void) static void overlay_fini(void) { + overlay_router_fini(); overlay_target_fini(); overlay_plugin_fini(); overlay_mux_fini(); diff --git a/usr/src/uts/common/io/overlay/overlay_mux.c b/usr/src/uts/common/io/overlay/overlay_mux.c index 882f350060fe..bc5c46385427 100644 --- a/usr/src/uts/common/io/overlay/overlay_mux.c +++ b/usr/src/uts/common/io/overlay/overlay_mux.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2019 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -31,13 +31,12 @@ #include #include #include +#include -#include - -#include +#include +#include -#define OVERLAY_FREEMSG(mp, reason) \ - DTRACE_PROBE2(overlay__fremsg, mblk_t *, mp, char *, reason) +#include static list_t overlay_mux_list; static kmutex_t overlay_mux_lock; @@ -71,6 +70,81 @@ overlay_mux_comparator(const void *a, const void *b) return (0); } +static void +overlay_rx(mac_handle_t mh, overlay_target_t *ott, overlay_router_t *orr, + mblk_t *mp) +{ + const char *reason; + overlay_net_t *ont = NULL; + struct ether_vlan_header *eth = NULL; + overlay_pkt_t op; + uint16_t pri, cfi, vlan; + + /* + * If this vnet doesn't have any router instances, no further checking + * is necessary and we can just send this mblk_t on up. + */ + if (orr == NULL) { + mac_rx(mh, NULL, mp); + return; + } + + if (overlay_pkt_init(&op, mh, mp, &reason) != 0) { + OVERLAY_FREEMSG(mp, reason); + freemsg(mp); + return; + } + + if (!op.op_mhi.mhi_istagged) { + OVERLAY_FREEMSG(op.op_mblk, "untagged packet"); + freemsg(op.op_mblk); + return; + } + + pri = VLAN_PRI(op.op_mhi.mhi_tci); + cfi = VLAN_CFI(op.op_mhi.mhi_tci); + vlan = VLAN_ID(op.op_mhi.mhi_tci); + + switch (OPKT_ETYPE(&op)) { + case ETHERTYPE_IP: + ont = overlay_hold_net_by_ip(orr, + op.op3_u.op3_ipv4->ipha_dst); + break; + case ETHERTYPE_IPV6: + ont = overlay_hold_net_by_ip6(orr, &op.op_dstaddr); + break; + default: + /* + * Non IPv4/IPv6 packets are not routed, so we can always + * pass those up. + */ + goto done; + } + + if (ont == NULL) { + OVERLAY_FREEMSG(op.op_mblk, "no destination net found"); + freemsg(op.op_mblk); + return; + } + + /* If the vlans match, no routing done, just pass up */ + if (ont->ont_vlan == vlan) + goto done; + + /* + * Set the source MAC to the router MAC and adjust the vlan to + * the net vlan. The destination MAC should have already been set + * by the source host. + */ + eth = (struct ether_vlan_header *)op.op_mblk->b_rptr; + bcopy(ont->ont_mac, ð->ether_shost, ETHERADDRL); + eth->ether_tci = htons(VLAN_TCI(pri, cfi, ont->ont_vlan)); + +done: + mac_rx(mh, NULL, op.op_mblk); + overlay_net_rele(ont); +} + /* * This is the central receive data path. We need to decode the packet, if we * can, and then deliver it to the appropriate overlay. @@ -93,6 +167,7 @@ overlay_mux_recv(ksocket_t ks, mblk_t *mpchain, size_t msgsize, int oob, struct T_unitdata_ind *tudi; ovep_encap_info_t infop; overlay_dev_t od, *odd; + overlay_router_t *orr; int ret; nmp = mp->b_next; @@ -126,6 +201,18 @@ overlay_mux_recv(ksocket_t ks, mblk_t *mpchain, size_t msgsize, int oob, mp = fmp->b_cont; freeb(fmp); + /* + * In cases of looped-back vxlan, that tends to have a + * prepended IP+UDP-only mblk, followed by the data. Parsing + * would've made that mblk a zero-length one (rptr == wptr). + */ + if (mp->b_rptr == mp->b_wptr && mp->b_cont != NULL) { + /* Ended up with zero-length mblk, lose it! */ + fmp = mp; + mp = fmp->b_cont; + freeb(fmp); + } + /* * Until we have VXLAN-or-other-decap HW acceleration support * (e.g. we support NICs that reach into VXLAN-encapsulated @@ -192,11 +279,23 @@ overlay_mux_recv(ksocket_t ks, mblk_t *mpchain, size_t msgsize, int oob, freemsg(mp); continue; } + + /* + * If the router isn't active, set orr to NULL so to allow + * overlay_rx() to bypass the routing checks. + */ + mutex_enter(&odd->odd_router->orr_lock); + if (overlay_router_active(odd->odd_router)) + orr = odd->odd_router; + else + orr = NULL; + mutex_exit(&odd->odd_router->orr_lock); + overlay_io_start(odd, OVERLAY_F_IN_RX); mutex_exit(&odd->odd_lock); mutex_exit(&mux->omux_lock); - mac_rx(odd->odd_mh, NULL, mp); + overlay_rx(odd->odd_mh, odd->odd_target, orr, mp); mutex_enter(&odd->odd_lock); overlay_io_done(odd, OVERLAY_F_IN_RX); @@ -364,8 +463,10 @@ overlay_mux_tx(overlay_mux_t *mux, struct msghdr *hdr, mblk_t *mp) * b_cont of mp post-call). We can't hold up this message (it's a * datagram), so we drop, and let the caller cope. */ - if (ret != 0) + if (ret != 0) { + OVERLAY_FREEMSG(mp, "ksocket_sendmblk failed"); freemsg(mp); + } return (ret); } diff --git a/usr/src/uts/common/io/overlay/overlay_router.c b/usr/src/uts/common/io/overlay/overlay_router.c new file mode 100644 index 000000000000..d4e3ee79a699 --- /dev/null +++ b/usr/src/uts/common/io/overlay/overlay_router.c @@ -0,0 +1,2389 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +/* + * This handles the layer 3 routing aspect of an overlay device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef enum overlay_router_ioctl_flags { + OFF_NONE = 0, /* no flags */ + OFF_RW = (1 << 0), /* RW access required */ + OFF_COPYOUT = (1 << 2) /* Perform copyout on success */ +} overlay_router_ioctl_flags_t; + +typedef int (*overlay_router_copyin_f)(const void *, void **, size_t *, int); +typedef int (*overlay_router_ioctl_f)(overlay_router_t *, void *); + +typedef struct overlay_router_ioctl { + int ori_cmd; + overlay_router_ioctl_flags_t ori_flags; + overlay_router_copyin_f ori_copyin; + overlay_router_ioctl_f ori_func; + size_t ori_size; +} overlay_router_ioctl_t; + +/* These exist in the kernel, but don't seem to have header files for them */ +extern void qsort(void *, size_t, size_t, int (*)(const void *, const void *)); +extern void *bsearch(const void *, const void *, size_t, size_t, + int (*)(const void *, const void *)); + +extern uint8_t overlay_macaddr[ETHERADDRL]; + +static kmem_cache_t *overlay_router_cache; +static kmem_cache_t *overlay_net_cache; +static kmem_cache_t *overlay_rtab_cache; + +static int overlay_net_cache_ctor(void *, void *, int); + +static inline void +net_addr(in_addr_t *dest, const in_addr_t *src, uint8_t prefixlen) +{ + const in_addr_t mask = htonl(((in_addr_t)1 << (32 - prefixlen)) - 1); + *dest = *src & ~mask; +} + +static inline boolean_t +net_addr_prefixequal(const in_addr_t *a1, const in_addr_t *a2, + uint8_t prefixlen) +{ + in_addr_t cmp1, cmp2; + + net_addr(&cmp1, a1, prefixlen); + net_addr(&cmp2, a2, prefixlen); + return ((cmp1 == cmp2) ? B_TRUE : B_FALSE); +} + +static inline void +net_addr6(struct in6_addr *dest, const struct in6_addr *src, uint8_t prefixlen) +{ + struct in6_addr maskv6; + + /* There's probably a better way to do this, but for now... */ + maskv6._S6_un._S6_u32[0] = IN6_MASK_FROM_PREFIX(0, prefixlen); + maskv6._S6_un._S6_u32[1] = IN6_MASK_FROM_PREFIX(1, prefixlen); + maskv6._S6_un._S6_u32[2] = IN6_MASK_FROM_PREFIX(2, prefixlen); + maskv6._S6_un._S6_u32[3] = IN6_MASK_FROM_PREFIX(3, prefixlen); + + for (uint_t i = 0; i < 4; i++) { + uint32_t sval = ntohl(src->_S6_un._S6_u32[i]); + uint32_t mask = maskv6._S6_un._S6_u32[i]; + + dest->_S6_un._S6_u32[i] = htonl(sval & mask); + } +} + +/* + * A wrapper around the macro for both better error checking, and for + * consistency with net_addr_prefixequal. + */ +static inline boolean_t +net_addr6_prefixequal(const struct in6_addr *a1, const struct in6_addr *a2, + uint8_t prefixlen) +{ + return (IN6_ARE_PREFIXEDADDR_EQUAL(a1, a2, prefixlen) ? + B_TRUE : B_FALSE); +} + +static boolean_t +overlay_valid_id(const char *str, size_t len) +{ + /* + * IDs are in fixed sized buffers. It should fit in the given length + * and be NUL-terminated. + */ + if (strnlen(str, len) == len && str[len - 1] != '\0') + return (B_FALSE); + + /* Require the first character to be alphanumeric */ + if (!ISALNUM(*str)) + return (B_FALSE); + str++; + + /* The remaining characters can be alphanumeric or include '-' or '.' */ + while (*str != '\0') { + if (!ISALNUM(*str) && *str != '-' && *str != '.' && *str != '#') + return (B_FALSE); + str++; + } + return (B_TRUE); +} + +static int +overlay_route_ent_cmp(const void *a, const void *b) +{ + const overlay_route_ent_t *l = a; + const overlay_route_ent_t *r = b; + int ret; + + /* + * Sort in reverse order -- 'largest' to 'smallest' (at least when + * treating the destination as a 128-bit integer). This should put + * more specific matches first when sorting. + */ + ret = memcmp(&l->ore_dest, &r->ore_dest, sizeof (l->ore_dest)); + if (ret < 0) + return (1); + if (ret > 0) + return (-1); + + /* + * If we have a tie, look at the prefix length and sort in order + * of longest prefix to shortest. + */ + if (l->ore_prefixlen < r->ore_prefixlen) + return (1); + if (l->ore_prefixlen > r->ore_prefixlen) + return (-1); + + /* + * Finally sort on target address. While we really don't care about + * ordering from largest to smallest here (when there are multiple + * targets for the longest prefix match, we'll typically hash on all + * of the targets), we define an order similar to above just to + * have one -- that way we can still easily locate _exact_ entries + * for removal. + */ + ret = memcmp(&l->ore_target, &r->ore_target, sizeof (l->ore_target)); + if (ret < 0) + return (1); + if (ret > 0) + return (-1); + return (0); +} + +static overlay_routetab_t * +overlay_route_tbl_hold_by_id(overlay_router_t *orr, const char *id) +{ + overlay_routetab_t *rtab; + list_t *rlist = &orr->orr_routetbls; + + mutex_enter(&orr->orr_lock); + for (rtab = list_head(rlist); rtab != NULL; + rtab = list_next(rlist, rtab)) { + if (strcmp(rtab->ort_id, id) == 0) { + mutex_enter(&rtab->ort_lock); + rtab->ort_refcnt++; + mutex_exit(&rtab->ort_lock); + mutex_exit(&orr->orr_lock); + return (rtab); + } + } + mutex_exit(&orr->orr_lock); + return (NULL); +} + +static void +overlay_route_tbl_rele(overlay_routetab_t *rtab) +{ + uint_t cnt; + + mutex_enter(&rtab->ort_lock); + ASSERT3U(rtab->ort_refcnt, >, 0); + cnt = --rtab->ort_refcnt; + mutex_exit(&rtab->ort_lock); + + if (cnt != 0) + return; + + VERIFY(!list_link_active(&rtab->ort_link)); + + if (rtab->ort_routev4 != NULL) { + kmem_free(rtab->ort_routev4, + rtab->ort_routev4_alloc * sizeof (overlay_route_ent_t)); + rtab->ort_routev4 = NULL; + } + + if (rtab->ort_routev6 != NULL) { + kmem_free(rtab->ort_routev6, + rtab->ort_routev6_alloc * sizeof (overlay_route_ent_t)); + rtab->ort_routev6 = NULL; + } + + kmem_cache_free(overlay_rtab_cache, rtab); +} + +static overlay_net_t * +overlay_hold_net_by_id(overlay_router_t *orr, const char *id) +{ + overlay_net_t *ont; + + mutex_enter(&orr->orr_lock); + /* + * Since every net must have a valid MAC address, we just + * search the mac AVL tree. We can always do something different + * in the future if this turns out to be too slow. + */ + for (ont = avl_first(&orr->orr_nets_mac); ont != NULL; + ont = AVL_NEXT(&orr->orr_nets_mac, ont)) { + if (strcmp(ont->ont_id, id) == 0) { + mutex_enter(&ont->ont_lock); + ont->ont_refcnt++; + mutex_exit(&ont->ont_lock); + mutex_exit(&orr->orr_lock); + return (ont); + } + } + mutex_exit(&orr->orr_lock); + return (NULL); +} + +/* vlan is in host byte order */ +overlay_net_t * +overlay_hold_net_by_vlan(overlay_router_t *orr, uint16_t vlan) +{ + overlay_net_t *ont = NULL; + overlay_net_t ref = { + .ont_vlan = vlan + }; + + mutex_enter(&orr->orr_lock); + ont = avl_find(&orr->orr_nets_vlan, &ref, NULL); + if (ont != NULL) { + mutex_enter(&ont->ont_lock); + ont->ont_refcnt++; + mutex_exit(&ont->ont_lock); + } + mutex_exit(&orr->orr_lock); + return (ont); +} + +overlay_net_t * +overlay_hold_net_by_mac(overlay_router_t *orr, const uint8_t mac[ETHERADDRL]) +{ + overlay_net_t *ont = NULL; + overlay_net_t ref = { 0 }; + + bcopy(mac, ref.ont_mac, ETHERADDRL); + + mutex_enter(&orr->orr_lock); + ont = avl_find(&orr->orr_nets_mac, &ref, NULL); + if (ont != NULL) { + mutex_enter(&ont->ont_lock); + ont->ont_refcnt++; + mutex_exit(&ont->ont_lock); + } + mutex_exit(&orr->orr_lock); + return (ont); +} + +/* + * Find and hold the overlay_net_t whose subnet contains addr, and return the + * overlay_net_t, or NULL if not found. + */ +overlay_net_t * +overlay_hold_net_by_ip(overlay_router_t *orr, in_addr_t addr) +{ + overlay_net_t *ont; + avl_index_t where; + overlay_net_t ref = { + .ont_net = addr + }; + + mutex_enter(&orr->orr_lock); + + /* + * The nets_v4 avl tree orders the entries by the network address, + * e.g. 10.0.1.0, 172.16.5.0, 192.168.1.0, 200.200.0.0, ... + * + * We use the fact that the network address is always the first + * (and thus lowest numerically) address in a subnet. When we search + * for the address, it should normally fail (unless it's an actual + * network address we have), however if the network exists, the + * AVL_BEFORE entry should be the network address that contains + * 'addr'. We verify and return, otherwise the network isn't present. + */ + ont = avl_find(&orr->orr_nets_v4, &ref, &where); + if (ont != NULL) + goto out; + + ont = avl_nearest(&orr->orr_nets_v4, where, AVL_BEFORE); + if (ont == NULL) { + mutex_exit(&orr->orr_lock); + return (NULL); + } + + net_addr(&addr, &addr, ont->ont_prefixlen); + if (addr != ont->ont_net) { + mutex_exit(&orr->orr_lock); + return (NULL); + } + +out: + mutex_enter(&ont->ont_lock); + ont->ont_refcnt++; + mutex_exit(&ont->ont_lock); + mutex_exit(&orr->orr_lock); + return (ont); +} + +/* Similar to overlay_net_hold_by_net() except for an IPv6 addr. */ +overlay_net_t * +overlay_hold_net_by_ip6(overlay_router_t *orr, const struct in6_addr *addr) +{ + overlay_net_t *ont; + avl_index_t where; + overlay_net_t ref = { 0 }; + + bcopy(addr, &ref.ont_netv6, sizeof (ref.ont_netv6)); + + mutex_enter(&orr->orr_lock); + + /* + * We use a similar strategy as in overlay_net_hold_by_net() to + * locate the net that contains 'addr'. + */ + ont = avl_find(&orr->orr_nets_v6, &ref, &where); + if (ont != NULL) + goto out; + + ont = avl_nearest(&orr->orr_nets_v6, where, AVL_BEFORE); + if (ont == NULL) { + mutex_exit(&orr->orr_lock); + return (NULL); + } + + if (!net_addr6_prefixequal(addr, &ont->ont_netv6, + ont->ont_prefixlenv6)) { + mutex_exit(&orr->orr_lock); + return (NULL); + } + +out: + mutex_enter(&ont->ont_lock); + ont->ont_refcnt++; + mutex_exit(&ont->ont_lock); + mutex_exit(&orr->orr_lock); + return (ont); +} + +void +overlay_net_rele(overlay_net_t *ont) +{ + uint_t cnt; + + mutex_enter(&ont->ont_lock); + ASSERT3U(ont->ont_refcnt, >, 0); + cnt = --ont->ont_refcnt; + mutex_exit(&ont->ont_lock); + + if (cnt != 0) + return; + + if (ont->ont_routetbl != NULL) { + overlay_route_tbl_rele(ont->ont_routetbl); + ont->ont_routetbl = NULL; + } + + /* + * It's simpler to just destroy/re-init the mutex so that what + * we return to the kmem cache looks exactly like an initialized + * overlay_net_t + */ + mutex_destroy(&ont->ont_lock); + (void) overlay_net_cache_ctor(ont, NULL, 0); + + kmem_cache_free(overlay_net_cache, ont); +} + +static int +overlay_router_hold_by_dlid(datalink_id_t id, overlay_router_t **orrp) +{ + overlay_dev_t *odd; + overlay_router_t *orr; + + odd = overlay_hold_by_dlid(id); + if (odd == NULL) + return (ENOENT); + + mutex_enter(&odd->odd_lock); + if (!(odd->odd_flags & OVERLAY_F_VARPD)) { + mutex_exit(&odd->odd_lock); + overlay_hold_rele(odd); + return (ENXIO); + } + orr = odd->odd_router; + + mutex_enter(&orr->orr_lock); + mutex_exit(&odd->odd_lock); + + orr->orr_refcnt++; + mutex_exit(&orr->orr_lock); + + *orrp = orr; + return (0); +} + +static void +overlay_router_rele(overlay_router_t *orr) +{ + mutex_enter(&orr->orr_lock); + orr->orr_refcnt--; + mutex_exit(&orr->orr_lock); +} + +overlay_router_t * +overlay_router_create(overlay_dev_t *odd __unused) +{ + overlay_router_t *orr; + + orr = kmem_cache_alloc(overlay_router_cache, KM_SLEEP); + return (orr); +} + +void +overlay_router_free(overlay_router_t *orr) +{ + if (orr == NULL) + return; + + kmem_cache_free(overlay_router_cache, orr); +} + +boolean_t +overlay_router_active(overlay_router_t *orr) +{ + ASSERT(MUTEX_HELD(&orr->orr_lock)); + return ((avl_numnodes(&orr->orr_nets_mac) > 0) ? B_TRUE : B_FALSE); +} + +static int +overlay_router_net_create(overlay_router_t *orr, void *buf) +{ + overlay_ioc_net_t *ioc_net = buf; + overlay_net_t *net = NULL; + overlay_routetab_t *rtab = NULL; + boolean_t hasv4 = B_FALSE; + boolean_t hasv6 = B_FALSE; + + /* + * Validate the addresses -- we need at least one of: + * (IPv4 network address, IPv4 router address, IPv4 prefix length) or + * (IPv6 network address, IPv6 router addresss, IPv6 prefix length) + */ + if (ioc_net->oin_routeraddr != INADDR_ANY) { + if (ioc_net->oin_prefixlen > 32) + return (EINVAL); + hasv4 = B_TRUE; + } + if (!IN6_IS_ADDR_UNSPECIFIED(&ioc_net->oin_routeraddrv6)) { + if (ioc_net->oin_prefixlenv6 > 128) + return (EINVAL); + hasv6 = B_TRUE; + } + if (!hasv4 && !hasv6) + return (EINVAL); + + /* Validate vlan id */ + if (ioc_net->oin_vlan < VLAN_ID_MIN || ioc_net->oin_vlan > VLAN_ID_MAX) + return (EINVAL); + + /* MAC address can't be all zeros */ + if (bcmp(ioc_net->oin_mac, overlay_macaddr, ETHERADDRL) == 0) + return (EINVAL); + + if (!overlay_valid_id(ioc_net->oin_id, sizeof (ioc_net->oin_id))) + return (EINVAL); + + if (overlay_valid_id(ioc_net->oin_routetbl, + sizeof (ioc_net->oin_routetbl))) { + rtab = overlay_route_tbl_hold_by_id(orr, ioc_net->oin_routetbl); + if (rtab == NULL) + return (EINVAL); + } else if (ioc_net->oin_routetbl[0] != '\0') { + /* + * The route table ID was not empty, but invalid. We + * allow an empty route table ID to indicate the default + * route table should be used (if it exists). + */ + return (EINVAL); + } + + net = kmem_cache_alloc(overlay_net_cache, KM_SLEEP); + + /* + * Set the initial refhold. We use this so that we can + * just use overlay_net_rele() and have things work correctly if we + * fail at some point. + */ + net->ont_refcnt++; + + (void) strlcpy(net->ont_id, ioc_net->oin_id, sizeof (net->ont_id)); + (void) bcopy(ioc_net->oin_mac, net->ont_mac, ETHERADDRL); + net->ont_routetbl = rtab; + + /* + * Since we use mac_vlan_header_info() for any routed packets, we + * have a copy of the VLAN id in host byte order. Keep it that way + * to make debugging simpler. + */ + net->ont_vlan = ioc_net->oin_vlan; + + net->ont_routeraddr = ioc_net->oin_routeraddr; + net->ont_prefixlen = ioc_net->oin_prefixlen; + net_addr(&net->ont_net, &net->ont_routeraddr, net->ont_prefixlen); + + bcopy(&ioc_net->oin_routeraddrv6, &net->ont_routeraddrv6, + sizeof (net->ont_routeraddrv6)); + net->ont_prefixlenv6 = ioc_net->oin_prefixlenv6; + net_addr6(&net->ont_netv6, &net->ont_routeraddrv6, + net->ont_prefixlenv6); + + mutex_enter(&orr->orr_lock); + mutex_enter(&net->ont_lock); + + if (avl_find(&orr->orr_nets_vlan, net, NULL) != NULL || + avl_find(&orr->orr_nets_mac, net, NULL) != NULL || + (hasv4 && avl_find(&orr->orr_nets_v4, net, NULL) != NULL) || + (hasv6 && avl_find(&orr->orr_nets_v6, net, NULL) != NULL)) { + mutex_exit(&net->ont_lock); + mutex_exit(&orr->orr_lock); + + overlay_net_rele(net); + return (EEXIST); + } + + avl_add(&orr->orr_nets_vlan, net); + net->ont_refcnt++; + + avl_add(&orr->orr_nets_mac, net); + net->ont_refcnt++; + + if (hasv4) { + avl_add(&orr->orr_nets_v4, net); + net->ont_refcnt++; + } + + if (hasv6) { + avl_add(&orr->orr_nets_v6, net); + net->ont_refcnt++; + } + + mutex_exit(&net->ont_lock); + mutex_exit(&orr->orr_lock); + + /* Release the initial hold we took immediately after allocation. */ + overlay_net_rele(net); + return (0); +} + +static int +overlay_router_net_delete(overlay_router_t *orr, void *buf) +{ + overlay_ioc_net_t *rnet = buf; + overlay_net_t *net = NULL; + + /* + * Require all other fields aside from the overlay id and the + * router id to be zero. We want to prevent a scenario where + * current clients pass in garbage for currently unused fields + * (and have it work) -- if other fields are used in the future this + * could be a source of interesting bugs. + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&rnet->oin_routeraddrv6) || + rnet->oin_routeraddr != INADDR_ANY || + rnet->oin_prefixlen != 0 || + rnet->oin_prefixlenv6 != 0 || + rnet->oin_vlan != 0 || + bcmp(rnet->oin_mac, overlay_macaddr, ETHERADDRL) != 0 || + strlen(rnet->oin_routetbl) != 0) + return (EINVAL); + + if (!overlay_valid_id(rnet->oin_id, sizeof (rnet->oin_id))) + return (EINVAL); + + if (strlen(rnet->oin_id) == 0) + return (EINVAL); + + net = overlay_hold_net_by_id(orr, rnet->oin_id); + if (net == NULL) + return (ENOENT); + + mutex_enter(&orr->orr_lock); + mutex_enter(&net->ont_lock); + + avl_remove(&orr->orr_nets_vlan, net); + net->ont_refcnt--; + + avl_remove(&orr->orr_nets_mac, net); + net->ont_refcnt--; + + if (net->ont_routeraddr != INADDR_ANY) { + avl_remove(&orr->orr_nets_v4, net); + net->ont_refcnt--; + } + + if (!IN6_IS_ADDR_UNSPECIFIED(&net->ont_routeraddrv6)) { + avl_remove(&orr->orr_nets_v6, net); + net->ont_refcnt--; + } + mutex_exit(&orr->orr_lock); + mutex_exit(&net->ont_lock); + + overlay_net_rele(net); + return (0); +} + +static int +overlay_router_net_delete_all(overlay_router_t *orr, void *buf) +{ + overlay_ioc_net_t *rnet = buf; + + /* + * Similar to overlay_router_delete(), we enforce that only + * the overlay id is set, and all other fields are 0. + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&rnet->oin_routeraddrv6) || + rnet->oin_routeraddr != INADDR_ANY || + rnet->oin_prefixlen != 0 || + rnet->oin_prefixlenv6 != 0 || + rnet->oin_vlan != 0 || + bcmp(rnet->oin_mac, overlay_macaddr, ETHERADDRL) != 0 || + strlen(rnet->oin_id) != 0 || + strlen(rnet->oin_routetbl) != 0) + return (EINVAL); + + /* TODO */ + + return (0); +} + +static int +overlay_router_net_get(overlay_router_t *orr, void *buf) +{ + overlay_ioc_net_t *ioc_net = buf; + overlay_net_t *net = NULL; + + /* + * Require all other fields aside from the overlay id and the + * router id to be zero. We want to prevent a scenario where + * current clients pass in garbage for currently unused fields + * (and have it work) -- if other fields are used in the future this + * could be a source of interesting bugs. + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&ioc_net->oin_routeraddrv6) || + ioc_net->oin_routeraddr != INADDR_ANY || + ioc_net->oin_prefixlen != 0 || + ioc_net->oin_prefixlenv6 != 0 || + ioc_net->oin_vlan != 0 || + bcmp(ioc_net->oin_mac, overlay_macaddr, ETHERADDRL) != 0 || + strlen(ioc_net->oin_routetbl) != 0) + return (EINVAL); + + if (!overlay_valid_id(ioc_net->oin_id, sizeof (ioc_net->oin_id))) + return (EINVAL); + + if (strlen(ioc_net->oin_id) == 0) + return (EINVAL); + + net = overlay_hold_net_by_id(orr, ioc_net->oin_id); + if (net == NULL) + return (ENOENT); + + mutex_enter(&orr->orr_lock); + mutex_enter(&net->ont_lock); + + if (net->ont_routetbl != NULL) { + (void) strlcpy(ioc_net->oin_routetbl, net->ont_routetbl->ort_id, + sizeof (ioc_net->oin_routetbl)); + } + + mutex_exit(&net->ont_lock); + mutex_exit(&orr->orr_lock); + + bcopy(net->ont_mac, ioc_net->oin_mac, ETHERADDRL); + ioc_net->oin_vlan = net->ont_vlan; + + ioc_net->oin_routeraddr = net->ont_routeraddr; + ioc_net->oin_prefixlen = net->ont_prefixlen; + + bcopy(&net->ont_routeraddrv6, &ioc_net->oin_routeraddrv6, + sizeof (ioc_net->oin_routeraddrv6)); + ioc_net->oin_prefixlenv6 = net->ont_prefixlenv6; + + overlay_net_rele(net); + return (0); +} + +static int +overlay_router_iter_copyin(const void *ubuf, void **outp, size_t *bsize, + int flags) +{ + overlay_ioc_net_iter_t base, *iter; + + if (ddi_copyin(ubuf, &base, sizeof (base), flags & FKIOCTL) != 0) + return (EFAULT); + + if (base.oini_count == 0) + return (EINVAL); + + *bsize = sizeof (base) + + base.oini_count * sizeof (overlay_ioc_net_t); + iter = kmem_zalloc(*bsize, KM_SLEEP); + bcopy(&base, iter, sizeof (base)); + + *outp = iter; + return (0); +} + +typedef struct overlay_router_marker { + uint8_t orm_mac[ETHERADDRL]; + uint16_t orm_done; +} overlay_router_marker_t; +CTASSERT(sizeof (overlay_router_marker_t) == sizeof (uint64_t)); + +static int +overlay_router_net_iter(overlay_router_t *orr, void *buf) +{ + overlay_ioc_net_iter_t *iter = buf; + overlay_net_t lookup, *ent; + overlay_router_marker_t *mark; + avl_index_t where; + avl_tree_t *avl; + uint16_t written = 0; + + mark = (void *)&iter->oini_marker; + + if (mark->orm_done != 0) { + iter->oini_count = 0; + return (0); + } + + avl = &orr->orr_nets_mac; + + mutex_enter(&orr->orr_lock); + + bcopy(mark->orm_mac, lookup.ont_mac, ETHERADDRL); + ent = avl_find(avl, &lookup, &where); + + if (ent == NULL) { + ent = avl_nearest(avl, where, AVL_AFTER); + if (ent == NULL) { + mark->orm_done = 1; + goto done; + } + } + + for (; ent != NULL && written < iter->oini_count; + ent = AVL_NEXT(avl, ent)) { + overlay_ioc_net_t *rnet = &iter->oini_ents[written++]; + + bzero(rnet, sizeof (*rnet)); + + mutex_enter(&ent->ont_lock); + ent->ont_refcnt++; + + /* + * This is the only RW field in ent, since we take a ref, + * this is the only field that needs to be copied while + * holding ont_lock. + */ + if (ent->ont_routetbl != NULL) { + (void) strlcpy(rnet->oin_routetbl, + ent->ont_routetbl->ort_id, + sizeof (rnet->oin_routetbl)); + } + mutex_exit(&ent->ont_lock); + + bcopy(&ent->ont_routeraddrv6, &rnet->oin_routeraddrv6, + sizeof (rnet->oin_routeraddrv6)); + bcopy(ent->ont_mac, rnet->oin_mac, ETHERADDRL); + (void) strlcpy(rnet->oin_id, ent->ont_id, + sizeof (rnet->oin_id)); + rnet->oin_routeraddr = ent->ont_routeraddr; + rnet->oin_prefixlen = ent->ont_prefixlen; + rnet->oin_prefixlenv6 = ent->ont_prefixlenv6; + rnet->oin_vlan = ent->ont_vlan; + + bcopy(ent->ont_mac, mark->orm_mac, ETHERADDRL); + + mutex_enter(&ent->ont_lock); + ent->ont_refcnt--; + mutex_exit(&ent->ont_lock); + } + + if (ent == NULL) { + mark->orm_done = 1; + } + +done: + iter->oini_count = written; + mutex_exit(&orr->orr_lock); + return (0); +} + +static int +overlay_router_net_set_routetbl(overlay_router_t *orr, void *buf) +{ + overlay_ioc_net_t *rnet = buf; + overlay_net_t *net; + overlay_routetab_t *rtab = NULL; + + /* + * Make sure only the overlay id, net id, and route table id are + * specified. + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&rnet->oin_routeraddrv6) || + rnet->oin_routeraddr != INADDR_ANY || + rnet->oin_prefixlen != 0 || + rnet->oin_prefixlenv6 != 0 || + rnet->oin_vlan != 0 || + bcmp(rnet->oin_mac, overlay_macaddr, ETHERADDRL) != 0) + return (EINVAL); + + if (!overlay_valid_id(rnet->oin_id, sizeof (rnet->oin_id)) || + !overlay_valid_id(rnet->oin_routetbl, sizeof (rnet->oin_routetbl))) + return (EINVAL); + + net = overlay_hold_net_by_id(orr, rnet->oin_id); + if (net == NULL) + return (ENOENT); + + if (strlen(rnet->oin_routetbl) > 0) { + rtab = overlay_route_tbl_hold_by_id(orr, rnet->oin_routetbl); + if (rtab == NULL) { + overlay_net_rele(net); + return (ENOENT); + } + } + + mutex_enter(&net->ont_lock); + + /* + * We have a hold from overlay_route_tbl_hold_by_id(), we don't need + * it after this, so give the ref to net->ont_routetbl + */ + if (net->ont_routetbl != NULL) + overlay_route_tbl_rele(net->ont_routetbl); + net->ont_routetbl = rtab; + mutex_exit(&net->ont_lock); + + overlay_net_rele(net); + return (0); +} + +static int +overlay_router_tbl_copyin(const void *buf, void **outp, size_t *bsize, + int flags) +{ + overlay_ioc_routetab_t base, *ioc_rtab; + + if (ddi_copyin(buf, &base, sizeof (base), flags & FKIOCTL) != 0) + return (EFAULT); + + if (!overlay_valid_id(base.oir_id, sizeof (base.oir_id))) + return (EINVAL); + + *bsize = sizeof (base) + + base.oir_count * sizeof (overlay_route_ent_t); + ioc_rtab = kmem_zalloc(*bsize, KM_SLEEP); + + if (base.oir_count == 0) { + bcopy(&base, ioc_rtab, sizeof (base)); + goto done; + } + + if (ddi_copyin(buf, ioc_rtab, *bsize, flags & FKIOCTL) != 0) { + kmem_free(ioc_rtab, *bsize); + return (EFAULT); + } + + if (ioc_rtab->oir_count != base.oir_count) { + kmem_free(ioc_rtab, *bsize); + return (EFAULT); + } + +done: + *outp = ioc_rtab; + return (0); +} + +/* + * The marker is an opaque value to userland, and is initialized to 0 + * upon start of an iteration request. + */ +#define OROUTE_ENT_MARKER_F_DONE 0x01 +#define OROUTE_ENT_MARKER_F_V6 0x02 +#define OROUTE_ENT_MARKER_F_ALL \ + (OROUTE_ENT_MARKER_F_DONE|OROUTE_ENT_MARKER_F_V6) + +typedef struct overlay_route_ent_marker { + uint32_t orm_index; + uint32_t orm_flags; +} overlay_route_ent_marker_t; +CTASSERT(sizeof (overlay_route_ent_marker_t) == sizeof (uint64_t)); + +static int +overlay_route_tbl_get(overlay_router_t *orr, void *buf) +{ + overlay_ioc_routetab_t *ioc_rtab = buf; + overlay_routetab_t *rtab; + overlay_route_ent_marker_t *mark; + overlay_route_ent_t *src, *dst; + uint_t len; + int ret = 0; + uint16_t written = 0; + + mark = (void *)&ioc_rtab->oir_marker; + + if ((mark->orm_flags | OROUTE_ENT_MARKER_F_ALL) != + OROUTE_ENT_MARKER_F_ALL) { + ioc_rtab->oir_count = 0; + return (EINVAL); + } + + if ((mark->orm_flags & OROUTE_ENT_MARKER_F_DONE) != 0) { + ioc_rtab->oir_count = 0; + return (0); + } + + rtab = overlay_route_tbl_hold_by_id(orr, ioc_rtab->oir_id); + if (rtab == NULL) + return (ENOENT); + + if ((mark->orm_flags & OROUTE_ENT_MARKER_F_V6) == 0) { + src = rtab->ort_routev4; + len = rtab->ort_nroutev4; + } else { + src = rtab->ort_routev6; + len = rtab->ort_nroutev6; + } + + dst = ioc_rtab->oir_ents; + while (written < ioc_rtab->oir_count) { + if (mark->orm_index > len) { + ret = EINVAL; + goto done; + } + + if (mark->orm_index == len) { + if ((mark->orm_flags & OROUTE_ENT_MARKER_F_V6) != 0) { + mark->orm_flags |= OROUTE_ENT_MARKER_F_DONE; + break; + } + + mark->orm_index = 0; + mark->orm_flags |= OROUTE_ENT_MARKER_F_V6; + + src = rtab->ort_routev6; + len = rtab->ort_nroutev6; + continue; + } + + bcopy(&src[mark->orm_index++], &dst[written++], sizeof (*dst)); + } + + ioc_rtab->oir_count = written; +done: + overlay_route_tbl_rele(rtab); + return (ret); +} + +/* + * Make sure *entp has enough free entries to hold 'amt' new entries in *entp + */ +static int +overlay_rtab_reserve(overlay_route_ent_t **entpp, uint_t nent, + uint_t *allocp, uint_t amt) +{ + overlay_route_ent_t *newent = NULL; + uint64_t total = nent + amt; + + if (total > UINT_MAX) + return (EOVERFLOW); + + if (total <= *allocp) + return (0); + + total = P2ROUNDUP(total, 4); + newent = kmem_zalloc(total * sizeof (overlay_route_ent_t), KM_SLEEP); + + if (nent > 0) { + bcopy(*entpp, newent, nent * sizeof (overlay_route_ent_t)); + kmem_free(*entpp, *allocp * sizeof (overlay_route_ent_t)); + } + + *entpp = newent; + *allocp = total; + + return (0); +} + +static void +overlay_rtab_remove_dups(overlay_route_ent_t *ents, uint_t *nentp, uint_t alloc) +{ + if (*nentp == 0) + return; + + uint_t i = 0; + uint_t n = *nentp; + + while (i < n - 1) { + if (overlay_route_ent_cmp(&ents[i], &ents[i + 1]) != 0) { + i++; + continue; + } + + /* + * If the last two entries are dup, just reduce the number + * of entries. + */ + if (i + 1 == n) { + n--; + break; + } + + /* + * ent[i] and ent[i + 1] are duplicates. Shift the entries + * starting at ent[i + 1] over one slot. + */ + (void) memmove(&ents[i], &ents[i + 1], + (n - i - 1) * sizeof (overlay_route_ent_t)); + n--; + + /* + * We don't advance i, so we can check i + 1 again in case + * there are a string of duplicates (since the entries are + * sorted, any duplicates will be contiguous). We could get + * more clever and count the run of duplicates to remove + * them in a single memmove(), but realistically userland + * shouldn't be adding lots of duplicates in a single + * request. If they do, they can take the small penalty + * doing it in this manner causes. + */ + } + + /* + * For diagnostic purposes, clear out any entries beyond what's + * actually valid. + */ + if (n < alloc) { + bzero(&ents[n], (alloc - n) * sizeof (overlay_route_ent_t)); + } + + *nentp = n; +} + +static int +add_rtbl_ents(overlay_routetab_t *rtab, const overlay_route_ent_t *ents, + uint_t nent) +{ + uint_t nv4, nv6; + uint_t i; + int ret; + + nv4 = nv6 = 0; + for (i = 0; i < nent; i++) { + const struct sockaddr_in6 *sin6 = &ents[i].ore_target; + + if (IN6_IS_ADDR_V4MAPPED(&ents[i].ore_dest)) + nv4++; + else + nv6++; + + if (sin6->sin6_family != AF_INET6) + return (EINVAL); + + if (!IN6_IS_ADDR_V4MAPPED_ANY(&sin6->sin6_addr) && + !IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && + sin6->sin6_port == 0) { + return (EINVAL); + } + } + + mutex_enter(&rtab->ort_lock); + + ret = overlay_rtab_reserve(&rtab->ort_routev4, rtab->ort_nroutev4, + &rtab->ort_routev4_alloc, nv4); + if (ret != 0) { + mutex_exit(&rtab->ort_lock); + return (ret); + } + + ret = overlay_rtab_reserve(&rtab->ort_routev6, rtab->ort_nroutev6, + &rtab->ort_routev6_alloc, nv6); + if (ret != 0) { + mutex_exit(&rtab->ort_lock); + return (ret); + } + + for (i = 0; i < nent; i++) { + overlay_route_ent_t *dst; + + if (IN6_IS_ADDR_V4MAPPED(&ents[i].ore_dest)) + dst = &rtab->ort_routev4[rtab->ort_nroutev4++]; + else + dst = &rtab->ort_routev6[rtab->ort_nroutev6++]; + + bcopy(&ents[i], dst, sizeof (*dst)); + } + + if (rtab->ort_nroutev4 > 0) { + qsort(rtab->ort_routev4, rtab->ort_nroutev4, + sizeof (overlay_route_ent_t), overlay_route_ent_cmp); + } + + if (rtab->ort_nroutev6 > 0) { + qsort(rtab->ort_routev6, rtab->ort_nroutev6, + sizeof (overlay_route_ent_t), overlay_route_ent_cmp); + } + + overlay_rtab_remove_dups(rtab->ort_routev4, &rtab->ort_nroutev4, + rtab->ort_routev4_alloc); + overlay_rtab_remove_dups(rtab->ort_routev6, &rtab->ort_nroutev6, + rtab->ort_routev6_alloc); + + mutex_exit(&rtab->ort_lock); + return (0); +} + +static int +overlay_route_tbl_set(overlay_router_t *orr, void *buf) +{ + const overlay_ioc_routetab_t *ioc_rtab = buf; + overlay_routetab_t *newtbl, *tbl; + int ret; + + if (!overlay_valid_id(ioc_rtab->oir_id, sizeof (ioc_rtab->oir_id))) + return (EINVAL); + + newtbl = kmem_cache_alloc(overlay_rtab_cache, KM_SLEEP); + + (void) strlcpy(newtbl->ort_id, ioc_rtab->oir_id, + sizeof (newtbl->ort_id)); + + /* + * Proactively set refcnt to 1 for hold in orr -- if we fail before + * adding, we can use overlay_route_tbl_rele() to free. + */ + newtbl->ort_refcnt = 1; + + ret = add_rtbl_ents(newtbl, ioc_rtab->oir_ents, ioc_rtab->oir_count); + if (ret != 0) { + overlay_route_tbl_rele(newtbl); + return (ret); + } + + tbl = overlay_route_tbl_hold_by_id(orr, ioc_rtab->oir_id); + + mutex_enter(&orr->orr_lock); + + /* We already set refcnt to 1 earlier for this */ + list_insert_tail(&orr->orr_routetbls, newtbl); + + /* + * If we're replacing an existing table, we need to remove the old + * one from orr, and update any references to it by any overlay_net_ts + */ + if (tbl != NULL) { + list_remove(&orr->orr_routetbls, tbl); + + mutex_enter(&tbl->ort_lock); + mutex_enter(&newtbl->ort_lock); + + /* + * We should have at least 2 refs -- the one we took plus + * the one from orr->orr_routetbls. Remove the ref for + * orr_routetbls. + */ + ASSERT3U(tbl->ort_refcnt, >, 1); + tbl->ort_refcnt--; + + if (orr->orr_def_routetab == tbl) { + orr->orr_def_routetab = newtbl; + + ASSERT3U(tbl->ort_refcnt, >, 1); + tbl->ort_refcnt--; + newtbl->ort_refcnt++; + } + + if (tbl->ort_refcnt > 1) { + overlay_net_t *iter; + + for (iter = avl_first(&orr->orr_nets_mac); iter != NULL; + iter = AVL_NEXT(&orr->orr_nets_mac, iter)) { + if (iter->ont_routetbl != tbl) + continue; + + mutex_enter(&iter->ont_lock); + iter->ont_routetbl = newtbl; + mutex_exit(&iter->ont_lock); + + ASSERT3U(tbl->ort_refcnt, >, 1); + tbl->ort_refcnt--; + newtbl->ort_refcnt++; + + } + } + mutex_exit(&newtbl->ort_lock); + mutex_exit(&tbl->ort_lock); + } + + mutex_exit(&orr->orr_lock); + + if (tbl != NULL) { + /* Our hold from overlay_route_tbl_hold_by_id() */ + overlay_route_tbl_rele(tbl); + } + + return (ret); +} + +static int +overlay_route_tbl_del(overlay_router_t *orr, void *buf) +{ + overlay_ioc_routetab_t *ioc_rtab = buf; + overlay_routetab_t *rtab; + uint_t cnt; + + if (!overlay_valid_id(ioc_rtab->oir_id, sizeof (ioc_rtab->oir_id))) + return (EINVAL); + + rtab = overlay_route_tbl_hold_by_id(orr, ioc_rtab->oir_id); + if (rtab == NULL) + return (ENOENT); + + cnt = 2; + + mutex_enter(&orr->orr_lock); + if (orr->orr_def_routetab == rtab) + cnt++; + + mutex_enter(&rtab->ort_lock); + if (rtab->ort_refcnt > cnt) { + mutex_exit(&rtab->ort_lock); + mutex_exit(&orr->orr_lock); + + overlay_route_tbl_rele(rtab); + return (EBUSY); + } + + if (orr->orr_def_routetab == rtab) { + orr->orr_def_routetab = NULL; + rtab->ort_refcnt--; + } + + list_remove(&orr->orr_routetbls, rtab); + rtab->ort_refcnt--; + + mutex_exit(&rtab->ort_lock); + mutex_exit(&orr->orr_lock); + + overlay_route_tbl_rele(rtab); + return (0); +} + +static int +overlay_route_tbl_set_default(overlay_router_t *orr, void *buf) +{ + overlay_ioc_routetab_t *ioc_rtab = buf; + overlay_routetab_t *rtab; + + if (ioc_rtab->oir_marker != 0 || ioc_rtab->oir_count != 0) + return (EINVAL); + + if (!overlay_valid_id(ioc_rtab->oir_id, sizeof (ioc_rtab->oir_id))) + return (EINVAL); + + rtab = overlay_route_tbl_hold_by_id(orr, ioc_rtab->oir_id); + if (rtab == NULL && ioc_rtab->oir_id[0] != '\0') + return (ENOENT); + + mutex_enter(&orr->orr_lock); + if (orr->orr_def_routetab != NULL) + overlay_route_tbl_rele(orr->orr_def_routetab); + + orr->orr_def_routetab = rtab; + + mutex_enter(&rtab->ort_lock); + rtab->ort_refcnt++; + mutex_exit(&rtab->ort_lock); + + mutex_exit(&orr->orr_lock); + + return (0); +} + +static int +overlay_route_tbl_addent(overlay_router_t *orr, void *buf) +{ + overlay_ioc_routetab_t *ioc_rtab = buf; + overlay_routetab_t *rtab; + int ret; + + if (ioc_rtab->oir_marker != 0) + return (EINVAL); + + if (!overlay_valid_id(ioc_rtab->oir_id, sizeof (ioc_rtab->oir_id))) + return (EINVAL); + + rtab = overlay_route_tbl_hold_by_id(orr, ioc_rtab->oir_id); + if (rtab == NULL) + return (ENOENT); + + ret = add_rtbl_ents(rtab, ioc_rtab->oir_ents, ioc_rtab->oir_count); + + overlay_route_tbl_rele(rtab); + return (ret); +} + +static int +del_ent(overlay_route_ent_t *ents, uint_t *nentp, + const overlay_route_ent_t *cmp) +{ + overlay_route_ent_t *tgt; + ptrdiff_t idx; + + tgt = bsearch(cmp, ents, *nentp, sizeof (*cmp), overlay_route_ent_cmp); + if (tgt == NULL) + return (ENOENT); + + idx = tgt - ents; + (void) memmove(tgt + 1, tgt, *nentp - idx - 1); + *nentp = *nentp - 1; + + return (0); +} + +static int +overlay_route_tbl_delent(overlay_router_t *orr, void *buf) +{ + overlay_ioc_routetab_t *ioc_rtab = buf; + overlay_routetab_t *rtab; + overlay_route_ent_t *ent; + int ret; + + /* + * We currently only allow deletions one at a time -- if we want to + * allow batch deletions, we probably want a way to respond with + * which (if any) entries weren't found. + */ + if (ioc_rtab->oir_marker != 0 || ioc_rtab->oir_count != 1) + return (EINVAL); + + if (!overlay_valid_id(ioc_rtab->oir_id, sizeof (ioc_rtab->oir_id))) + return (EINVAL); + + ent = ioc_rtab->oir_ents; + + rtab = overlay_route_tbl_hold_by_id(orr, ioc_rtab->oir_id); + + mutex_enter(&rtab->ort_lock); + if (IN6_IS_ADDR_V4MAPPED(&ent->ore_dest)) { + ret = del_ent(rtab->ort_routev4, &rtab->ort_nroutev4, ent); + } else { + ret = del_ent(rtab->ort_routev6, &rtab->ort_nroutev6, ent); + } + mutex_exit(&rtab->ort_lock); + + overlay_route_tbl_rele(rtab); + return (ret); +} + +static int +overlay_routetab_iter_copyin(const void *buf, void **outp, size_t *bsize, + int flags) +{ + overlay_ioc_rtab_iter_t base, *ioc_rtab; + + if (ddi_copyin(buf, &base, sizeof (base), flags & FKIOCTL) != 0) + return (EFAULT); + + *bsize = sizeof (base) + + base.oiri_count * sizeof (overlay_ioc_routetab_t); + ioc_rtab = kmem_zalloc(*bsize, KM_SLEEP); + + if (base.oiri_count == 0) { + bcopy(&base, ioc_rtab, sizeof (base)); + goto done; + } + + if (ddi_copyin(buf, ioc_rtab, *bsize, flags & FKIOCTL) != 0) { + kmem_free(ioc_rtab, *bsize); + return (EFAULT); + } + + if (ioc_rtab->oiri_count != base.oiri_count) { + kmem_free(ioc_rtab, *bsize); + return (EFAULT); + } + +done: + *outp = ioc_rtab; + return (0); +} + +static int +overlay_routetab_iter(overlay_router_t *orr, void *buf) +{ + overlay_ioc_rtab_iter_t *iter = buf; + overlay_routetab_t *start = + (overlay_routetab_t *)(uintptr_t)iter->oiri_marker; + overlay_routetab_t *rtab; + size_t n; + + /* + * iter->oiri_marker is either 0 to start iterating through the + * route tables, 1 when we've finished, or we've set it (on the + * previous ioctl) to the address of the overlay_routetab_t to + * start at + */ + if (iter->oiri_marker == 1UL) { + iter->oiri_count = 0; + return (0); + } + + mutex_enter(&orr->orr_lock); + + /* + * Even though we have the address to start with, we don't + * want to blindly trust the value, we find it, or return an error. + */ + rtab = list_head(&orr->orr_routetbls); + if (start != NULL) { + while (rtab != NULL && rtab != start) + rtab = list_next(&orr->orr_routetbls, rtab); + + if (rtab == NULL) { + mutex_exit(&orr->orr_lock); + return (EINVAL); + } + } + + n = 0; + while (rtab != NULL && n < iter->oiri_count) { + overlay_ioc_routetab_t *ioc_rtab = &iter->oiri_rtabs[n]; + + bzero(ioc_rtab, sizeof (*ioc_rtab)); + ioc_rtab->oir_count = rtab->ort_nroutev4 + rtab->ort_nroutev6; + (void) strlcpy(ioc_rtab->oir_id, rtab->ort_id, + sizeof (ioc_rtab->oir_id)); + + n++; + rtab = list_next(&orr->orr_routetbls, rtab); + } + mutex_exit(&orr->orr_lock); + + iter->oiri_marker = (rtab == NULL) ? 1 : (uintptr_t)rtab; + iter->oiri_count = n; + return (0); +} + +int +overlay_router_open(dev_t *devp, int flags, int otype, cred_t *credp) +{ + if (secpolicy_dl_config(credp) != 0) + return (EPERM); + + if (getminor(*devp) != OVERLAY_ROUTER_MINOR) + return (ENXIO); + + if (otype & OTYP_BLK) + return (EINVAL); + + if (flags & ~(FREAD | FWRITE | FEXCL | FOFFMAX)) + return (EINVAL); + + /* We don't allow O_EXCL for the router device.. for now at least */ + if ((flags & FEXCL) != 0) + return (EINVAL); + + if (!(flags & FREAD) && !(flags & FWRITE)) + return (EINVAL); + + if (crgetzoneid(credp) != GLOBAL_ZONEID) + return (EPERM); + + return (0); +} + +int +overlay_router_close(dev_t dev, int flags, int otype, cred_t *credp) +{ + /* + * Currently nothing to do (i.e. this function left intentionally empty) + */ + return (0); +} + +static overlay_router_ioctl_t overlay_router_ioctab[] = { + { OVERLAY_ROUTER_NET_CREATE, OFF_RW, + NULL, overlay_router_net_create, + sizeof (overlay_ioc_net_t) }, + { OVERLAY_ROUTER_NET_DELETE, OFF_RW, + NULL, overlay_router_net_delete, + sizeof (overlay_ioc_net_t) }, + { OVERLAY_ROUTER_NET_DELETE_ALL, OFF_RW, + NULL, overlay_router_net_delete_all, + sizeof (overlay_ioc_net_t) }, + { OVERLAY_ROUTER_NET_GET, OFF_COPYOUT, + NULL, overlay_router_net_get, + sizeof (overlay_ioc_net_t) }, + { OVERLAY_ROUTER_NET_ITER, OFF_COPYOUT, + overlay_router_iter_copyin, overlay_router_net_iter, + sizeof (overlay_ioc_net_iter_t) }, + { OVERLAY_ROUTER_NET_SET_ROUTETBL, OFF_RW, + NULL, overlay_router_net_set_routetbl, + sizeof (overlay_ioc_net_t) }, + + { OVERLAY_ROUTETBL_GET, OFF_COPYOUT, + overlay_router_tbl_copyin, overlay_route_tbl_get, + sizeof (overlay_ioc_routetab_t) }, + { OVERLAY_ROUTETBL_SET, OFF_RW, + overlay_router_tbl_copyin, overlay_route_tbl_set, + sizeof (overlay_ioc_routetab_t) }, + { OVERLAY_ROUTETBL_REMOVE, OFF_RW, + overlay_router_tbl_copyin, overlay_route_tbl_del, + sizeof (overlay_ioc_routetab_t) }, + { OVERLAY_ROUTETBL_SET_DEFAULT, OFF_RW, + overlay_router_tbl_copyin, overlay_route_tbl_set_default, + sizeof (overlay_ioc_routetab_t) }, +#if 0 + { OVERLAY_ROUTETBL_FLUSH, }, +#endif + + { OVERLAY_ROUTETBL_ITER, OFF_COPYOUT, + overlay_routetab_iter_copyin, overlay_routetab_iter, + sizeof (overlay_ioc_routetab_t) }, + { OVERLAY_ROUTETBL_ADDENT, OFF_RW, + overlay_router_tbl_copyin, overlay_route_tbl_addent, + sizeof (overlay_ioc_routetab_t) }, + { OVERLAY_ROUTETBL_DELENT, OFF_RW, + overlay_router_tbl_copyin, overlay_route_tbl_delent, + sizeof (overlay_ioc_rtab_iter_t) }, + +#if 0 + { OVERLAY_ROUTETBL_FLUSHENT, }, +#endif + + { 0 } +}; + +int +overlay_router_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + overlay_router_ioctl_t *ioc; + + if (secpolicy_dl_config(credp) != 0) + return (EPERM); + + /* + * This whole thing is a private interface, we demand callers are + * 64-bit (it's not worth the effort to support 32-bit callers + * at this time). + */ + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) + return (ENOTSUP); + + for (ioc = overlay_router_ioctab; ioc->ori_cmd != 0; ioc++) { + overlay_router_t *orr; + overlay_router_ioc_hdr_t *hdr; + int ret; + caddr_t buf = NULL; + size_t bufsize = 0; + + if (ioc->ori_cmd != cmd) + continue; + + if (((ioc->ori_flags & OFF_RW) != 0) && ((mode & FWRITE) == 0)) + return (EBADF); + + ASSERT3U(ioc->ori_size, >, 0); + + if (ioc->ori_copyin == NULL) { + bufsize = ioc->ori_size; + buf = kmem_alloc(bufsize, KM_SLEEP); + if (ddi_copyin((void *)(uintptr_t)arg, buf, bufsize, + mode & FKIOCTL) != 0) { + kmem_free(buf, bufsize); + return (EFAULT); + } + } else { + ret = ioc->ori_copyin((void *)(uintptr_t)arg, + (void **)&buf, &bufsize, mode); + if (ret != 0) + return (ret); + } + + VERIFY3U(bufsize, >=, sizeof (*hdr)); + hdr = (overlay_router_ioc_hdr_t *)buf; + ret = overlay_router_hold_by_dlid(hdr->orih_linkid, &orr); + if (ret != 0) + return (ret); + + ret = ioc->ori_func(orr, buf); + overlay_router_rele(orr); + + if ((ioc->ori_flags & OFF_COPYOUT) != 0) { + ret = ddi_copyout(buf, (void *)(uintptr_t)arg, + bufsize, mode & FKIOCTL); + if (ret != 0) + ret = EFAULT; + } + + kmem_free(buf, bufsize); + return (ret); + } + + return (ENXIO); +} + +static overlay_routetab_t * +overlay_get_rtab(overlay_router_t *orr, overlay_net_t *ont) +{ + overlay_routetab_t *rtab; + + mutex_enter(&ont->ont_lock); + rtab = ont->ont_routetbl; + if (rtab != NULL) { + mutex_enter(&rtab->ort_lock); + rtab->ort_refcnt++; + mutex_exit(&rtab->ort_lock); + mutex_exit(&ont->ont_lock); + return (rtab); + } + mutex_exit(&ont->ont_lock); + + mutex_enter(&orr->orr_lock); + rtab = orr->orr_def_routetab; + if (rtab == NULL) { + mutex_exit(&orr->orr_lock); + return (NULL); + } + + mutex_enter(&rtab->ort_lock); + rtab->ort_refcnt++; + mutex_exit(&rtab->ort_lock); + mutex_exit(&orr->orr_lock); + + return (rtab); +} + +static inline uint16_t +overlay_get16(unsigned char **pp) +{ + unsigned char *p = *pp; + uint16_t val; + + val = *p++ << 8; + val |= *p++; + + *pp = p; + return (val); +} + +static inline void +overlay_put8(mblk_t *mp, uint8_t val) +{ + ASSERT3U(MBLKTAIL(mp), >=, sizeof (uint8_t)); + + *(mp->b_wptr) = val; + mp->b_wptr++; +} + +static inline void +overlay_put16(mblk_t *mp, uint16_t val) +{ + /* Since we're constructing the packet, these should be correct */ + ASSERT3U(MBLKTAIL(mp), >=, sizeof (uint16_t)); + ASSERT(IS_P2ALIGNED(mp->b_wptr, sizeof (uint16_t))); + + uint16_t *p = (uint16_t *)mp->b_wptr; + + *p = val; + mp->b_wptr += sizeof (uint16_t); +} + +static inline void +overlay_put32(mblk_t *mp, uint32_t val) +{ + /* + * Since we're constructing the packet, these should be correct + * Because of the ethernet header, the best we can assume is + * 16-bit alignment. + */ + ASSERT3U(MBLKTAIL(mp), >=, sizeof (uint16_t)); + ASSERT(IS_P2ALIGNED(mp->b_wptr, sizeof (uint16_t))); + + bcopy(&val, mp->b_wptr, sizeof (val)); + mp->b_wptr += sizeof (val); +} + +static inline void +overlay_put_mac(mblk_t *mp, const uint8_t *mac) +{ + ASSERT3U(MBLKTAIL(mp), >=, ETHERADDRL); + + bcopy(mac, mp->b_wptr, ETHERADDRL); + mp->b_wptr += ETHERADDRL; +} + +static inline void +overlay_put_ip(mblk_t *mp, in_addr_t ip) +{ + ASSERT3U(MBLKTAIL(mp), >=, sizeof (in_addr_t)); + + bcopy(&ip, mp->b_wptr, sizeof (ip)); + mp->b_wptr += sizeof (ip); +} + +static inline void +overlay_put_ip6(mblk_t *mp, const struct in6_addr *addr) +{ + ASSERT3U(MBLKTAIL(mp), >=, sizeof (struct in6_addr)); + + bcopy(addr, mp->b_wptr, sizeof (*addr)); + mp->b_wptr += sizeof (*addr); +} + +static inline void +overlay_put_eth_vlan(mblk_t *mp, const uint8_t *dst, const uint8_t *src, + uint16_t tci, uint16_t etype) +{ + overlay_put_mac(mp, dst); + overlay_put_mac(mp, src); + if (tci > 0) { + overlay_put16(mp, htons(ETHERTYPE_VLAN)); + overlay_put16(mp, htons(tci)); + } + overlay_put16(mp, htons(etype)); +} + +/* The size of an ARP packet for ethernet */ +#define ARP_ETHER_SIZE 28 + +/* The ARP hardware type for ethernet */ +#define ARP_HW_ETHER 1 + +/* + * Handle ARP requests for the router MAC. Returns B_TRUE if we handled the + * ARP request, B_FALSE if we did not (implying pkt should undergo further + * handling). + * + * In the future, we could look to use the overlay_target L3->L2 mappings + * to satisify ARP requests for known targets (and only drop out to varpd + * for unknown targets), but for now, we just handle ARP for the router + * IP/MAC. + */ +boolean_t +overlay_router_arp(overlay_dev_t *odd, overlay_net_t *ont, overlay_pkt_t *pkt) +{ + unsigned char *ptr; + unsigned char *src_hwaddr; + mblk_t *resp = NULL; + in_addr_t src_ip, tgt_ip; + + ASSERT3P(ont, !=, NULL); + + /* + * Must be sent to either ethernet broadcast address or our + * router address to handle it. + */ + if (bcmp(overlay_bcast, pkt->op_mhi.mhi_daddr, ETHERADDRL) != 0 && + bcmp(ont->ont_mac, pkt->op_mhi.mhi_daddr, ETHERADDRL) != 0) + return (B_FALSE); + + /* + * Some sanity checks: + * + * - Hardware type is ARP_HW_ETHER + * - Protocol type is IPv4 (ETHERTYPE_IP) + * - Hardware length is ETHERADDRL + * - Protocol length is sizeof (in_addr_t) + * - Operation is ARP_REQUEST + * + * overlay_pkt_init() already guarantees us a 28 byte, contiguous + * in ram packet when it's ARP, so we can traverse this safely. + */ + ptr = pkt->op3_u.op3_char; + if (overlay_get16(&ptr) != ARP_HW_ETHER || + overlay_get16(&ptr) != ETHERTYPE_IP || + *ptr++ != ETHERADDRL || *ptr++ != sizeof (in_addr_t) || + overlay_get16(&ptr) != ARPOP_REQUEST) + return (B_FALSE); + + src_hwaddr = ptr; + ptr += ETHERADDRL; + + bcopy(ptr, &src_ip, sizeof (in_addr_t)); + ptr += sizeof (in_addr_t); + + /* skip over the target ethernet address */ + ptr += ETHERADDRL; + + bcopy(ptr, &tgt_ip, sizeof (in_addr_t)); + ptr += sizeof (in_addr_t); + + if (tgt_ip != ont->ont_routeraddr) + return (B_FALSE); + + resp = allocb(pkt->op_mhi.mhi_hdrsize + ARP_ETHER_SIZE, 0); + if (resp == NULL) + return (B_TRUE); + + /* + * For our response, the destination mac is src_hwaddr (the originator + * of the request), and the source mac is our router mac (mac). The + * tci (really just vlan) comes from the source packet, and we're of + * course sending an ARP packet. + */ + overlay_put_eth_vlan(resp, pkt->op_mhi.mhi_saddr, ont->ont_mac, + pkt->op_mhi.mhi_tci, ETHERTYPE_ARP); + + /* + * Construct our ARP response. Since this is a response, the + * sender is the router and the target is the originator of the + * ARP request. + */ + overlay_put16(resp, htons(ARP_HW_ETHER)); /* hardware type */ + overlay_put16(resp, htons(ETHERTYPE_IP)); /* protocol type */ + overlay_put8(resp, ETHERADDRL); /* hw address length */ + overlay_put8(resp, sizeof (in_addr_t)); /* protocol address length */ + overlay_put16(resp, htons(ARPOP_REPLY)); /* operation */ + overlay_put_mac(resp, ont->ont_mac); /* sender hw address */ + overlay_put_ip(resp, tgt_ip); /* sender protocol address */ + overlay_put_mac(resp, src_hwaddr); /* target hw address */ + overlay_put_ip(resp, src_ip); /* target protocol address */ + + mutex_enter(&odd->odd_lock); + overlay_io_start(odd, OVERLAY_F_IN_RX); + mutex_exit(&odd->odd_lock); + + mac_rx(odd->odd_mh, NULL, resp); + + mutex_enter(&odd->odd_lock); + overlay_io_done(odd, OVERLAY_F_IN_RX); + mutex_exit(&odd->odd_lock); + + return (B_TRUE); +} + +boolean_t +overlay_router_ndp(overlay_dev_t *odd, overlay_net_t *ont, overlay_pkt_t *pkt) +{ + nd_neighbor_solicit_t *nd; + size_t len = pkt->op_l4len; + + ASSERT3P(ont, !=, NULL); + ASSERT3U(pkt->op_l4proto, ==, IPPROTO_ICMPV6); + + if (IN6_IS_ADDR_UNSPECIFIED(&ont->ont_routeraddrv6)) + return (B_FALSE); + + if (!IN6_IS_ADDR_MC_SOLICITEDNODE(&pkt->op_dstaddr) && + !IN6_IS_ADDR_MC_LINKLOCAL(&pkt->op_dstaddr)) + return (B_FALSE); + + nd = (nd_neighbor_solicit_t *)pkt->op4_u.op4_char; + + if (nd->nd_ns_type != ND_NEIGHBOR_SOLICIT && nd->nd_ns_code != 0) + return (B_FALSE); + + if (len < sizeof (*nd)) + return (B_FALSE); + + if (IN6_IS_ADDR_MULTICAST(&nd->nd_ns_target) || + IN6_IS_ADDR_V4MAPPED(&nd->nd_ns_target) || + IN6_IS_ADDR_LOOPBACK(&nd->nd_ns_target)) + return (B_FALSE); + + uint8_t *eth = NULL; + nd_opt_hdr_t *opt = (nd_opt_hdr_t *)(nd + 1); + + len -= sizeof (*nd); + while (len >= sizeof (*opt)) { + if (opt->nd_opt_len == 0) + return (B_FALSE); + + if (opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR) { + eth = (uint8_t *)((uintptr_t)opt + + sizeof (nd_opt_hdr_t)); + } + len -= opt->nd_opt_len * 8; + opt = (nd_opt_hdr_t *)((uintptr_t)opt + + opt->nd_opt_len * 8); + } + + if (eth == NULL) + return (B_FALSE); + + if (!IN6_ARE_ADDR_EQUAL(&ont->ont_routeraddrv6, &nd->nd_ns_target)) + return (B_FALSE); + + /* It's for us, construct a reply */ + mblk_t *resp = allocb(ETHERMAX + VLAN_TAGSZ, 0); + + if (resp == NULL) + return (B_TRUE); + + overlay_put_eth_vlan(resp, pkt->op_mhi.mhi_saddr, ont->ont_mac, + pkt->op_mhi.mhi_tci, ETHERTYPE_IPV6); + + ip6_t *ip6h = (ip6_t *)resp->b_wptr; + + /* + * Write the IPv6 header out. Destination IP is the source IP from + * the request, and the source IP is the router IP. + */ + bcopy(pkt->op3_u.op3_ipv6, ip6h, sizeof (*ip6h)); + bcopy(&ont->ont_routeraddrv6, &ip6h->ip6_src, sizeof (struct in6_addr)); + bcopy(&pkt->op3_u.op3_ipv6->ip6_src, &ip6h->ip6_dst, + sizeof (struct in6_addr)); + ip6h->ip6_nxt = IPPROTO_ICMPV6; + resp->b_wptr += sizeof (*ip6h); + + nd_neighbor_advert_t *na = (nd_neighbor_advert_t *)resp->b_wptr; + + bzero(na, sizeof (*na)); + na->nd_na_type = ND_NEIGHBOR_ADVERT; + na->nd_na_code = 0; + /* + * RFC 4443 defines that we should set the checksum to zero before we + * calculate the checksumat we should set the checksum to zero before we + * calculate it. + */ + na->nd_na_cksum = 0; + /* + * The header has already transformed this + * into the appropriate host order. Don't use htonl. + */ + na->nd_na_flags_reserved = ND_NA_FLAG_SOLICITED | ND_NA_FLAG_OVERRIDE; + bcopy(&ont->ont_routeraddrv6, &na->nd_na_target, + sizeof (struct in6_addr)); + resp->b_wptr += sizeof (*na); + + opt = (nd_opt_hdr_t *)resp->b_wptr; + opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; + opt->nd_opt_len = 1; + resp->b_wptr += sizeof (*opt); + + overlay_put_mac(resp, ont->ont_mac); + + /* Set the IPv6 length */ + len = (uintptr_t)resp->b_wptr - (uintptr_t)na; + ip6h->ip6_plen = htons(len); + + /* + * Calculate the IPv6 checksum. As nice it would be to re-use the + * existing in-kernel IPv6 checksum code, it requires some additional + * structures. Maybe in the future we can adjust things to use it. + */ + uint16_t *v; + uint32_t sum = 0; + + v = (uint16_t *)&ip6h->ip6_src; + for (size_t i = 0; i < sizeof (struct in6_addr); i += 2, v++) + sum += *v; + + v = (uint16_t *)&ip6h->ip6_dst; + for (size_t i = 0; i < sizeof (struct in6_addr); i += 2, v++) + sum += *v; + + sum += ip6h->ip6_plen; + +#ifdef _BIG_ENDIAN + sum += IPPROTO_ICMPV6; +#else + sum += IPPROTO_ICMPV6 << 8; +#endif + + v = (uint16_t *)na; + for (size_t i = 0; i < len; i += 2, v++) + sum += *v; + + while ((sum >> 16) != 0) + sum = (sum & 0xffff) + (sum >> 16); + + sum &= 0xffff; + na->nd_na_cksum = ~sum & 0xffff; + + mutex_enter(&odd->odd_lock); + overlay_io_start(odd, OVERLAY_F_IN_RX); + mutex_exit(&odd->odd_lock); + + mac_rx(odd->odd_mh, NULL, resp); + + mutex_enter(&odd->odd_lock); + overlay_io_done(odd, OVERLAY_F_IN_RX); + + return (B_TRUE); +} + +static uint8_t +overlay_pkt_hash(overlay_pkt_t *pkt) +{ + uint32_t hash = 0; + + hash ^= pkt->op_srcaddr.s6_addr32[0] ^ pkt->op_dstaddr.s6_addr32[0]; + hash ^= pkt->op_srcaddr.s6_addr32[1] ^ pkt->op_dstaddr.s6_addr32[1]; + hash ^= pkt->op_srcaddr.s6_addr32[2] ^ pkt->op_dstaddr.s6_addr32[2]; + hash ^= pkt->op_srcaddr.s6_addr32[3] ^ pkt->op_dstaddr.s6_addr32[3]; + hash ^= ((uint32_t)pkt->op_dstport) << 16 | pkt->op_srcport; + + uint8_t *p = (uint8_t *)&hash; + + return (p[0] ^ p[1] ^ p[2] ^ p[3] ^ pkt->op_l4proto); +} + +static boolean_t +overlay_router_get_target(overlay_routetab_t *ort, overlay_pkt_t *pkt, + struct sockaddr *addr, socklen_t *slenp) +{ + const struct in6_addr *dst = &pkt->op_dstaddr; + overlay_route_ent_t *ents, **matches; + uint_t nents, matchlen, nmatch; + + if (IN6_IS_ADDR_V4MAPPED(dst)) { + ents = ort->ort_routev4; + nents = ort->ort_nroutev4; + } else { + ents = ort->ort_routev6; + nents = ort->ort_nroutev6; + } + + if (nents == 0) + return (B_FALSE); + + /* + * We assume the size of a given overlay route table should be + * rather small, such that holding an array of pointers equal to + * the number of entries (the largest possible number of matches) + * shouldn't be too terrible. + * + * For now we just check the entire table. In the future, we may + * want to store the route tables in a more efficient manner (e.g. + * some form of radix tree), but this should be good enough for now. + * The way we store route table entries is an implementation detail + * and not even the ioctl interfaces depend on it, so it shouldn't + * too burdensome to swap it out in the future if necessary. + */ + matches = kmem_zalloc(nents * sizeof (overlay_route_ent_t *), + KM_NOSLEEP | KM_NORMALPRI); + if (matches == NULL) + return (B_FALSE); + + matchlen = 0; + nmatch = 0; + for (uint_t i = 0; i < nents; i++) { + if (!IN6_ARE_PREFIXEDADDR_EQUAL(&ents[i].ore_dest, dst, + ents[i].ore_prefixlen)) { + continue; + } + + /* + * If the match is a shorter match then the longest one so + * far, skip it. + */ + if (ents[i].ore_prefixlen < matchlen) + continue; + + /* + * If we have a longer match, reset the number of matches, + * and update our longest prefix match length. + */ + if (ents[i].ore_prefixlen > matchlen) { + matchlen = ents[i].ore_prefixlen; + nmatch = 0; + } + + matches[nmatch++] = &ents[i]; + } + + if (nmatch == 0) { + kmem_free(matches, nents * sizeof (overlay_route_ent_t *)); + return (B_FALSE); + } + + uint_t idx = (nmatch > 1) ? overlay_pkt_hash(pkt) % nmatch : 0; + struct sockaddr_in6 *sin6_match = &matches[idx]->ore_target; + + VERIFY3S(sin6_match->sin6_family, ==, AF_INET6); + + if (IN6_IS_ADDR_V4MAPPED(&sin6_match->sin6_addr)) { + struct sockaddr_in *sin = (struct sockaddr_in *)addr; + + sin->sin_family = AF_INET; + IN6_V4MAPPED_TO_INADDR(&sin6_match->sin6_addr, &sin->sin_addr); + sin->sin_port = sin6_match->sin6_port; + *slenp = sizeof (struct sockaddr_in); + } else { + bcopy(sin6_match, addr, sizeof (*sin6_match)); + *slenp = sizeof (*sin6_match); + } + + kmem_free(matches, nents * sizeof (overlay_route_ent_t *)); + return (B_TRUE); +} + +static inline boolean_t +is_target_local(const struct sockaddr *sa) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa; + + switch (sa->sa_family) { + case AF_INET: + if (sin->sin_addr.s_addr == INADDR_ANY) + return (B_TRUE); + break; + case AF_INET6: + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + return (B_TRUE); + break; + default: + cmn_err(CE_PANIC, "unexpected address family value"); + } + + return (B_FALSE); +} + +int +overlay_route(overlay_dev_t *odd, overlay_net_t *ont, overlay_pkt_t *pkt, + struct sockaddr *addr, socklen_t *lenp) +{ + overlay_router_t *orr = odd->odd_router; + overlay_routetab_t *rtab = NULL; + overlay_target_t *ott = NULL; + int ret = 0; + + /* + * If we're routing, the destination MAC and vlan of the packet + * should agree, otherwise we drop the packet. + */ + if (OPKT_VLAN(pkt) != ont->ont_vlan) + return (OVERLAY_TARGET_DROP); + + switch (pkt->op_mhi.mhi_bindsap) { + case ETHERTYPE_IP: + case ETHERTYPE_IPV6: + break; + default: + /* + * Drop any non IPv4 or IPv6 packets directed to the router + * MAC. + */ + return (OVERLAY_TARGET_DROP); + } + + ott = odd->odd_target; + if (ott == NULL) + return (OVERLAY_TARGET_DROP); + + /* + * No routing table, and the packet was sent to the router MAC, + * we drop. + */ + rtab = overlay_get_rtab(orr, ont); + if (rtab == NULL) + return (OVERLAY_TARGET_DROP); + + /* + * Find the target address+port for this packet in the routing table. + * If no target was found, we drop the packet. + */ + if (!overlay_router_get_target(rtab, pkt, addr, lenp)) { + ret = OVERLAY_TARGET_DROP; + goto done; + } + + /* + * If the target is the 'local' destination (the target is the + * any/unspecified address, we do a VL3->VL2 lookup and route within + * the vnet. + */ + if (is_target_local(addr)) { + ret = overlay_target_lookup(odd, pkt, B_TRUE, addr, lenp); + } + + /* XXX: Any additional handling (e.g. NAT, ...) would go here */ + +done: + overlay_route_tbl_rele(rtab); + return (ret); +} + +static int +overlay_cmp_net_vlan(const void *a, const void *b) +{ + const overlay_net_t *l = a; + const overlay_net_t *r = b; + + if (l->ont_vlan < r->ont_vlan) + return (-1); + if (l->ont_vlan > r->ont_vlan) + return (1); + return (0); +} + +static int +overlay_cmp_net_mac(const void *a, const void *b) +{ + const overlay_net_t *l = a; + const overlay_net_t *r = b; + + for (uint_t i = 0; i < ETHERADDRL; i++) { + if (l->ont_mac[i] > r->ont_mac[i]) + return (1); + if (l->ont_mac[i] < r->ont_mac[i]) + return (-1); + } + + return (0); +} + +static int +overlay_cmp_net_v4(const void *a, const void *b) +{ + const overlay_net_t *l_net = a; + const overlay_net_t *r_net = b; + const uint32_t l = ntohl((uint32_t)l_net->ont_net); + const uint32_t r = ntohl((uint32_t)r_net->ont_net); + + if (l < r) + return (-1); + if (l > r) + return (1); + return (0); +} + +static int +overlay_cmp_net_v6(const void *a, const void *b) +{ + const overlay_net_t *l = a; + const overlay_net_t *r = b; + const struct in6_addr *laddr = &l->ont_netv6; + const struct in6_addr *raddr = &r->ont_netv6; + + for (uint_t i = 0; i < sizeof (struct in6_addr); i++) { + if (laddr->s6_addr[i] < raddr->s6_addr[i]) + return (-1); + if (laddr->s6_addr[i] > raddr->s6_addr[i]) + return (1); + } + return (0); +} + +static int +overlay_router_cache_ctor(void *buf, void *arg __unused, int kmflags __unused) +{ + overlay_router_t *orr = buf; + + bzero(orr, sizeof (*orr)); + mutex_init(&orr->orr_lock, NULL, MUTEX_DRIVER, NULL); + + list_create(&orr->orr_routetbls, + sizeof (overlay_routetab_t), + offsetof(overlay_routetab_t, ort_link)); + + avl_create(&orr->orr_nets_vlan, overlay_cmp_net_vlan, + sizeof (overlay_net_t), offsetof(overlay_net_t, ont_node_vlan)); + avl_create(&orr->orr_nets_mac, overlay_cmp_net_mac, + sizeof (overlay_net_t), offsetof(overlay_net_t, ont_node_mac)); + avl_create(&orr->orr_nets_v4, overlay_cmp_net_v4, + sizeof (overlay_net_t), offsetof(overlay_net_t, ont_node_v4)); + avl_create(&orr->orr_nets_v6, overlay_cmp_net_v6, + sizeof (overlay_net_t), offsetof(overlay_net_t, ont_node_v6)); + + return (0); +} + +static void +overlay_router_cache_dtor(void *buf, void *arg __unused) +{ + overlay_router_t *orr = buf; + + avl_destroy(&orr->orr_nets_v6); + avl_destroy(&orr->orr_nets_v4); + avl_destroy(&orr->orr_nets_mac); + avl_destroy(&orr->orr_nets_vlan); + list_destroy(&orr->orr_routetbls); + + mutex_destroy(&orr->orr_lock); +} + +static int +overlay_net_cache_ctor(void *buf, void *arg __unused, int kmflags __unused) +{ + overlay_net_t *ont = buf; + + bzero(ont, sizeof (*ont)); + mutex_init(&ont->ont_lock, NULL, MUTEX_DRIVER, NULL); + return (0); +} + +static void +overlay_net_cache_dtor(void *buf, void *arg __unused) +{ + overlay_net_t *ont = buf; + + mutex_destroy(&ont->ont_lock); +} + +static int +overlay_rtab_cache_ctor(void *buf, void *arg __unused, int kmflags __unused) +{ + overlay_routetab_t *ort = buf; + + bzero(ort, sizeof (*ort)); + mutex_init(&ort->ort_lock, NULL, MUTEX_DRIVER, NULL); + return (0); +} + +static void +overlay_rtab_cache_dtor(void *buf, void *arg __unused) +{ + overlay_routetab_t *ort = buf; + + mutex_destroy(&ort->ort_lock); +} + +void +overlay_router_init(void) +{ + overlay_router_cache = kmem_cache_create("overlay_router", + sizeof (overlay_router_t), 0, overlay_router_cache_ctor, + overlay_router_cache_dtor, NULL, NULL, NULL, 0); + overlay_net_cache = kmem_cache_create("overlay_router_net", + sizeof (overlay_net_t), 0, overlay_net_cache_ctor, + overlay_net_cache_dtor, NULL, NULL, NULL, 0); + overlay_rtab_cache = kmem_cache_create("overlay_route_table", + sizeof (overlay_routetab_t), 0, overlay_rtab_cache_ctor, + overlay_rtab_cache_dtor, NULL, NULL, NULL, 0); +} + +void +overlay_router_fini(void) +{ + kmem_cache_destroy(overlay_router_cache); + kmem_cache_destroy(overlay_net_cache); + kmem_cache_destroy(overlay_rtab_cache); +} diff --git a/usr/src/uts/common/io/overlay/overlay_target.c b/usr/src/uts/common/io/overlay/overlay_target.c index f4147b56d13a..038f9999c722 100644 --- a/usr/src/uts/common/io/overlay/overlay_target.c +++ b/usr/src/uts/common/io/overlay/overlay_target.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2016 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ /* @@ -39,8 +39,25 @@ #include #include +#include +#include +#include +#include +#include + #include -#include + +/* + * We normally should not need to do a msgpullup() on an mblk_t. + * However, there are certain circumstances that shouldn't be common, but + * still technically possible where we elect to use msgpullup() for our + * own sanity (e.g. a header is split across more than one mblk_t). + * Anything generating such awful mblk_ts is probably worth investigating, + * so we define some dtrace probes to help identify those if they occur. + */ +#define OVERLAY_PULLUPMSG(mp, reason) \ + DTRACE_PROBE2(__overlay_pullupmsg, mblk_t *, mp, const char *, reason) + /* * This is total straw man, but at least it's a prime number. Here we're @@ -69,7 +86,7 @@ typedef int (*overlay_target_copyin_f)(const void *, void **, size_t *, int); typedef int (*overlay_target_ioctl_f)(overlay_target_hdl_t *, void *); typedef int (*overlay_target_copyout_f)(void *, void *, size_t, int); -typedef struct overaly_target_ioctl { +typedef struct overlay_target_ioctl { int oti_cmd; /* ioctl id */ boolean_t oti_write; /* ioctl requires FWRITE */ boolean_t oti_ncopyout; /* copyout data? */ @@ -155,6 +172,41 @@ overlay_mac_cmp(const void *a, const void *b) return (bcmp(a, b, ETHERADDRL)); } +static uint64_t +overlay_ip_hash(const void *v) +{ + uint32_t crc; + CRC32(crc, v, sizeof (struct in6_addr), -1U, crc32_table); + return (crc); +} + +static int +overlay_ip_cmp(const void *a, const void *b) +{ + return (bcmp(a, b, sizeof (struct in6_addr))); +} + +static int +overlay_ip_avl(const void *a, const void *b) +{ + const overlay_target_entry_t *l = a; + const overlay_target_entry_t *r = b; + const struct in6_addr *l_ip = &l->ote_key.otk_ip; + const struct in6_addr *r_ip = &r->ote_key.otk_ip; + int i; + + ASSERT(l->ote_flags & OVERLAY_ENTRY_F_L3); + ASSERT(r->ote_flags & OVERLAY_ENTRY_F_L3); + + for (i = 0; i < sizeof (struct in6_addr); i++) { + if (l_ip->s6_addr[i] < r_ip->s6_addr[i]) + return (-1); + if (l_ip->s6_addr[i] > r_ip->s6_addr[i]) + return (1); + } + return (0); +} + /* ARGSUSED */ static void overlay_target_entry_dtor(void *arg) @@ -162,7 +214,8 @@ overlay_target_entry_dtor(void *arg) overlay_target_entry_t *ote = arg; ote->ote_flags = 0; - bzero(ote->ote_addr, ETHERADDRL); + bzero(&ote->ote_key, sizeof (ote->ote_key)); + bzero(&ote->ote_entry, sizeof (ote->ote_entry)); ote->ote_ott = NULL; ote->ote_odd = NULL; freemsgchain(ote->ote_chead); @@ -175,15 +228,19 @@ overlay_target_entry_dtor(void *arg) static int overlay_mac_avl(const void *a, const void *b) { + const overlay_target_entry_t *l = a; + const overlay_target_entry_t *r = b; + const uint8_t *l_mac = l->ote_key.otk_mac; + const uint8_t *r_mac = r->ote_key.otk_mac; int i; - const overlay_target_entry_t *l, *r; - l = a; - r = b; + + ASSERT0(l->ote_flags & OVERLAY_ENTRY_F_L3); + ASSERT0(r->ote_flags & OVERLAY_ENTRY_F_L3); for (i = 0; i < ETHERADDRL; i++) { - if (l->ote_addr[i] > r->ote_addr[i]) + if (l_mac[i] > r_mac[i]) return (1); - else if (l->ote_addr[i] < r->ote_addr[i]) + else if (l_mac[i] < r_mac[i]) return (-1); } @@ -210,7 +267,7 @@ overlay_target_init(void) list_create(&overlay_thdl_list, sizeof (overlay_target_hdl_t), offsetof(overlay_target_hdl_t, oth_link)); overlay_thdl_idspace = id_space_create("overlay_target_minors", - 1, INT32_MAX); + OVERLAY_MINOR_START, INT32_MAX); } void @@ -226,6 +283,28 @@ overlay_target_fini(void) ddi_soft_state_fini(&overlay_thdl_state); } +static void +overlay_cache_free(qqcache_t *qp, avl_tree_t *ap) +{ + overlay_target_entry_t *ote; + + /* + * Our AVL tree and hashtable contain the same elements, + * therefore we should just remove it from the tree, but then + * delete the entries when we remove them from the hash table + * (which happens through the qqcache dtor). + */ + while ((ote = avl_first(ap)) != NULL) + avl_remove(ap, ote); + + avl_destroy(ap); + for (ote = qqcache_first(qp); ote != NULL; + ote = qqcache_next(qp, ote)) { + qqcache_remove(qp, ote); + } + qqcache_destroy(qp); +} + void overlay_target_free(overlay_dev_t *odd) { @@ -233,25 +312,10 @@ overlay_target_free(overlay_dev_t *odd) return; if (odd->odd_target->ott_mode == OVERLAY_TARGET_DYNAMIC) { - refhash_t *rp = odd->odd_target->ott_u.ott_dyn.ott_dhash; - avl_tree_t *ap = &odd->odd_target->ott_u.ott_dyn.ott_tree; - overlay_target_entry_t *ote; - - /* - * Our AVL tree and hashtable contain the same elements, - * therefore we should just remove it from the tree, but then - * delete the entries when we remove them from the hash table - * (which happens through the refhash dtor). - */ - while ((ote = avl_first(ap)) != NULL) - avl_remove(ap, ote); - - avl_destroy(ap); - for (ote = refhash_first(rp); ote != NULL; - ote = refhash_next(rp, ote)) { - refhash_remove(rp, ote); - } - refhash_destroy(rp); + overlay_cache_free(odd->odd_target->ott_u.ott_dyn.ott_cache, + &odd->odd_target->ott_u.ott_dyn.ott_tree); + overlay_cache_free(odd->odd_target->ott_u.ott_dyn.ott_l3cache, + &odd->odd_target->ott_u.ott_dyn.ott_l3tree); } ASSERT(odd->odd_target->ott_ocount == 0); @@ -305,14 +369,16 @@ overlay_target_quiesce(overlay_target_t *ott) * this time, say for NVGRE, we drop all packets that mcuh this. */ int -overlay_target_lookup(overlay_dev_t *odd, mblk_t *mp, struct sockaddr *sock, - socklen_t *slenp) +overlay_target_lookup(overlay_dev_t *odd, overlay_pkt_t *pkt, boolean_t is_l3, + struct sockaddr *sock, socklen_t *slenp) { int ret; struct sockaddr_in6 *v6; overlay_target_t *ott; - mac_header_info_t mhi; overlay_target_entry_t *entry; + qqcache_t *cache; + avl_tree_t *avl; + const void *key; ASSERT(odd->odd_target != NULL); @@ -343,16 +409,19 @@ overlay_target_lookup(overlay_dev_t *odd, mblk_t *mp, struct sockaddr *sock, ASSERT(ott->ott_mode == OVERLAY_TARGET_DYNAMIC); - /* - * Note we only want the MAC address here, therefore we won't bother - * using mac_vlan_header_info(). If any caller needs the vlan info at - * this point, this should change to a call to mac_vlan_header_info(). - */ - if (mac_header_info(odd->odd_mh, mp, &mhi) != 0) - return (OVERLAY_TARGET_DROP); + if (is_l3) { + cache = ott->ott_u.ott_dyn.ott_l3cache; + avl = &ott->ott_u.ott_dyn.ott_l3tree; + key = &pkt->op_dstaddr; + } else { + cache = ott->ott_u.ott_dyn.ott_cache; + avl = &ott->ott_u.ott_dyn.ott_tree; + key = pkt->op_mhi.mhi_daddr; + } + + v6 = (struct sockaddr_in6 *)sock; mutex_enter(&ott->ott_lock); - entry = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, - mhi.mhi_daddr); + entry = qqcache_lookup(cache, key); if (entry == NULL) { entry = kmem_cache_alloc(overlay_entry_cache, KM_NOSLEEP | KM_NORMALPRI); @@ -360,32 +429,60 @@ overlay_target_lookup(overlay_dev_t *odd, mblk_t *mp, struct sockaddr *sock, mutex_exit(&ott->ott_lock); return (OVERLAY_TARGET_DROP); } - bcopy(mhi.mhi_daddr, entry->ote_addr, ETHERADDRL); - entry->ote_chead = entry->ote_ctail = mp; - entry->ote_mbsize = msgsize(mp); + if (is_l3) { + entry->ote_flags |= OVERLAY_ENTRY_F_L3; + bcopy(&pkt->op_dstaddr, &entry->ote_key.otk_ip, + sizeof (struct in6_addr)); + } else { + bcopy(pkt->op_mhi.mhi_daddr, entry->ote_key.otk_mac, + ETHERADDRL); + } + entry->ote_chead = entry->ote_ctail = pkt->op_mblk; + entry->ote_mbsize = msgsize(pkt->op_mblk); entry->ote_flags |= OVERLAY_ENTRY_F_PENDING; entry->ote_ott = ott; entry->ote_odd = odd; - refhash_insert(ott->ott_u.ott_dyn.ott_dhash, entry); - avl_add(&ott->ott_u.ott_dyn.ott_tree, entry); + qqcache_insert(cache, entry); + avl_add(avl, entry); mutex_exit(&ott->ott_lock); overlay_target_queue(entry); return (OVERLAY_TARGET_ASYNC); } - refhash_hold(ott->ott_u.ott_dyn.ott_dhash, entry); + qqcache_hold(cache, entry); mutex_exit(&ott->ott_lock); mutex_enter(&entry->ote_lock); if (entry->ote_flags & OVERLAY_ENTRY_F_DROP) { ret = OVERLAY_TARGET_DROP; } else if (entry->ote_flags & OVERLAY_ENTRY_F_VALID) { - bcopy(&entry->ote_dest.otp_ip, &v6->sin6_addr, + if (is_l3) { + /* + * If we are routing this packet (is_l3 is B_TRUE), + * we've found the MAC for the VL3 dest IP (the guest's + * destination IP). Set the destination MAC for the + * packet to the guest's MAC and process it as a L2 + * packet. The destination will adjust the vlan and + * source MAC as necessary before passing it to the + * upper vnic. + */ + + /* drop const */ + void *destp = (void *)pkt->op_mhi.mhi_daddr; + + bcopy(entry->ote_entry.otp_mac, destp, ETHERADDRL); + mutex_exit(&entry->ote_lock); + ret = overlay_target_lookup(odd, pkt, B_FALSE, sock, + slenp); + goto done; + } + + bcopy(&entry->ote_entry.otp_ip, &v6->sin6_addr, sizeof (struct in6_addr)); - v6->sin6_port = htons(entry->ote_dest.otp_port); + v6->sin6_port = htons(entry->ote_entry.otp_port); *slenp = sizeof (struct sockaddr_in6); ret = OVERLAY_TARGET_OK; } else { - size_t mlen = msgsize(mp); + size_t mlen = msgsize(pkt->op_mblk); if (mlen + entry->ote_mbsize > overlay_ent_size) { ret = OVERLAY_TARGET_DROP; @@ -393,11 +490,11 @@ overlay_target_lookup(overlay_dev_t *odd, mblk_t *mp, struct sockaddr *sock, if (entry->ote_ctail != NULL) { ASSERT(entry->ote_ctail->b_next == NULL); - entry->ote_ctail->b_next = mp; - entry->ote_ctail = mp; + entry->ote_ctail->b_next = pkt->op_mblk; + entry->ote_ctail = pkt->op_mblk; } else { - entry->ote_chead = mp; - entry->ote_ctail = mp; + entry->ote_chead = pkt->op_mblk; + entry->ote_ctail = pkt->op_mblk; } entry->ote_mbsize += mlen; if ((entry->ote_flags & @@ -411,8 +508,9 @@ overlay_target_lookup(overlay_dev_t *odd, mblk_t *mp, struct sockaddr *sock, } mutex_exit(&entry->ote_lock); +done: mutex_enter(&ott->ott_lock); - refhash_rele(ott->ott_u.ott_dyn.ott_dhash, entry); + qqcache_rele(cache, entry); mutex_exit(&ott->ott_lock); return (ret); @@ -448,6 +546,7 @@ overlay_target_associate(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_target_t *ott; + overlay_router_t *orr; overlay_targ_associate_t *ota = arg; odd = overlay_hold_by_dlid(ota->ota_linkid); @@ -498,31 +597,69 @@ overlay_target_associate(overlay_target_hdl_t *thdl, void *arg) if (ott->ott_mode == OVERLAY_TARGET_POINT) { bcopy(&ota->ota_point, &ott->ott_u.ott_point, sizeof (overlay_target_point_t)); + orr = NULL; } else { - ott->ott_u.ott_dyn.ott_dhash = refhash_create(OVERLAY_HSIZE, + int ret; + + ret = qqcache_create(&ott->ott_u.ott_dyn.ott_cache, + odd->odd_cachesz, odd->odd_cachea, OVERLAY_HSIZE, overlay_mac_hash, overlay_mac_cmp, overlay_target_entry_dtor, sizeof (overlay_target_entry_t), - offsetof(overlay_target_entry_t, ote_reflink), - offsetof(overlay_target_entry_t, ote_addr), KM_SLEEP); + offsetof(overlay_target_entry_t, ote_cachelink), + offsetof(overlay_target_entry_t, ote_key.otk_mac), + KM_SLEEP); + + if (ret != 0) { + kmem_cache_free(overlay_target_cache, ott); + overlay_hold_rele(odd); + return (ret); + } + + /* + * At least initially, the L3 cache is the same size as + * the L2 cache. Perhaps later we'll add separate sizes. + */ + ret = qqcache_create(&ott->ott_u.ott_dyn.ott_l3cache, + odd->odd_cachesz, odd->odd_cachea, OVERLAY_HSIZE, + overlay_ip_hash, overlay_ip_cmp, + overlay_target_entry_dtor, sizeof (overlay_target_entry_t), + offsetof(overlay_target_entry_t, ote_cachelink), + offsetof(overlay_target_entry_t, ote_key.otk_ip), + KM_SLEEP); + + if (ret != 0) { + qqcache_destroy(ott->ott_u.ott_dyn.ott_cache); + kmem_cache_free(overlay_target_cache, ott); + overlay_hold_rele(odd); + return (ret); + } + avl_create(&ott->ott_u.ott_dyn.ott_tree, overlay_mac_avl, sizeof (overlay_target_entry_t), offsetof(overlay_target_entry_t, ote_avllink)); + + avl_create(&ott->ott_u.ott_dyn.ott_l3tree, overlay_ip_avl, + sizeof (overlay_target_entry_t), + offsetof(overlay_target_entry_t, ote_avllink)); + + orr = overlay_router_create(odd); } mutex_enter(&odd->odd_lock); if (odd->odd_flags & OVERLAY_F_VARPD) { mutex_exit(&odd->odd_lock); kmem_cache_free(overlay_target_cache, ott); + overlay_router_free(orr); overlay_hold_rele(odd); return (EEXIST); } odd->odd_flags |= OVERLAY_F_VARPD; odd->odd_target = ott; + odd->odd_router = orr; mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); - return (0); } @@ -585,7 +722,8 @@ overlay_target_lookup_request(overlay_target_hdl_t *thdl, void *arg) overlay_targ_lookup_t *otl = arg; overlay_target_entry_t *entry; clock_t ret, timeout; - mac_header_info_t mhi; + overlay_pkt_t pkt; + const char *reason; timeout = ddi_get_lbolt() + drv_usectohz(MICROSEC); again: @@ -612,20 +750,25 @@ overlay_target_lookup_request(overlay_target_hdl_t *thdl, void *arg) * If we have a bogon that doesn't have a valid mac header, drop it and * try again. */ - if (mac_vlan_header_info(entry->ote_odd->odd_mh, entry->ote_chead, - &mhi) != 0) { + if (overlay_pkt_init(&pkt, entry->ote_odd->odd_mh, + entry->ote_chead, &reason) != 0) { boolean_t queue = B_FALSE; mblk_t *mp = entry->ote_chead; + entry->ote_chead = mp->b_next; mp->b_next = NULL; if (entry->ote_ctail == mp) entry->ote_ctail = entry->ote_chead; entry->ote_mbsize -= msgsize(mp); + if (entry->ote_chead != NULL) queue = B_TRUE; mutex_exit(&entry->ote_lock); + if (queue == B_TRUE) overlay_target_queue(entry); + + OVERLAY_FREEMSG(mp, reason); freemsg(mp); goto again; } @@ -634,14 +777,31 @@ overlay_target_lookup_request(overlay_target_hdl_t *thdl, void *arg) otl->otl_reqid = (uintptr_t)entry; otl->otl_varpdid = entry->ote_ott->ott_id; otl->otl_vnetid = entry->ote_odd->odd_vid; - - otl->otl_hdrsize = mhi.mhi_hdrsize; + otl->otl_hdrsize = pkt.op_mhi.mhi_hdrsize; otl->otl_pktsize = msgsize(entry->ote_chead) - otl->otl_hdrsize; - bcopy(mhi.mhi_daddr, otl->otl_dstaddr, ETHERADDRL); - bcopy(mhi.mhi_saddr, otl->otl_srcaddr, ETHERADDRL); - otl->otl_dsttype = mhi.mhi_dsttype; - otl->otl_sap = mhi.mhi_bindsap; - otl->otl_vlan = VLAN_ID(mhi.mhi_tci); + + if (OTE_IS_L3(entry)) { + otl->otl_l3lookup = B_TRUE; + bcopy(&pkt.op_dstaddr, &otl->otl_u.otl_l3.otl3_dest, + sizeof (struct in6_addr)); + + DTRACE_PROBE3(__overlay_vl3__lookup__req, + uintptr_t, otl->otl_reqid, uint64_t, otl->otl_vnetid, + struct in6_addr *, &otl->otl_u.otl_l3.otl3_dest); + } else { + otl->otl_l3lookup = B_FALSE; + bcopy(pkt.op_mhi.mhi_daddr, otl->otl_u.otl_l2.otl2_dstaddr, + ETHERADDRL); + bcopy(pkt.op_mhi.mhi_saddr, otl->otl_u.otl_l2.otl2_srcaddr, + ETHERADDRL); + otl->otl_u.otl_l2.otl2_dsttype = pkt.op_mhi.mhi_dsttype; + otl->otl_u.otl_l2.otl2_sap = pkt.op_mhi.mhi_bindsap; + otl->otl_u.otl_l2.otl2_vlan = VLAN_ID(pkt.op_mhi.mhi_tci); + + DTRACE_PROBE3(__overlay_vl2__lookup__req, + uintptr_t, otl->otl_reqid, uint64_t, otl->otl_vnetid, + uint8_t *, otl->otl_u.otl_l2.otl2_dstaddr); + } mutex_exit(&entry->ote_lock); mutex_enter(&thdl->oth_lock); @@ -672,8 +832,14 @@ overlay_target_lookup_respond(overlay_target_hdl_t *thdl, void *arg) list_remove(&thdl->oth_outstanding, entry); mutex_exit(&thdl->oth_lock); + /* + * For L3 lookups, we depend on varpd injecting the L2 + * entry prior to issuing the reply ioctl to an L3 request, + * so once we have the L3 entry, we should be able to + * process any L3 queued messages. + */ mutex_enter(&entry->ote_lock); - bcopy(&otr->otr_answer, &entry->ote_dest, + bcopy(&otr->otr_answer, &entry->ote_entry, sizeof (overlay_target_point_t)); entry->ote_flags &= ~OVERLAY_ENTRY_F_PENDING; entry->ote_flags |= OVERLAY_ENTRY_F_VALID; @@ -682,6 +848,19 @@ overlay_target_lookup_respond(overlay_target_hdl_t *thdl, void *arg) entry->ote_ctail = NULL; entry->ote_mbsize = 0; entry->ote_vtime = gethrtime(); + + if (OTE_IS_L3(entry)) { + DTRACE_PROBE2(__overlay_vl3__lookup__resp, + uintptr_t, otr->otr_reqid, + uint8_t *, entry->ote_entry.otp_mac); + } else { + uint16_t port = ntohs(entry->ote_entry.otp_port); + + DTRACE_PROBE3(__overlay_vl2__lookup__resp, + uintptr_t, otr->otr_reqid, + struct in6_addr *, &entry->ote_entry.otp_ip, + uint16_t, port); + } mutex_exit(&entry->ote_lock); /* @@ -749,6 +928,8 @@ overlay_target_lookup_drop(overlay_target_hdl_t *thdl, void *arg) if (queue == B_TRUE) overlay_target_queue(entry); + + OVERLAY_FREEMSG(mp, "received OVERLAY_TARGET_DROP ioctl"); freemsg(mp); done: @@ -1082,32 +1263,55 @@ overlay_target_cache_get(overlay_target_hdl_t *thdl, void *arg) bcopy(&ott->ott_u.ott_point, &otc->otc_entry.otce_dest, sizeof (overlay_target_point_t)); } else { + qqcache_t *cache; overlay_target_entry_t *ote; - ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, - otc->otc_entry.otce_mac); - if (ote != NULL) { - mutex_enter(&ote->ote_lock); - if ((ote->ote_flags & - OVERLAY_ENTRY_F_VALID_MASK) != 0) { - if (ote->ote_flags & OVERLAY_ENTRY_F_DROP) { - otc->otc_entry.otce_flags = - OVERLAY_TARGET_CACHE_DROP; - } else { - otc->otc_entry.otce_flags = 0; - bcopy(&ote->ote_dest, - &otc->otc_entry.otce_dest, - sizeof (overlay_target_point_t)); - } - ret = 0; - } else { - ret = ENOENT; - } - mutex_exit(&ote->ote_lock); + const void *key; + + if (OTCE_IS_L3(&otc->otc_entry)) { + cache = ott->ott_u.ott_dyn.ott_l3cache; + key = &otc->otc_entry.otce_dest.otp_ip; } else { + cache = ott->ott_u.ott_dyn.ott_cache; + key = otc->otc_entry.otce_mac; + } + + ote = qqcache_lookup(cache, key); + + if (ote == NULL) { ret = ENOENT; + goto done; + } + + mutex_enter(&ote->ote_lock); + + if (OTCE_IS_L3(&otc->otc_entry)) + VERIFY(OTE_IS_L3(ote)); + else + VERIFY(!OTE_IS_L3(ote)); + + if ((ote->ote_flags & OVERLAY_ENTRY_F_VALID_MASK) == 0) { + mutex_exit(&ote->ote_lock); + ret = ENOENT; + goto done; } + + /* Set the flags early in case the entry is marked 'drop' */ + otc->otc_entry.otce_flags = 0; + if (OTE_IS_L3(ote)) + otc->otc_entry.otce_flags |= OVERLAY_TARGET_CACHE_L3; + + if (ote->ote_flags & OVERLAY_ENTRY_F_DROP) { + mutex_exit(&ote->ote_lock); + otc->otc_entry.otce_flags |= OVERLAY_TARGET_CACHE_DROP; + goto done; + } + + bcopy(&ote->ote_entry, &otc->otc_entry.otce_dest, + sizeof (overlay_target_point_t)); + mutex_exit(&ote->ote_lock); } +done: mutex_exit(&ott->ott_lock); overlay_hold_rele(odd); @@ -1146,17 +1350,23 @@ overlay_target_cache_set(overlay_target_hdl_t *thdl, void *arg) mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); - ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, + ote = qqcache_lookup(ott->ott_u.ott_dyn.ott_cache, otc->otc_entry.otce_mac); if (ote == NULL) { ote = kmem_cache_alloc(overlay_entry_cache, KM_SLEEP); - bcopy(otc->otc_entry.otce_mac, ote->ote_addr, ETHERADDRL); + if (OTCE_IS_L3(&otc->otc_entry)) { + bcopy(&otc->otc_entry.otce_dest.otp_ip, + &ote->ote_key.otk_ip, sizeof (struct in6_addr)); + } else { + bcopy(otc->otc_entry.otce_mac, ote->ote_key.otk_mac, + ETHERADDRL); + } ote->ote_chead = ote->ote_ctail = NULL; ote->ote_mbsize = 0; ote->ote_ott = ott; ote->ote_odd = odd; mutex_enter(&ote->ote_lock); - refhash_insert(ott->ott_u.ott_dyn.ott_dhash, ote); + qqcache_insert(ott->ott_u.ott_dyn.ott_cache, ote); avl_add(&ott->ott_u.ott_dyn.ott_tree, ote); } else { mutex_enter(&ote->ote_lock); @@ -1166,7 +1376,7 @@ overlay_target_cache_set(overlay_target_hdl_t *thdl, void *arg) ote->ote_flags |= OVERLAY_ENTRY_F_DROP; } else { ote->ote_flags |= OVERLAY_ENTRY_F_VALID; - bcopy(&otc->otc_entry.otce_dest, &ote->ote_dest, + bcopy(&otc->otc_entry.otce_dest, &ote->ote_entry, sizeof (overlay_target_point_t)); mp = ote->ote_chead; ote->ote_chead = NULL; @@ -1197,6 +1407,8 @@ overlay_target_cache_remove(overlay_target_hdl_t *thdl, void *arg) overlay_target_t *ott; overlay_target_entry_t *ote; overlay_targ_cache_t *otc = arg; + qqcache_t *cache; + const void *key; odd = overlay_hold_by_dlid(otc->otc_linkid); if (odd == NULL) @@ -1217,8 +1429,15 @@ overlay_target_cache_remove(overlay_target_hdl_t *thdl, void *arg) mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); - ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, - otc->otc_entry.otce_mac); + if (OTCE_IS_L3(&otc->otc_entry)) { + cache = ott->ott_u.ott_dyn.ott_l3cache; + key = &otc->otc_entry.otce_dest.otp_ip; + } else { + cache = ott->ott_u.ott_dyn.ott_cache; + key = otc->otc_entry.otce_mac; + } + + ote = qqcache_lookup(cache, key); if (ote != NULL) { mutex_enter(&ote->ote_lock); ote->ote_flags &= ~OVERLAY_ENTRY_F_VALID_MASK; @@ -1234,14 +1453,24 @@ overlay_target_cache_remove(overlay_target_hdl_t *thdl, void *arg) return (ret); } +static void +overlay_target_cache_flush_avl(avl_tree_t *avl) +{ + overlay_target_entry_t *ote; + + for (ote = avl_first(avl); ote != NULL; ote = AVL_NEXT(avl, ote)) { + mutex_enter(&ote->ote_lock); + ote->ote_flags &= ~OVERLAY_ENTRY_F_VALID_MASK; + mutex_exit(&ote->ote_lock); + } +} + /* ARGSUSED */ static int overlay_target_cache_flush(overlay_target_hdl_t *thdl, void *arg) { - avl_tree_t *avl; overlay_dev_t *odd; overlay_target_t *ott; - overlay_target_entry_t *ote; overlay_targ_cache_t *otc = arg; odd = overlay_hold_by_dlid(otc->otc_linkid); @@ -1262,15 +1491,9 @@ overlay_target_cache_flush(overlay_target_hdl_t *thdl, void *arg) } mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); - avl = &ott->ott_u.ott_dyn.ott_tree; - for (ote = avl_first(avl); ote != NULL; ote = AVL_NEXT(avl, ote)) { - mutex_enter(&ote->ote_lock); - ote->ote_flags &= ~OVERLAY_ENTRY_F_VALID_MASK; - mutex_exit(&ote->ote_lock); - } - ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, - otc->otc_entry.otce_mac); + overlay_target_cache_flush_avl(&ott->ott_u.ott_dyn.ott_tree); + overlay_target_cache_flush_avl(&ott->ott_u.ott_dyn.ott_l3tree); mutex_exit(&ott->ott_lock); overlay_hold_rele(odd); @@ -1304,10 +1527,17 @@ overlay_target_cache_iter_copyin(const void *ubuf, void **outp, size_t *bsize, } typedef struct overlay_targ_cache_marker { - uint8_t otcm_mac[ETHERADDRL]; - uint16_t otcm_done; + overlay_target_key_t otcm_key; + uint8_t otcm_done; + uint8_t otcm_l3; } overlay_targ_cache_marker_t; +/* cstyle can't decide if this should be 4 spaces or 1 tab */ +/* BEGIN CSTYLED */ +CTASSERT(sizeof (overlay_targ_cache_marker_t) == + (OVERLAY_TARG_NMARKER * sizeof (uint32_t))); +/* END CSTYLED */ + /* ARGSUSED */ static int overlay_target_cache_iter(overlay_target_hdl_t *thdl, void *arg) @@ -1364,19 +1594,33 @@ overlay_target_cache_iter(overlay_target_hdl_t *thdl, void *arg) mark->otcm_done = 1; } - avl = &ott->ott_u.ott_dyn.ott_tree; - bcopy(mark->otcm_mac, lookup.ote_addr, ETHERADDRL); +again: + if (mark->otcm_l3) { + avl = &ott->ott_u.ott_dyn.ott_l3tree; + bcopy(&mark->otcm_key.otk_ip, &lookup.ote_key.otk_ip, + sizeof (struct in6_addr)); + } else { + avl = &ott->ott_u.ott_dyn.ott_tree; + bcopy(mark->otcm_key.otk_mac, lookup.ote_key.otk_mac, + ETHERADDRL); + } ent = avl_find(avl, &lookup, &where); /* * NULL ent means that the entry does not exist, so we want to start * with the closest node in the tree. This means that we implicitly rely * on the tree's order and the first node will be the mac 00:00:00:00:00 - * and the last will be ff:ff:ff:ff:ff:ff. + * and the last will be ff:ff:ff:ff:ff:ff. For the L3 tree, we + * similarly rely on the first node would be ::0 and the last node + * would be ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff. */ if (ent == NULL) { ent = avl_nearest(avl, where, AVL_AFTER); if (ent == NULL) { + if (!mark->otcm_l3) { + mark->otcm_l3 = 1; + goto again; + } mark->otcm_done = 1; goto done; } @@ -1390,20 +1634,37 @@ overlay_target_cache_iter(overlay_target_hdl_t *thdl, void *arg) mutex_exit(&ent->ote_lock); continue; } - bcopy(ent->ote_addr, out->otce_mac, ETHERADDRL); out->otce_flags = 0; + if (OTE_IS_L3(ent)) { + out->otce_flags |= OVERLAY_TARGET_CACHE_L3; + bcopy(&ent->ote_key.otk_ip, &out->otce_dest.otp_ip, + sizeof (struct in6_addr)); + } else { + bcopy(ent->ote_key.otk_mac, out->otce_mac, ETHERADDRL); + } if (ent->ote_flags & OVERLAY_ENTRY_F_DROP) out->otce_flags |= OVERLAY_TARGET_CACHE_DROP; - if (ent->ote_flags & OVERLAY_ENTRY_F_VALID) - bcopy(&ent->ote_dest, &out->otce_dest, + if (ent->ote_flags & OVERLAY_ENTRY_F_VALID) { + bcopy(&ent->ote_entry, &out->otce_dest, sizeof (overlay_target_point_t)); + } written++; mutex_exit(&ent->ote_lock); } if (ent != NULL) { - bcopy(ent->ote_addr, mark->otcm_mac, ETHERADDRL); + if (OTE_IS_L3(ent)) { + bcopy(&ent->ote_key.otk_ip, &mark->otcm_key.otk_ip, + sizeof (struct in6_addr)); + } else { + bcopy(ent->ote_key.otk_mac, mark->otcm_key.otk_mac, + ETHERADDRL); + } } else { + if (!mark->otcm_l3) { + mark->otcm_l3 = 1; + goto again; + } mark->otcm_done = 1; } @@ -1504,7 +1765,7 @@ overlay_target_open(dev_t *devp, int flags, int otype, cred_t *credp) if (secpolicy_dl_config(credp) != 0) return (EPERM); - if (getminor(*devp) != 0) + if (getminor(*devp) != OVERLAY_TARGET_MINOR) return (ENXIO); if (otype & OTYP_BLK) @@ -1562,7 +1823,7 @@ int overlay_target_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { - overlay_target_ioctl_t *ioc; + overlay_target_ioctl_t *ioc = overlay_target_ioctab; overlay_target_hdl_t *thdl; if (secpolicy_dl_config(credp) != 0) @@ -1572,7 +1833,7 @@ overlay_target_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, getminor(dev))) == NULL) return (ENXIO); - for (ioc = &overlay_target_ioctab[0]; ioc->oti_cmd != 0; ioc++) { + for (; ioc->oti_cmd != 0; ioc++) { int ret; caddr_t buf; size_t bufsize; @@ -1649,3 +1910,396 @@ overlay_target_close(dev_t dev, int flags, int otype, cred_t *credp) return (0); } + +/* + * Return a pointer to the byte offset 'offset' within mp, traversing any + * b_cont fragments as necessary. Sets remaining bytes in the mblk_t containing + * 'offset' in *lenp. If offset is past the end of the packet, returns NULL. + */ +static void * +overlay_mblk_offset(mblk_t **restrict mpp, size_t offset, size_t *restrict lenp) +{ + mblk_t *mp = *mpp; + size_t len = MBLKL(mp); + + while (offset >= len) { + offset -= len; + + if ((mp = mp->b_cont) == NULL) + return (NULL); + len = MBLKL(mp); + } + + *lenp = len - offset; + *mpp = mp; + return (mp->b_rptr + offset); +} + +typedef enum overlay_ip6_res { + OIP6_OK, + OIP6_FRAGMENT, + OIP6_TRUNCATED, + OIP6_PULLUP +} overlay_ip6_res_t; + +/* + * Locate the L4 header in an IPv6 packet. mpp is the ptr to the address of + * the mblk_t that contains the fixed portion of the IPv6 header, hdrp points + * to the start of the IPv6 header (within *mpp). + * + * *lenp is set to the length of the remaining data in the mblk_t containing + * the start of the L4 header. *protop is set to the L4 protocol (TCP, UDP, + * etc.). + */ +static overlay_ip6_res_t +overlay_ip6_l4(mblk_t **mpp, unsigned char *hdrp, size_t *restrict lenp, + uint8_t *restrict protop, unsigned char **l4hdrpp) +{ + ip6_t *ip6; + struct ip6_opt *ip6_opt; + size_t offset; + uint8_t len; + uint8_t opt_type; + + ip6 = (ip6_t *)hdrp; + offset = hdrp - (*mpp)->b_rptr; + opt_type = ip6->ip6_nxt; + len = sizeof (ip6_t); + + /* + * IPv6 commingles IPv6 options and L4 protocols (they share the + * same ID space). + */ + while (opt_type != IPPROTO_NONE) { + switch (opt_type) { + case IPPROTO_TCP: + case IPPROTO_UDP: + case IPPROTO_ICMPV6: + goto done; + case IPPROTO_FRAGMENT: + /* Punt on fragments */ + return (OIP6_FRAGMENT); + case IP6OPT_PAD1: + offset++; + break; + default: + /* + * The length of an IPv6 option does not include + * the type byte header, so we must include that to + * skip past the option + */ + offset += 2 + len; + break; + } + + /* + * The IPv6 option header consists of two 8-bit values + * (type, length), so ip6_opt can be safely dereferenced + * without alignment concerns, however we do require the + * option header is not split across mblk_ts. + */ + ip6_opt = overlay_mblk_offset(mpp, offset, lenp); + if (ip6_opt == NULL) + return (OIP6_TRUNCATED); + if (*lenp < 2) + return (OIP6_PULLUP); + + opt_type = ip6_opt->ip6o_type; + len = ip6_opt->ip6o_len; + } + +done: + *protop = opt_type; + if (opt_type == IPPROTO_NONE) + return (OIP6_TRUNCATED); + + *l4hdrpp = overlay_mblk_offset(mpp, offset, lenp); + if (*l4hdrpp == NULL) + return (OIP6_TRUNCATED); + + return (OIP6_OK); +} + +/* + * This fills out 'op' with the data in 'orig_mp'. + * + * On success, 0 is returned and op->op_mblk will point to the corresponding + * mblk_t. If msgpullup() had to be used on 'mp', 'orig_mp' will be freed and + * op->op_mblk will point to the pulled-up copy of orig_mp. + * + * On failure, a non-zero value is returned and 'orig_mp' is not modified. + * Additionally, *reasonp is set to a static string describing the reason + * for the failure (primairly for use for dtrace probes). + */ +int +overlay_pkt_init(overlay_pkt_t *restrict op, mac_handle_t restrict mh, + mblk_t *restrict orig_mp, const char **reasonp) +{ + mblk_t *first_mp = orig_mp; + mblk_t *mp; + size_t len, l3_len, l4_len; + int ret = 0; + boolean_t looped = B_FALSE; + + *reasonp = NULL; + +again: + mp = first_mp; + + /* + * If we ever msgpullup(), we should only need to attempt this + * a second time. + */ + VERIFY(!looped); + + bzero(op, sizeof (*op)); + + if ((ret = mac_vlan_header_info(mh, mp, &op->op_mhi)) != 0) + return (ret); + + /* + * Sanitize the mblk. If any of L2, L3, or L4 headers are split + * mid-header, we msgpullup() the whole thing and use that instead, + * otherwise cache the start of each header. + */ + len = MBLKL(mp); + + if (op->op_mhi.mhi_hdrsize > len) { + OVERLAY_PULLUPMSG(orig_mp, "split ethernet header"); + if ((first_mp = msgpullup(orig_mp, -1)) == NULL) { + *reasonp = "msgpullup failed"; + return (ENOMEM); + } + + looped = B_TRUE; + goto again; + } + + /* Set the L3 header address */ + op->op3_u.op3_char = overlay_mblk_offset(&mp, + op->op_mhi.mhi_hdrsize, &len); + + /* + * Verify the remaining length in the mblk_t that contains the start + * of the L3 header doesn't split it across mblk_ts. + */ + switch (OPKT_ETYPE(op)) { + case ETHERTYPE_IP: + l3_len = sizeof (ipha_t); + break; + case ETHERTYPE_IPV6: + l3_len = sizeof (ip6_t); + break; + case ETHERTYPE_ARP: + l3_len = 28; + break; + default: + /* + * For any other types, we don't bother checking for any + * further segmentation of the data. + */ + goto done; + } + + if (len < l3_len) { + OVERLAY_PULLUPMSG(orig_mp, "L3 header is split"); + if ((first_mp = msgpullup(orig_mp, -1)) == NULL) { + *reasonp = "msgpullup failed"; + return (ENOMEM); + } + + looped = B_TRUE; + goto again; + } + + /* + * We started with l3_len as the size of the fixed portion of the + * IPv4/IPv6 header. Now that we're sure the fixed portion isn't + * split across mblk_ts, we reset it to the total length of the + * IPv4/IPv6 header (i.e. including options) to determine the + * start of the L4 header. + * + * Also copy in the source and destination addresses. + */ + switch (op->op_mhi.mhi_bindsap) { + case ETHERTYPE_IP: + l3_len = IPH_HDR_LENGTH(op->op3_u.op3_ipv4); + + /* + * Sanity check the length. It must be within the min and max + * values, but also, it cannot be greater then the total + * size of the IPv4 header + IP payload. Upper layer will + * check this as well, be we need to do it just so we + * don't try to interpret the TCP/UDP header at the + * wrong location. + */ + if (l3_len < IP_SIMPLE_HDR_LENGTH || + l3_len > IP_MAX_HDR_LENGTH || + l3_len > ntohs(op->op3_u.op3_ipv4->ipha_length)) { + *reasonp = "IP header length invalid"; + ret = EINVAL; + goto done; + } + + op->op_l4proto = op->op3_u.op3_ipv4->ipha_protocol; + op->op4_u.op4_char = overlay_mblk_offset(&mp, + op->op_mhi.mhi_hdrsize + l3_len, &len); + + IN6_IPADDR_TO_V4MAPPED(op->op3_u.op3_ipv4->ipha_dst, + &op->op_dstaddr); + IN6_IPADDR_TO_V4MAPPED(op->op3_u.op3_ipv4->ipha_src, + &op->op_srcaddr); + break; + case ETHERTYPE_IPV6: + /* + * overlay_ip6_l4() also sets op->op4_u.op4_char for us + * after skipping over any options. + */ + switch (overlay_ip6_l4(&mp, op->op3_u.op3_char, &len, + &op->op_l4proto, &op->op4_u.op4_char)) { + case OIP6_OK: + break; + case OIP6_FRAGMENT: + *reasonp = "fragmented IPv6 packet"; + ret = EBADMSG; + goto done; + case OIP6_TRUNCATED: + *reasonp = "truncated IPv6 packet"; + ret = EINVAL; + goto done; + case OIP6_PULLUP: + if ((first_mp = msgpullup(orig_mp, -1)) == NULL) { + *reasonp = "msgpullup failed"; + ret = ENOMEM; + goto done; + } + + looped = B_TRUE; + goto again; + } + + bcopy(&op->op3_u.op3_ipv6->ip6_dst, &op->op_dstaddr, + sizeof (struct in6_addr)); + bcopy(&op->op3_u.op3_ipv6->ip6_src, &op->op_srcaddr, + sizeof (struct in6_addr)); + break; + case ETHERTYPE_ARP: + /* There is no L4 with ARP packets */ + if (len < 28) { + *reasonp = "truncated ARP packet"; + ret = EINVAL; + goto done; + } + + /* We don't need to do anything else for ARP packets */ + goto done; + } + + switch (op->op_l4proto) { + case IPPROTO_TCP: + l4_len = sizeof (struct tcphdra_s); + break; + case IPPROTO_UDP: + l4_len = sizeof (struct udphdr); + break; + case IPPROTO_ICMP: + if (op->op_mhi.mhi_bindsap != ETHERTYPE_IP) { + *reasonp = "ICMP in non-IP packet"; + ret = EINVAL; + goto done; + } + l4_len = ICMPH_SIZE; + break; + case IPPROTO_ICMPV6: + if (op->op_mhi.mhi_bindsap != ETHERTYPE_IPV6) { + *reasonp = "ICMPv6 in non-IPv6 packet"; + ret = EINVAL; + goto done; + } + l4_len = ICMP6_MINLEN; + break; + default: + l4_len = 0; + } + + if (len < l4_len) { + OVERLAY_PULLUPMSG(orig_mp, "L4 header is split"); + if ((first_mp = msgpullup(orig_mp, -1)) == NULL) { + *reasonp = "msgpullup failed"; + ret = ENOMEM; + goto done; + } + + looped = B_TRUE; + goto again; + } + + /* Now set the src/dst port */ + switch (op->op_l4proto) { + case IPPROTO_TCP: + op->op_srcport = ntohs(op->op4_u.op4_tcp->tha_lport); + op->op_dstport = ntohs(op->op4_u.op4_tcp->tha_fport); + + /* Whatever is left in this mblk + any remaining fragments */ + op->op_l4len = len + msgdsize(mp->b_cont); + break; + case IPPROTO_UDP: + op->op_srcport = ntohs(op->op4_u.op4_udp->uh_sport); + op->op_dstport = ntohs(op->op4_u.op4_udp->uh_dport); + + /* Whatever is left in this mblk + any remaining fragments */ + op->op_l4len = len + msgdsize(mp->b_cont); + break; + case IPPROTO_ICMPV6: + /* + * For ICMPv6 packets, we require the entire ICMPv6 payload to + * be contiguous. This is largely to simplify neighbor + * discovery handling for the router IP. + */ + if (mp->b_cont != NULL) { + OVERLAY_PULLUPMSG(orig_mp, "ICMPv6 pkt is split"); + if ((first_mp = msgpullup(orig_mp, -1)) == NULL) { + *reasonp = "msgpullup failed"; + ret = ENOMEM; + goto done; + } + + looped = B_TRUE; + goto again; + } + + /* + * From the above contiguous requirement, this means len + * must be the size of the ICMPv6 data (including header). + */ + op->op_l4len = len; + break; + } + +done: + if (first_mp != orig_mp) { + if (ret == 0) { + /* + * We had to do a msgpullup(), but were otherwise + * successful. We free the original mblk_t, and + * use our pulled-up mblk (first_mp). + */ + OVERLAY_FREEMSG(orig_mp, + "freeing original mblk after pullup"); + freemsg(orig_mp); + } else if (first_mp != NULL) { + /* + * Even after doing a msgpullup(), there was some + * other problem. We're returning an error so + * dispose of our pulled-up msg and leave the + * original untouched. + */ + freemsg(first_mp); + } + } + + if (ret == 0) + op->op_mblk = first_mp; + + return (ret); +} diff --git a/usr/src/uts/common/qqcache/qqcache.c b/usr/src/uts/common/qqcache/qqcache.c new file mode 100644 index 000000000000..0bf555567179 --- /dev/null +++ b/usr/src/uts/common/qqcache/qqcache.c @@ -0,0 +1,471 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * Currently, the non _KERNEL pieces are to support testing in usr/src/test. + */ +#ifdef _KERNEL +#include +#define ZALLOC kmem_zalloc +#define FREE kmem_free +#else +#include +#define ZALLOC umem_zalloc +#define FREE umem_free +#endif + + +/* + * The *_overflow functions mimic the gcc/clang intrinsic functions. Once + * we are using a newer compiler version to that includes these as intrisnics, + * these can be replaced with those versions. + */ +static int +uadd_overflow(const size_t a, const size_t b, size_t *sump) +{ + *sump = a + b; + if (*sump < a || *sump < b) + return (1); + return (0); +} + +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof (size_t) * 4)) + +static int +umul_overflow(const size_t a, const size_t b, size_t *cp) +{ + *cp = a * b; + + if ((a >= MUL_NO_OVERFLOW || b >= MUL_NO_OVERFLOW) && + a != 0 && b != 0 && SIZE_MAX / a < b) + return (1); + + return (0); +} + +/* Calculate the capacity of each list based on sz and a */ +static void +qqcache_size_lists(size_t sz, size_t a, size_t *maxp) +{ + VERIFY3U(sz, >=, QQCACHE_NUM_LISTS); + + /* + * The general approach is to start with list 0 being sized as a% of + * sz. However every other list must be able to hold at least one + * entry unless a == 100 (i.e. 100%). If the straight percentage + * leaves any of the remaining lists with zero entries, we give them + * a size of 1, and then adjust list0's size according so that the + * sum off all list sizes == sz (this is mostly only a concern where + * sz is small enough such that (100 - a)% of sz < QQCACHE_NUM_LISTS). + */ + size_t list0sz = sz * a / 100; + size_t othersz = (sz - list0sz) / (QQCACHE_NUM_LISTS - 1); + + if (list0sz == 0) + list0sz = 1; + + if (othersz == 0 && a != 100) + othersz = 1; + + if (list0sz + othersz * (QQCACHE_NUM_LISTS - 1) > sz) + list0sz = sz - othersz * (QQCACHE_NUM_LISTS - 1); + + maxp[0] = list0sz; + for (size_t i = 1; i < QQCACHE_NUM_LISTS; i++) + maxp[i] = othersz; +} + +int +qqcache_create(qqcache_t **qp, size_t sz, size_t a, size_t buckets, + qqcache_hash_fn_t hash_fn, qqcache_cmp_fn_t cmp_fn, + qqcache_dtor_fn_t dtor_fn, size_t elsize, size_t link_off, size_t tag_off, + int kmflags) +{ + qqcache_t *qc; + size_t len = 0; + + if (sz < QQCACHE_MIN_SIZE) + return (EINVAL); + if (a > 100) + return (EINVAL); + + if (umul_overflow(sizeof (qqcache_list_t), buckets, &len)) + return (EINVAL); + if (uadd_overflow(sizeof (*qc), len, &len)) + return (EINVAL); + + if ((qc = ZALLOC(len, kmflags)) == NULL) + return (ENOMEM); + + qc->qqc_hash_fn = hash_fn; + qc->qqc_cmp_fn = cmp_fn; + qc->qqc_dtor_fn = dtor_fn; + qc->qqc_link_off = link_off; + qc->qqc_tag_off = tag_off; + qc->qqc_nbuckets = buckets; + qc->qqc_size = sz; + qc->qqc_a = a; + + qqcache_size_lists(sz, a, qc->qqc_max); + + for (size_t i = 0; i < buckets; i++) { + list_create(&qc->qqc_buckets[i].qqcl_list, elsize, + offsetof(qqcache_link_t, qqln_hash_link)); + } + + for (size_t i = 0; i < QQCACHE_NUM_LISTS; i++) { + list_create(&qc->qqc_lists[i].qqcl_list, elsize, + offsetof(qqcache_link_t, qqln_list_link)); + } + + *qp = qc; + return (0); +} + +void +qqcache_destroy(qqcache_t *qc) +{ + size_t len; + + if (qc == NULL) + return; + + /* If creation succeeded, this calculation cannot overflow */ + len = sizeof (*qc) + qc->qqc_nbuckets * sizeof (qqcache_list_t); + + for (size_t i = 0; i < QQCACHE_NUM_LISTS; i++) { + list_t *l = &qc->qqc_lists[i].qqcl_list; + qqcache_link_t *lnk; + + while ((lnk = list_remove_head(l)) != NULL) + ; + } + + for (size_t i = 0; i < qc->qqc_nbuckets; i++) { + list_t *l = &qc->qqc_buckets[i].qqcl_list; + qqcache_link_t *lnk; + + while ((lnk = list_remove_head(l)) != NULL) { + ASSERT0(lnk->qqln_refcnt); + qc->qqc_dtor_fn(link_to_obj(qc, lnk)); + } + } + + FREE(qc, len); +} + +/* + * Removal of an entry is a two step process. qqcache_remove() removes the + * entry from the cache lists, and if a reference is held, sets the + * QQCACHE_F_DEAD flag. When there are no more references held on an entry, + * (either none are held at the time qqcache_remove() is called, or the last + * reference is removed via qqcache_rele(), qqcache_delete() is called which + * removes the entry from its hash bucket and calls the entry's dtor function. + * + * The main reason for the two step process is largely simplicity. If the + * entry remains in the cache lists w/ the QQCACHE_F_DEAD flag set, it + * complicates keeping each cache within its size limits -- either the + * list size must reflect the number of non-dead entries (which could be + * confusing during troubleshooting), or as we push things down the list, we + * would need to skip/ignore dead entries. The hash buckets however don't + * have any size limits (to impose limits would require the hash function + * provided by the consumer to produce perfectly equal distribution of entries + * across all the hash buckets at all times). The only time we care about + * the QQCACHE_F_DEAD flag in the hash buckets is when trying to lookup a + * 'dead' value, so leaving the entries in there does not present the same + * issues as leaving them in the hash buckets (while still providing a way to + * find refheld entries). + */ +static void +qqcache_delete(qqcache_t *qc, qqcache_link_t *lp) +{ + void *op = link_to_obj(qc, lp); + void *tp = obj_to_tag(qc, op); + uint_t n = qc->qqc_hash_fn(tp) % qc->qqc_nbuckets; + + ASSERT3U(qc->qqc_buckets[n].qqcl_len, >, 0); + ASSERT(!list_is_empty(&qc->qqc_buckets[n].qqcl_list)); + ASSERT(!list_link_active(&lp->qqln_list_link)); + ASSERT(list_link_active(&lp->qqln_hash_link)); + + list_remove(&qc->qqc_buckets[n].qqcl_list, lp); + qc->qqc_buckets[n].qqcl_len--; + qc->qqc_dtor_fn(op); +} + +void +qqcache_remove(qqcache_t *qc, void *op) +{ + qqcache_link_t *lp = obj_to_link(qc, op); + qqcache_list_t *lst = QQCACHE_LIST(qc, lp); + + ASSERT(!list_is_empty(&lst->qqcl_list)); + ASSERT3U(lst->qqcl_len, >, 0); + + list_remove(&lst->qqcl_list, lp); + lst->qqcl_len--; + + if (lp->qqln_refcnt > 0) + lp->qqln_flags |= QQCACHE_F_DEAD; + else + qqcache_delete(qc, lp); +} + +void +qqcache_hold(qqcache_t *qc, void *op) +{ + qqcache_link_t *lp = obj_to_link(qc, op); + + ++lp->qqln_refcnt; +} + +void +qqcache_rele(qqcache_t *qc, void *op) +{ + qqcache_link_t *lp = obj_to_link(qc, op); + + VERIFY3U(lp->qqln_refcnt, >, 0); + + if (--lp->qqln_refcnt == 0 && (lp->qqln_flags & QQCACHE_F_DEAD)) + qqcache_delete(qc, lp); +} + +static qqcache_link_t * +qqcache_hash_lookup(qqcache_t *qc, const void *tp, qqcache_list_t **lpp) +{ + uint_t n = qc->qqc_hash_fn(tp) % qc->qqc_nbuckets; + qqcache_link_t *lp; + qqcache_list_t *bucket = &qc->qqc_buckets[n]; + list_t *l = &bucket->qqcl_list; + void *cmp; + + if (lpp != NULL) + *lpp = bucket; + + for (lp = list_head(l); lp != NULL; lp = list_next(l, lp)) { + cmp = obj_to_tag(qc, link_to_obj(qc, lp)); + + if (qc->qqc_cmp_fn(cmp, tp) == 0 && + !(lp->qqln_flags & QQCACHE_F_DEAD)) { + return (lp); + } + } + + return (NULL); +} + +/* + * Starting at listnum, push entries from the tail of cache list 'n' to the + * head of * list 'n + 1', keeping each list within their size limits. Excess + * entries on the tail of the last list are deleted. If 'for_insert' is + * B_TRUE, also guarantee after this returns that there are no more than + * 'max - 1' entries on listnum (so there is room to insert an entry onto + * listnum). + */ +static void +qqcache_ripple(qqcache_t *qc, uint_t listnum, boolean_t for_insert) +{ + VERIFY3U(listnum, <, QQCACHE_NUM_LISTS); + + for (uint_t i = listnum; i < QQCACHE_NUM_LISTS; i++) { + qqcache_list_t *ql = &qc->qqc_lists[i]; + qqcache_list_t *qlnext = &qc->qqc_lists[i + 1]; + size_t max = qc->qqc_max[i]; + + ASSERT3U(max, >, 0); + + /* + * If we're planning to insert an entry on list 'listnum', + * we bump the maximum size down by one to guarantee we + * have sufficient room for the entry + */ + if (for_insert && i == listnum) + max--; + + while (ql->qqcl_len > max) { + qqcache_link_t *lnk = list_tail(&ql->qqcl_list); + + if (i + 1 < QQCACHE_NUM_LISTS) { + list_remove(&ql->qqcl_list, lnk); + ql->qqcl_len--; + + ASSERT3U(lnk->qqln_listnum, ==, i); + lnk->qqln_listnum++; + + list_insert_head(&qlnext->qqcl_list, lnk); + qlnext->qqcl_len++; + } else { + qqcache_remove(qc, link_to_obj(qc, lnk)); + } + } + } +} + +int +qqcache_insert(qqcache_t *qc, void *obj) +{ + qqcache_link_t *lp = obj_to_link(qc, obj); + qqcache_list_t *bucket; + + if (qqcache_hash_lookup(qc, obj_to_tag(qc, obj), &bucket) != NULL) + return (EEXIST); + + list_link_init(&lp->qqln_hash_link); + list_link_init(&lp->qqln_list_link); + lp->qqln_refcnt = 0; + lp->qqln_flags = 0; + lp->qqln_listnum = QQCACHE_INSERT_LIST; + + qqcache_ripple(qc, QQCACHE_INSERT_LIST, B_TRUE); + + list_insert_tail(&bucket->qqcl_list, lp); + bucket->qqcl_len++; + + list_insert_head(&qc->qqc_lists[QQCACHE_INSERT_LIST].qqcl_list, lp); + qc->qqc_lists[QQCACHE_INSERT_LIST].qqcl_len++; + + return (0); +} + +void * +qqcache_lookup(qqcache_t *qc, const void *tp) +{ + qqcache_link_t *lp; + qqcache_list_t *src; + uint_t tgtnum; + + qc->qqc_lookups++; + + if ((lp = qqcache_hash_lookup(qc, tp, NULL)) == NULL) + return (NULL); + + qc->qqc_hits++; + + src = QQCACHE_LIST(qc, lp); + list_remove(&src->qqcl_list, lp); + src->qqcl_len--; + + tgtnum = (lp->qqln_listnum > 0) ? lp->qqln_listnum - 1 : 0; + + if (tgtnum != lp->qqln_listnum) + qqcache_ripple(qc, tgtnum, B_TRUE); + + lp->qqln_listnum = tgtnum; + list_insert_head(&qc->qqc_lists[tgtnum].qqcl_list, lp); + qc->qqc_lists[tgtnum].qqcl_len++; + + return (link_to_obj(qc, lp)); +} + +int +qqcache_adjust_size(qqcache_t *qc, size_t sz) +{ + if (sz < QQCACHE_MIN_SIZE) + return (EINVAL); + + qc->qqc_size = sz; + qqcache_size_lists(sz, qc->qqc_a, qc->qqc_max); + qqcache_ripple(qc, 0, B_FALSE); + return (0); +} + +int +qqcache_adjust_a(qqcache_t *qc, size_t a) +{ + if (a > 100) + return (EINVAL); + + qc->qqc_a = a; + qqcache_size_lists(qc->qqc_size, a, qc->qqc_max); + qqcache_ripple(qc, 0, B_FALSE); + return (0); +} + +size_t +qqcache_size(const qqcache_t *qc) +{ + return (qc->qqc_size); +} + +size_t +qqcache_a(const qqcache_t *qc) +{ + return (qc->qqc_a); +} + +void * +qqcache_first(qqcache_t *qc) +{ + for (size_t i = 0; i < QQCACHE_NUM_LISTS; i++) { + qqcache_list_t *l = &qc->qqc_lists[i]; + + if (l->qqcl_len > 0) + return (link_to_obj(qc, list_head(&l->qqcl_list))); + } + + return (NULL); +} + +void * +qqcache_next(qqcache_t *qc, void *obj) +{ + qqcache_link_t *lp = obj_to_link(qc, obj); + qqcache_link_t *next; + qqcache_list_t *l = QQCACHE_LIST(qc, lp); + + ASSERT3U(lp->qqln_listnum, <, QQCACHE_NUM_LISTS); + + if ((next = list_next(&l->qqcl_list, lp)) != NULL) + return (link_to_obj(qc, next)); + + for (size_t i = lp->qqln_listnum + 1; i < QQCACHE_NUM_LISTS; i++) { + l = &qc->qqc_lists[i]; + if (l->qqcl_len > 0) + return (link_to_obj(qc, list_head(&l->qqcl_list))); + } + + return (NULL); +} + +uint64_t +qqcache_lookups(const qqcache_t *qc) +{ + return (qc->qqc_lookups); +} + +uint64_t +qqcache_hits(const qqcache_t *qc) +{ + return (qc->qqc_hits); +} + +size_t +qqcache_nentries(const qqcache_t *qc) +{ + size_t tot = 0; + + for (size_t i = 0; i < QQCACHE_NUM_LISTS; i++) + tot += qc->qqc_lists[i].qqcl_len; + + return (tot); +} diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile index 24fdd94c1130..b4db87304067 100644 --- a/usr/src/uts/common/sys/Makefile +++ b/usr/src/uts/common/sys/Makefile @@ -21,14 +21,13 @@ # # Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. -# Copyright 2019 Joyent, Inc. +# Copyright 2021 Joyent, Inc. # Copyright 2013 Garrett D'Amore # Copyright 2013 Saso Kiselkov. All rights reserved. # Copyright 2015 Igor Kozhukhov # Copyright 2017 Nexenta Systems, Inc. # Copyright 2016 Hans Rosenfeld # Copyright 2019 Peter Tribble. -# Copyright 2015, Joyent, Inc. All rights reserved. # include $(SRC)/uts/Makefile.uts @@ -440,6 +439,7 @@ CHKHDRS= \ openpromio.h \ overlay.h \ overlay_common.h \ + overlay_router.h \ overlay_target.h \ panic.h \ param.h \ @@ -491,6 +491,8 @@ CHKHDRS= \ ptem.h \ ptms.h \ ptyvar.h \ + qqcache.h \ + qqcache_impl.h \ raidioctl.h \ ramdisk.h \ random.h \ diff --git a/usr/src/uts/common/sys/overlay_impl.h b/usr/src/uts/common/sys/overlay_impl.h index 7fb8b8da1dfd..56a8cdbc2479 100644 --- a/usr/src/uts/common/sys/overlay_impl.h +++ b/usr/src/uts/common/sys/overlay_impl.h @@ -10,7 +10,7 @@ */ /* - * Copyright 2016 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ #ifndef _SYS_OVERLAY_IMPL_H @@ -23,15 +23,17 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include #include +#include #ifdef __cplusplus extern "C" { @@ -59,13 +61,58 @@ typedef struct overlay_mux { int omux_domain; /* RO: socket domain */ int omux_family; /* RO: socket family */ int omux_protocol; /* RO: socket protocol */ - struct sockaddr *omux_addr; /* RO: socket address */ + struct sockaddr *omux_addr; /* RO: socket address */ socklen_t omux_alen; /* RO: sockaddr len */ kmutex_t omux_lock; /* Protects everything below */ uint_t omux_count; /* Active instances */ avl_tree_t omux_devices; /* Tree of devices */ } overlay_mux_t; +typedef struct overlay_routetab { + kmutex_t ort_lock; + list_node_t ort_link; + uint_t ort_refcnt; /* RW */ + overlay_route_ent_t *ort_routev4; /* RW */ + overlay_route_ent_t *ort_routev6; /* RW */ + uint_t ort_nroutev4; /* RW */ + uint_t ort_nroutev6; /* RW */ + uint_t ort_routev4_alloc; /* RW */ + uint_t ort_routev6_alloc; /* RW */ + char ort_id[OVERLAY_ID_MAX]; /* RO */ + boolean_t ort_default; /* RW */ +} overlay_routetab_t; + +typedef struct overlay_net { + kmutex_t ont_lock; + avl_node_t ont_node_vlan; + avl_node_t ont_node_mac; + avl_node_t ont_node_v4; + avl_node_t ont_node_v6; + uint_t ont_refcnt; /* RW */ + in_addr_t ont_net; /* RO */ + in_addr_t ont_routeraddr; /* RO */ + struct in6_addr ont_netv6; /* RO */ + struct in6_addr ont_routeraddrv6; /* RO */ + uint8_t ont_prefixlen; /* RO */ + uint8_t ont_prefixlenv6; /* RO */ + /* vlan is in host byte order */ + uint16_t ont_vlan; /* RO */ + uint8_t ont_mac[ETHERADDRL]; /* RO */ + char ont_id[OVERLAY_ID_MAX]; /* RO */ + overlay_routetab_t *ont_routetbl; /* RW */ +} overlay_net_t; + +typedef struct overlay_router { + kmutex_t orr_lock; + uint_t orr_refcnt; + list_t orr_routetbls; + avl_tree_t orr_nets_vlan; + avl_tree_t orr_nets_mac; + avl_tree_t orr_nets_v4; + avl_tree_t orr_nets_v6; + overlay_routetab_t *orr_def_routetab; +} overlay_router_t; + typedef enum overlay_target_flag { OVERLAY_T_TEARDOWN = 0x1 } overlay_target_flag_t; @@ -81,8 +128,10 @@ typedef struct overlay_target { union { /* ott_lock */ overlay_target_point_t ott_point; struct overlay_target_dyn { - refhash_t *ott_dhash; + qqcache_t *ott_cache; avl_tree_t ott_tree; + qqcache_t *ott_l3cache; + avl_tree_t ott_l3tree; } ott_dyn; } ott_u; } overlay_target_t; @@ -118,35 +167,101 @@ typedef struct overlay_dev { avl_node_t odd_muxnode; /* managed by mux */ overlay_target_t *odd_target; /* See big theory statement */ char odd_fmamsg[OVERLAY_STATUS_BUFLEN]; /* odd_lock */ + uint_t odd_cachesz; + uint_t odd_cachea; + overlay_router_t *odd_router; } overlay_dev_t; typedef enum overlay_target_entry_flags { OVERLAY_ENTRY_F_PENDING = 0x01, /* lookup in progress */ OVERLAY_ENTRY_F_VALID = 0x02, /* entry is currently valid */ OVERLAY_ENTRY_F_DROP = 0x04, /* always drop target */ - OVERLAY_ENTRY_F_VALID_MASK = 0x06 + OVERLAY_ENTRY_F_VALID_MASK = 0x06, + OVERLAY_ENTRY_F_L3 = 0x08, /* is a L3 entry */ } overlay_target_entry_flags_t; +typedef union overlay_target_key { + struct in6_addr otk_ip; + uint8_t otk_mac[ETHERADDRL]; +} overlay_target_key_t; + typedef struct overlay_target_entry { kmutex_t ote_lock; - refhash_link_t ote_reflink; /* hashtable link */ + qqcache_link_t ote_cachelink; /* cache link */ avl_node_t ote_avllink; /* iteration link */ list_node_t ote_qlink; overlay_target_entry_flags_t ote_flags; /* RW: state flags */ - uint8_t ote_addr[ETHERADDRL]; /* RO: mac addr */ overlay_target_t *ote_ott; /* RO */ overlay_dev_t *ote_odd; /* RO */ - overlay_target_point_t ote_dest; /* RW: destination */ + overlay_target_key_t ote_key; /* RO */ + overlay_target_point_t ote_entry; /* RW */ mblk_t *ote_chead; /* RW: blocked mb chain head */ mblk_t *ote_ctail; /* RW: blocked mb chain tail */ size_t ote_mbsize; /* RW: outstanding mblk size */ hrtime_t ote_vtime; /* RW: valid timestamp */ } overlay_target_entry_t; +#define OTE_IS_L3(ote) (((ote)->ote_flags & OVERLAY_ENTRY_F_L3) != 0) + +/* + * When doing any processing on a decapsulated inbound or not yet encapsulated + * outbound packet (e.g. packets that are being routed or NATed), we cache the + * start of the various headers in overlay_pkt_t. + * We also sanitize the mblk_t as necessary -- that is, we don't allow a given + * L2 (ethernet), L3 (IPv4/IPv6), or L4 (TCP/UDP) header to be split across + * multiple mblk_ts. For headers that can have a fixed header followed by a + * variable portion (e.g. IP options), we do relax the contiguous requirement + * to only the fixed portion of the header. + * + * Basically we want to be able to cast the various pointers to the appropriate + * header struct and dereference (modulo any needed byteswapping of values) + * instead of constantly having to worry that a particular header has been + * split across dblk_ts (anything doing that should feel bad). + */ +typedef struct overlay_pkt { + mblk_t *op_mblk; + mac_header_info_t op_mhi; + union { + unsigned char *op3_char; + struct ipha_s *op3_ipv4; + struct ip6_hdr *op3_ipv6; + } op3_u; + union { + unsigned char *op4_char; + struct tcphdra_s *op4_tcp; + struct udphdr *op4_udp; + struct icmph *op4_icmp; + struct icmp6_hdr *op4_icmp6; + } op4_u; + /* + * For IPv4 addresses, the src/dst addressess are stored as V4MAPPED + * addresses in op_{src,dst}addr. + */ + struct in6_addr op_srcaddr; + struct in6_addr op_dstaddr; + uint16_t op_srcport; /* in host byteorder */ + uint16_t op_dstport; /* in host byteorder */ + uint16_t op_l4len; + uint8_t op_l4proto; +} overlay_pkt_t; +#define OPKT_ETYPE(pkt) ((pkt)->op_mhi.mhi_bindsap) +#define OPKT_VLAN(pkt) (VLAN_ID((pkt)->op_mhi.mhi_tci)) -#define OVERLAY_CTL "overlay" +#define OVERLAY_CTL "overlay" +#define OVERLAY_ROUTER_CTL "overlay_router" + +#define OVERLAY_TARGET_MINOR ((minor_t)0) +#define OVERLAY_ROUTER_MINOR ((minor_t)1) +#define OVERLAY_MINOR_START 2 + +#define OVERLAY_FREEMSG(mp, reason) \ + DTRACE_PROBE2(__overlay_freemsg, mblk_t *, mp, const char *, reason) extern dev_info_t *overlay_dip; +extern uint8_t overlay_bcast[ETHERADDRL]; + +extern int overlay_pkt_init(overlay_pkt_t *, mac_handle_t, mblk_t *, + const char **); extern mblk_t *overlay_m_tx(void *, mblk_t *); @@ -185,8 +300,8 @@ extern void overlay_target_free(overlay_dev_t *); #define OVERLAY_TARGET_OK 0 #define OVERLAY_TARGET_DROP 1 #define OVERLAY_TARGET_ASYNC 2 -extern int overlay_target_lookup(overlay_dev_t *, mblk_t *, struct sockaddr *, - socklen_t *); +extern int overlay_target_lookup(overlay_dev_t *, overlay_pkt_t *, boolean_t, + struct sockaddr *, socklen_t *); extern void overlay_target_quiesce(overlay_target_t *); extern void overlay_target_fini(void); @@ -198,6 +313,32 @@ extern void overlay_fm_restore(overlay_dev_t *); extern overlay_dev_t *overlay_hold_by_dlid(datalink_id_t); extern void overlay_hold_rele(overlay_dev_t *); +extern void overlay_router_init(void); +extern void overlay_router_fini(void); + +extern overlay_router_t *overlay_router_create(overlay_dev_t *); +extern void overlay_router_free(overlay_router_t *); +extern boolean_t overlay_router_active(overlay_router_t *); + +extern overlay_net_t *overlay_hold_net_by_vlan(overlay_router_t *, uint16_t); +extern overlay_net_t *overlay_hold_net_by_ip(overlay_router_t *, in_addr_t); +extern overlay_net_t *overlay_hold_net_by_ip6(overlay_router_t *, + const struct in6_addr *); +extern overlay_net_t *overlay_hold_net_by_mac(overlay_router_t *orr, + const uint8_t mac[ETHERADDRL]); +extern void overlay_net_rele(overlay_net_t *); + +extern int overlay_router_open(dev_t *, int, int, cred_t *); +extern int overlay_router_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); +extern int overlay_router_close(dev_t, int, int, cred_t *); + +extern boolean_t overlay_router_arp(overlay_dev_t *, overlay_net_t *, + overlay_pkt_t *); +extern boolean_t overlay_router_ndp(overlay_dev_t *, overlay_net_t *, + overlay_pkt_t *); +extern int overlay_route(overlay_dev_t *, overlay_net_t *, overlay_pkt_t *, + struct sockaddr *, socklen_t *); + #ifdef __cplusplus } #endif diff --git a/usr/src/uts/common/sys/overlay_router.h b/usr/src/uts/common/sys/overlay_router.h new file mode 100644 index 000000000000..489961c28011 --- /dev/null +++ b/usr/src/uts/common/sys/overlay_router.h @@ -0,0 +1,125 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#ifndef _OVERLAY_ROUTER_H +#define _OVERLAY_ROUTER_H + +/* + * Overlay device router ioctl interface (/dev/overlay) + */ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct overlay_router_ioc_hdr { + datalink_id_t orih_linkid; + uint32_t orih_pad[1]; +} overlay_router_ioc_hdr_t; +CTASSERT(sizeof (overlay_router_ioc_hdr_t) == sizeof (uint64_t)); + +/* + * The ID strings need to be large enough to hold a UUID in string form. + * Anyone using non-UUIDs is likely to not need more characters than this, + * so UUID_PRINTABLE_STRING_LENGTH is used as the size (this includes the + * terminating NUL). + */ +#define OVERLAY_ID_MAX UUID_PRINTABLE_STRING_LENGTH + +#define OVERLAY_ROUTER_IOCTL (('o' << 24) | ('v' << 16) | ('r' << 8)) + +#define OVERLAY_ROUTER_NET_CREATE (OVERLAY_ROUTER_IOCTL | 0x10) +#define OVERLAY_ROUTER_NET_DELETE (OVERLAY_ROUTER_IOCTL | 0x11) +#define OVERLAY_ROUTER_NET_DELETE_ALL (OVERLAY_ROUTER_IOCTL | 0x12) +#define OVERLAY_ROUTER_NET_GET (OVERLAY_ROUTER_IOCTL | 0x13) +#define OVERLAY_ROUTER_NET_ITER (OVERLAY_ROUTER_IOCTL | 0x14) +#define OVERLAY_ROUTER_NET_SET_ROUTETBL (OVERLAY_ROUTER_IOCTL | 0x15) + +typedef struct overlay_ioc_net { + overlay_router_ioc_hdr_t oin_hdr; + struct in6_addr oin_routeraddrv6; + in_addr_t oin_routeraddr; + uint8_t oin_prefixlen; + uint8_t oin_prefixlenv6; + uint16_t oin_vlan; /* in host order */ + uint8_t oin_mac[ETHERADDRL]; + char oin_id[OVERLAY_ID_MAX]; + char oin_routetbl[OVERLAY_ID_MAX]; +} overlay_ioc_net_t; + +/* Max # of entries per ioctl() call */ +#define OVERLAY_ROUTER_ITER_MAX UINT16_MAX + +typedef struct overlay_ioc_net_iter { + overlay_router_ioc_hdr_t oini_hdr; + uint64_t oini_marker; + uint16_t oini_count; + uint8_t oini_pad2[3]; + overlay_ioc_net_t oini_ents[]; +} overlay_ioc_net_iter_t; + +#define OVERLAY_ROUTETBL_GET (OVERLAY_ROUTER_IOCTL | 0x20) +#define OVERLAY_ROUTETBL_SET (OVERLAY_ROUTER_IOCTL | 0x21) +#define OVERLAY_ROUTETBL_REMOVE (OVERLAY_ROUTER_IOCTL | 0x22) +#define OVERLAY_ROUTETBL_SET_DEFAULT (OVERLAY_ROUTER_IOCTL | 0x23) +#define OVERLAY_ROUTETBL_FLUSH (OVERLAY_ROUTER_IOCTL | 0x24) +#define OVERLAY_ROUTETBL_ITER (OVERLAY_ROUTER_IOCTL | 0x25) +#define OVERLAY_ROUTETBL_ADDENT (OVERLAY_ROUTER_IOCTL | 0x26) +#define OVERLAY_ROUTETBL_DELENT (OVERLAY_ROUTER_IOCTL | 0x27) +#define OVERLAY_ROUTETBL_FLUSHENT (OVERLAY_ROUTER_IOCTL | 0x28) + +/* + * We use the same struct for IPv4 and IPv6 targets. We assume route tables + * should be small, so the small amount of space saved by having separate IPv4 + * and IPv6 entries would be negligible. Since this is an implementation + * detail, we can always change it later if it proves to be problematic. + * + * This is also used for both ioctls and internally within the overlay + * driver (thus no ioc in the name). + */ +typedef struct overlay_route_ent { + struct sockaddr_in6 ore_target; /* the 'next hop' */ + struct in6_addr ore_dest; + uint8_t ore_prefixlen; +} overlay_route_ent_t; + +typedef struct overlay_ioc_routetab { + overlay_router_ioc_hdr_t oir_hdr; + uint64_t oir_marker; /* for entry iteration */ + uint16_t oir_count; + char oir_id[OVERLAY_ID_MAX]; + overlay_route_ent_t oir_ents[]; +} overlay_ioc_routetab_t; + +typedef struct overlay_ioc_rtab_iter { + overlay_router_ioc_hdr_t oiri_hdr; + uint64_t oiri_marker; /* for route table iteration */ + uint16_t oiri_count; + uint8_t oiri_pad[6]; + overlay_ioc_routetab_t oiri_rtabs[]; +} overlay_ioc_rtab_iter_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _OVERLAY_ROUTER_H */ diff --git a/usr/src/uts/common/sys/overlay_target.h b/usr/src/uts/common/sys/overlay_target.h index db9f4b67a900..575cfbb17956 100644 --- a/usr/src/uts/common/sys/overlay_target.h +++ b/usr/src/uts/common/sys/overlay_target.h @@ -10,7 +10,7 @@ */ /* - * Copyright (c) 2015 Joyent, Inc. + * Copyright 2021 Joyent, Inc. */ #ifndef _OVERLAY_TARGET_H @@ -31,8 +31,8 @@ extern "C" { typedef struct overlay_target_point { uint8_t otp_mac[ETHERADDRL]; - struct in6_addr otp_ip; uint16_t otp_port; + struct in6_addr otp_ip; } overlay_target_point_t; #define OVERLAY_TARG_IOCTL (('o' << 24) | ('v' << 16) | ('t' << 8)) @@ -120,30 +120,30 @@ typedef struct overlay_targ_id { * drop a packet. * * - * OVERLAY_TARG_INJECT - overlay_targ_pkt_t + * OVERLAY_TARG_INJECT - overlay_targ_pkt_t * - * The overlay_targ_pkt_t injects a fully formed packet into the - * virtual network. It may either be identified by its data link id - * or by the request id. If both are specified, the - * datalink id will be used. Note, that an injection is not - * considered a reply and if this corresponds to a requeset, then - * that individual packet must still be dropped. + * The overlay_targ_pkt_t injects a fully formed packet into the + * virtual network. It may either be identified by its data link id + * or by the request id. If both are specified, the + * datalink id will be used. Note, that an injection is not + * considered a reply and if this corresponds to a requeset, then + * that individual packet must still be dropped. * * - * OVERLAY_TARG_PKT - overlay_targ_pkt_t + * OVERLAY_TARG_PKT - overlay_targ_pkt_t * - * This ioctl can be used to copy data from a given request into a - * user buffer. This can be used in combination with - * OVERLAY_TARG_INJECT to implemnt services such as a proxy-arp. + * This ioctl can be used to copy data from a given request into a + * user buffer. This can be used in combination with + * OVERLAY_TARG_INJECT to implemnt services such as a proxy-arp. * * - * OVERLAY_TARG_RESEND - overlay_targ_pkt_t + * OVERLAY_TARG_RESEND - overlay_targ_pkt_t * - * This ioctl is similar to the OVERLAY_TARG_INJECT, except instead - * of receiving it on the local mac handle, it queues it for - * retransmission again. This is useful if you have a packet that - * was originally destined for some broadcast or multicast address - * that you now want to send to a unicast address. + * This ioctl is similar to the OVERLAY_TARG_INJECT, except instead + * of receiving it on the local mac handle, it queues it for + * retransmission again. This is useful if you have a packet that + * was originally destined for some broadcast or multicast address + * that you now want to send to a unicast address. */ #define OVERLAY_TARG_LOOKUP (OVERLAY_TARG_IOCTL | 0x10) #define OVERLAY_TARG_RESPOND (OVERLAY_TARG_IOCTL | 0x11) @@ -152,6 +152,18 @@ typedef struct overlay_targ_id { #define OVERLAY_TARG_PKT (OVERLAY_TARG_IOCTL | 0x14) #define OVERLAY_TARG_RESEND (OVERLAY_TARG_IOCTL | 0x15) +typedef struct overlay_targ_lookup_l2 { + uint8_t otl2_srcaddr[ETHERADDRL]; + uint8_t otl2_dstaddr[ETHERADDRL]; + uint32_t otl2_dsttype; + uint32_t otl2_sap; + int32_t otl2_vlan; +} overlay_targ_lookup_l2_t; + +typedef struct overlay_targ_lookup_l3 { + struct in6_addr otl3_dest; +} overlay_targ_lookup_l3_t; + typedef struct overlay_targ_lookup { uint64_t otl_dlid; uint64_t otl_reqid; @@ -159,11 +171,11 @@ typedef struct overlay_targ_lookup { uint64_t otl_vnetid; uint64_t otl_hdrsize; uint64_t otl_pktsize; - uint8_t otl_srcaddr[ETHERADDRL]; - uint8_t otl_dstaddr[ETHERADDRL]; - uint32_t otl_dsttype; - uint32_t otl_sap; - int32_t otl_vlan; + union { + overlay_targ_lookup_l2_t otl_l2; + overlay_targ_lookup_l3_t otl_l3; + } otl_u; + boolean_t otl_l3lookup; } overlay_targ_lookup_t; typedef struct overlay_targ_resp { @@ -210,46 +222,46 @@ typedef struct overlay_targ_list { * The following family of ioctls all manipulate the target cache of a given * device. * - * OVERLAY_TARG_CACHE_GET - overlay_targ_cache_t + * OVERLAY_TARG_CACHE_GET - overlay_targ_cache_t * - * The overlay_targ_cache_t should be have its link identifier and - * the desired mac address filled in. On return, it will fill in - * the otc_dest member, if the entry exists in the table. + * The overlay_targ_cache_t should be have its link identifier and + * the desired mac address filled in. On return, it will fill in + * the otc_dest member, if the entry exists in the table. * * - * OVERLAY_TARG_CACHE_SET - overlay_targ_cache_t + * OVERLAY_TARG_CACHE_SET - overlay_targ_cache_t * - * The cache table entry of the mac address referred to by otc_mac - * and otd_linkid will be filled in with the details provided by in - * the otc_dest member. + * The cache table entry of the mac address referred to by otc_mac + * and otd_linkid will be filled in with the details provided by in + * the otc_dest member. * - * OVERLAY_TARG_CACHE_REMOVE - overlay_targ_cache_t + * OVERLAY_TARG_CACHE_REMOVE - overlay_targ_cache_t * - * Removes the cache entry identified by otc_mac from the table. - * Note that this does not stop any in-flight lookups or deal with - * any data that is awaiting a lookup. + * Removes the cache entry identified by otc_mac from the table. + * Note that this does not stop any in-flight lookups or deal with + * any data that is awaiting a lookup. * * - * OVERLAY_TARG_CACHE_FLUSH - overlay_targ_cache_t + * OVERLAY_TARG_CACHE_FLUSH - overlay_targ_cache_t * - * Similar to OVERLAY_TARG_CACHE_REMOVE, but functions on the - * entire table identified by otc_linkid. All other parameters are - * ignored. + * Similar to OVERLAY_TARG_CACHE_REMOVE, but functions on the + * entire table identified by otc_linkid. All other parameters are + * ignored. * * - * OVERLAY_TARG_CACHE_ITER - overlay_targ_cache_iter_t + * OVERLAY_TARG_CACHE_ITER - overlay_targ_cache_iter_t * - * Iterates over the contents of a target cache identified by - * otci_linkid. Iteration is guaranteed to be exactly once for - * items which are in the hashtable at the beginning and end of - * iteration. For items which are added or removed after iteration - * has begun, only at most once semantics are guaranteed. Consumers - * should ensure that otci_marker is zeroed before starting - * iteration and should preserve its contents across calls. + * Iterates over the contents of a target cache identified by + * otci_linkid. Iteration is guaranteed to be exactly once for + * items which are in the hashtable at the beginning and end of + * iteration. For items which are added or removed after iteration + * has begun, only at most once semantics are guaranteed. Consumers + * should ensure that otci_marker is zeroed before starting + * iteration and should preserve its contents across calls. * - * Before calling in, otci_count should be set to the number of - * entries that space has been allocated for in otci_ents. The - * value will be updated to indicate the total number written out. + * Before calling in, otci_count should be set to the number of + * entries that space has been allocated for in otci_ents. The + * value will be updated to indicate the total number written out. */ #define OVERLAY_TARG_CACHE_GET (OVERLAY_TARG_IOCTL | 0x30) @@ -267,24 +279,27 @@ typedef struct overlay_targ_list { #define OVERLAY_TARGET_ITER_MAX 500 #define OVERLAY_TARGET_CACHE_DROP 0x01 +#define OVERLAY_TARGET_CACHE_L3 0x02 typedef struct overlay_targ_cache_entry { uint8_t otce_mac[ETHERADDRL]; uint16_t otce_flags; overlay_target_point_t otce_dest; } overlay_targ_cache_entry_t; +#define OTCE_IS_L3(otce) \ + (((otce)->otce_flags & OVERLAY_TARGET_CACHE_L3) != 0) typedef struct overlay_targ_cache { datalink_id_t otc_linkid; overlay_targ_cache_entry_t otc_entry; } overlay_targ_cache_t; +#define OVERLAY_TARG_NMARKER 5 typedef struct overlay_targ_cache_iter { datalink_id_t otci_linkid; - uint32_t otci_pad; - uint64_t otci_marker; + uint32_t otci_marker[OVERLAY_TARG_NMARKER]; uint16_t otci_count; - uint8_t otci_pad2[3]; + uint8_t otci_pad2[2]; overlay_targ_cache_entry_t otci_ents[]; } overlay_targ_cache_iter_t; diff --git a/usr/src/uts/common/sys/qqcache.h b/usr/src/uts/common/sys/qqcache.h new file mode 100644 index 000000000000..dcf8369f56ab --- /dev/null +++ b/usr/src/uts/common/sys/qqcache.h @@ -0,0 +1,182 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#ifndef _QQCACHE_H +#define _QQCACHE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This implements a fixed-size hash table that uses the 2Q algorithm + * from Johnson and Shasha to manage the contents of the entries. + * + * Briefly, there are two fixed sizes lists (0 and 1). New entries are + * added to the head of list 1, and upon subsequent access (lookup), are + * moved to the head of list 0. Entries that fall off the end of list 0 + * are pushed onto the head of list 1, and entries that fall off the end + * of list 1 are deleted. The percentage of the total size of the cache + * for each list is determined by the parameter 'a', which is a percentage + * (0-100) of the cache size that is dedicated to list 0. + * + * This implementation does generalize this algorithm somewhat to an + * arbitrary number of lists (instead of just 2) via the QQCACHE_NUM_LISTS + * and QQCACHE_INSERT_LIST preprocessor symbols (defined in + * sys/qqcache_impl.h). New entries are added to list QQCACHE_INSERT_LIST + * and as each list gets full, the oldest entry in each list is pushed to + * the head of the succeeding list, and the oldest entries are removed + * from the cache (so each list never has more entries than their maximum + * size). + * + * The API itself is very similar to that of refhash. A qqcache_link_t struct + * is embedded within the definition of the entries that are being stored in + * a given qqcache_t. Functions are provided to hash/compare the tag (key) + * value of an entry, as well as destroying the entry during the creation + * of the cache. Lookups then occur by passing a pointer to the key value + * being looked up. + * + * NOTE: As one can take references to entries in the cache via the + * qqcache_hold() function, refheld entries that are marked for deletion are + * not counted when tracking the cache size, and their dtor function is not + * called until the last reference has been released (by calling the + * qqcache_rele() function). + */ + +typedef enum qqcache_flag { + QQCACHE_F_DEAD = 0x01, +} qqcache_flag_t; + +typedef struct qqcache_link { + list_node_t qqln_hash_link; /* Hash chain bucket */ + list_node_t qqln_list_link; /* Cache list link */ + uint_t qqln_listnum; + uint_t qqln_refcnt; + qqcache_flag_t qqln_flags; +} qqcache_link_t; + +struct qqcache; +typedef struct qqcache qqcache_t; + +typedef uint64_t (*qqcache_hash_fn_t)(const void *); +typedef int (*qqcache_cmp_fn_t)(const void *, const void *); +typedef void (*qqcache_dtor_fn_t)(void *); + +/* + * qqcache_create(qcp, sz, a, buckets, hash_fn, cmp_fn, dtor_fn, + * elsize, link_off, tag_off, flags); + * + * Creates a new 2Q cache: + * + * qqcache_t **qcp A pointer to the pointer that will hold the new + * cache. + * + * size_t sz The size of the cache (in entries). + * + * size_t a The percentage (0-100) of the cache dedicated to + * MRU entries (list 0); + * + * size_t buckets The number of hash buckets in the cache. + * + * qqcache_hash_fn_t hash_fn The function used to create a + * hash value for a given entry's tag + * value. + * + * qqcache_cmp_fn_t cmp_fn The function used to compare the two + * tag values of two entries. The function + * should return '0' if the two entries + * are equal, '1' if they are not equal. + * + * qqcache_dtor_fn_t dtor_fn The function used to destroy/free + * entries. + * + * size_t elsize The size of each entry. + * + * size_t link_off The offset of the qqcache_link_t struct in the entry. + * + * size_t tag_off The offset in the entry of the tag value (used for + * hashing and comparison). + * + * int flags The flags passed to kmem_zalloc/umem_zalloc. + * + * Returns: + * 0 Success + * EINVAL A parameter was not valid + * ENOMEM The memory allocation failed (only possible when + * KM_NOSLEEP/UMEM_DEFAULT is passed to flags). + */ +extern int qqcache_create(qqcache_t **, size_t, size_t, size_t, + qqcache_hash_fn_t, qqcache_cmp_fn_t, qqcache_dtor_fn_t, + size_t, size_t, size_t, int); + +/* Destroy the given qqcache_t */ +extern void qqcache_destroy(qqcache_t *); + +/* + * qqcache_insert(qc, obj) + * + * qqcache_t *qc The cache to insert the item into. + * + * void *obj The object to add. + * + * Returns: + * 0 Success + * EEXIST The same entry (as determined by the cache cmp function) already + * exists in the cache. + */ +extern int qqcache_insert(qqcache_t *, void *); + +/* Lookup an entry with the given tag/key, or return NULL if not found */ +extern void *qqcache_lookup(qqcache_t *, const void *); + +/* Remove the given entry from the cache */ +extern void qqcache_remove(qqcache_t *, void *); + +/* Add a hold on the entry in the cache */ +extern void qqcache_hold(qqcache_t *, void *); + +/* Release the hold on the entry in the cache */ +extern void qqcache_rele(qqcache_t *, void *); + +/* + * Adjust the size and percentage of the cache for list 0. If new values are + * smaller than current values, entries may be evicted as necessary to reduce + * the size of the cache to the given size. + */ +extern int qqcache_adjust_size(qqcache_t *, size_t); +extern int qqcache_adjust_a(qqcache_t *, size_t); + +/* Return the current values of size or a. */ +extern size_t qqcache_size(const qqcache_t *); +extern size_t qqcache_a(const qqcache_t *); + +extern size_t qqcache_nentries(const qqcache_t *); + +/* Iterate through entries. */ +extern void *qqcache_first(qqcache_t *); +extern void *qqcache_next(qqcache_t *, void *); + +/* Lookup stats */ +extern uint64_t qqcache_lookups(const qqcache_t *); +extern uint64_t qqcache_hits(const qqcache_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _QQCACHE_H */ diff --git a/usr/src/uts/common/sys/qqcache_impl.h b/usr/src/uts/common/sys/qqcache_impl.h new file mode 100644 index 000000000000..c7537f4be239 --- /dev/null +++ b/usr/src/uts/common/sys/qqcache_impl.h @@ -0,0 +1,68 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2021 Joyent, Inc. + */ + +#ifndef _QQCACHE_IMPL_H +#define _QQCACHE_IMPL_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define QQCACHE_NUM_LISTS 2 +#define QQCACHE_INSERT_LIST 1 +#define QQCACHE_MIN_SIZE 10 + +CTASSERT(QQCACHE_INSERT_LIST < QQCACHE_NUM_LISTS); +CTASSERT(QQCACHE_NUM_LISTS >= 2); + +typedef struct qqcache_list { + list_t qqcl_list; + size_t qqcl_len; +} qqcache_list_t; + +struct qqcache { + qqcache_hash_fn_t qqc_hash_fn; + qqcache_cmp_fn_t qqc_cmp_fn; + qqcache_dtor_fn_t qqc_dtor_fn; + size_t qqc_link_off; + size_t qqc_tag_off; + size_t qqc_nbuckets; + size_t qqc_size; + size_t qqc_a; + size_t qqc_max[QQCACHE_NUM_LISTS]; + uint64_t qqc_lookups; + uint64_t qqc_hits; + qqcache_list_t qqc_lists[QQCACHE_NUM_LISTS]; + qqcache_list_t qqc_buckets[]; +}; + +#define QQCACHE_LIST(qqc, lnk) \ + (&(qqc)->qqc_lists[(lnk)->qqln_listnum]) + +#define obj_to_link(_q, _o) \ + ((qqcache_link_t *)(((char *)(_o)) + (_q)->qqc_link_off)) +#define link_to_obj(_q, _l) \ + ((void *)(((char *)(_l)) - (_q)->qqc_link_off)) +#define obj_to_tag(_q, _o) \ + ((void *)(((char *)(_o)) + (_q)->qqc_tag_off)) + +#ifdef __cplusplus +} +#endif + +#endif /* _QQCACHE_IMPL_H */