/* * 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 2015, Joyent, Inc. */ /* * Files based plug-in for varpd * * This is a dynamic varpd plug-in that has a static backing store. It's really * nothing more than a glorified version of /etc/ethers, though it facilitates * a bit more. The files module allows for the full set of mappings to be fixed * at creation time. In addition, it also provides support for proxying ARP, * NDP, and DHCP. * * At this time, the plugin requires that the destination type involve both an * IP address and a port; however, there's no reason that this cannot be made * more flexible as we have additional encapsulation algorithms that support it. * 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", * "ip": "192.168.1.1", * "port": 8080 * }, * ... * } */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct varpd_files { overlay_plugin_dest_t vaf_dest; /* RO */ varpd_provider_handle_t *vaf_hdl; /* RO */ char *vaf_path; /* WO */ nvlist_t *vaf_nvl; /* WO */ uint64_t vaf_nmisses; /* Atomic */ uint64_t vaf_narp; /* Atomic */ } varpd_files_t; static const char *varpd_files_props[] = { "files/config" }; static boolean_t varpd_files_valid_dest(overlay_plugin_dest_t dest) { if (dest & ~(OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT)) return (B_FALSE); if (!(dest & (OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT))) return (B_FALSE); return (B_TRUE); } static int varpd_files_create(varpd_provider_handle_t *hdl, void **outp, overlay_plugin_dest_t dest) { varpd_files_t *vaf; if (varpd_files_valid_dest(dest) == B_FALSE) return (ENOTSUP); vaf = umem_alloc(sizeof (varpd_files_t), UMEM_DEFAULT); if (vaf == NULL) return (ENOMEM); bzero(vaf, sizeof (varpd_files_t)); vaf->vaf_dest = dest; vaf->vaf_path = NULL; vaf->vaf_nvl = NULL; vaf->vaf_hdl = hdl; *outp = vaf; return (0); } static int varpd_files_normalize_nvlist(varpd_files_t *vaf, nvlist_t *nvl) { int ret; nvlist_t *out; nvpair_t *pair; if ((ret = nvlist_alloc(&out, NV_UNIQUE_NAME, 0)) != 0) return (ret); for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL; pair = nvlist_next_nvpair(nvl, pair)) { char *name, fname[ETHERADDRSTRL]; nvlist_t *data; struct ether_addr ether, *e; e = ðer; if (nvpair_type(pair) != DATA_TYPE_NVLIST) { nvlist_free(out); return (EINVAL); } name = nvpair_name(pair); if ((ret = nvpair_value_nvlist(pair, &data)) != 0) { nvlist_free(out); return (EINVAL); } if (ether_aton_r(name, e) == NULL) { nvlist_free(out); return (EINVAL); } if (ether_ntoa_r(e, fname) == NULL) { nvlist_free(out); return (ENOMEM); } if ((ret = nvlist_add_nvlist(out, fname, data)) != 0) { nvlist_free(out); return (EINVAL); } } vaf->vaf_nvl = out; return (0); } static int varpd_files_start(void *arg) { int fd, ret; void *maddr; struct stat st; nvlist_t *nvl; varpd_files_t *vaf = arg; if (vaf->vaf_path == NULL) return (EAGAIN); if ((fd = open(vaf->vaf_path, O_RDONLY)) < 0) return (errno); if (fstat(fd, &st) != 0) { ret = errno; if (close(fd) != 0) abort(); return (ret); } maddr = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); if (maddr == NULL) { ret = errno; if (close(fd) != 0) abort(); return (ret); } ret = nvlist_parse_json(maddr, st.st_size, &nvl, NVJSON_FORCE_INTEGER, NULL); if (ret == 0) { ret = varpd_files_normalize_nvlist(vaf, nvl); nvlist_free(nvl); } if (munmap(maddr, st.st_size) != 0) abort(); if (close(fd) != 0) abort(); return (ret); } static void varpd_files_stop(void *arg) { varpd_files_t *vaf = arg; nvlist_free(vaf->vaf_nvl); vaf->vaf_nvl = NULL; } static void varpd_files_destroy(void *arg) { varpd_files_t *vaf = arg; assert(vaf->vaf_nvl == NULL); if (vaf->vaf_path != NULL) { umem_free(vaf->vaf_path, strlen(vaf->vaf_path) + 1); vaf->vaf_path = NULL; } umem_free(vaf, sizeof (varpd_files_t)); } static void varpd_files_lookup(void *arg, varpd_query_handle_t *qh, const overlay_targ_lookup_t *otl, overlay_target_point_t *otp) { char macstr[ETHERADDRSTRL], *ipstr; nvlist_t *nvl; varpd_files_t *vaf = arg; int32_t port; static const uint8_t bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* We don't support a default */ if (otl == NULL) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (otl->otl_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) { libvarpd_plugin_proxy_ndp(vaf->vaf_hdl, qh, otl); return; } if (otl->otl_sap == ETHERTYPE_IP && bcmp(otl->otl_dstaddr, bcast, ETHERADDRL) == 0) { char *mac; struct ether_addr a, *addr; addr = &a; if (ether_ntoa_r((struct ether_addr *)otl->otl_srcaddr, macstr) == NULL) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (nvlist_lookup_nvlist(vaf->vaf_nvl, macstr, &nvl) != 0) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (nvlist_lookup_string(nvl, "dhcp-proxy", &mac) != 0) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (ether_aton_r(mac, addr) == NULL) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } libvarpd_plugin_proxy_dhcp(vaf->vaf_hdl, qh, otl); return; } if (ether_ntoa_r((struct ether_addr *)otl->otl_dstaddr, macstr) == NULL) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (nvlist_lookup_nvlist(vaf->vaf_nvl, macstr, &nvl) != 0) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (nvlist_lookup_int32(nvl, "port", &port) != 0) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } if (port <= 0 || port > UINT16_MAX) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } otp->otp_port = port; if (nvlist_lookup_string(nvl, "ip", &ipstr) != 0) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } /* * Try to parse it as a v6 address and then if it's not, try to * transform it into a v4 address which we'll then wrap it into a v4 * mapped address. */ if (inet_pton(AF_INET6, ipstr, &otp->otp_ip) != 1) { uint32_t v4; if (inet_pton(AF_INET, ipstr, &v4) != 1) { libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP); return; } IN6_IPADDR_TO_V4MAPPED(v4, &otp->otp_ip); } libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_OK); } /* ARGSUSED */ static int varpd_files_nprops(void *arg, uint_t *nprops) { *nprops = 1; return (0); } /* ARGSUSED */ static int varpd_files_propinfo(void *arg, uint_t propid, varpd_prop_handle_t *vph) { if (propid != 0) return (EINVAL); libvarpd_prop_set_name(vph, varpd_files_props[0]); libvarpd_prop_set_prot(vph, OVERLAY_PROP_PERM_RRW); libvarpd_prop_set_type(vph, OVERLAY_PROP_T_STRING); libvarpd_prop_set_nodefault(vph); return (0); } static int varpd_files_getprop(void *arg, const char *pname, void *buf, uint32_t *sizep) { varpd_files_t *vaf = arg; if (strcmp(pname, varpd_files_props[0]) != 0) return (EINVAL); if (vaf->vaf_path != NULL) { size_t len = strlen(vaf->vaf_path) + 1; if (*sizep < len) return (EOVERFLOW); *sizep = len; (void) strlcpy(buf, vaf->vaf_path, *sizep); } else { *sizep = 0; } return (0); } static int varpd_files_setprop(void *arg, const char *pname, const void *buf, const uint32_t size) { varpd_files_t *vaf = arg; if (strcmp(pname, varpd_files_props[0]) != 0) return (EINVAL); if (vaf->vaf_path != NULL) umem_free(vaf->vaf_path, strlen(vaf->vaf_path) + 1); vaf->vaf_path = umem_alloc(size, UMEM_DEFAULT); if (vaf->vaf_path == NULL) return (ENOMEM); (void) strlcpy(vaf->vaf_path, buf, size); return (0); } static int varpd_files_save(void *arg, nvlist_t *nvp) { int ret; varpd_files_t *vaf = arg; if (vaf->vaf_path == NULL) return (0); if ((ret = nvlist_add_string(nvp, varpd_files_props[0], vaf->vaf_path)) != 0) return (ret); if ((ret = nvlist_add_uint64(nvp, "files/vaf_nmisses", vaf->vaf_nmisses)) != 0) return (ret); if ((ret = nvlist_add_uint64(nvp, "files/vaf_narp", vaf->vaf_narp)) != 0) return (ret); return (0); } static int varpd_files_restore(nvlist_t *nvp, varpd_provider_handle_t *hdl, overlay_plugin_dest_t dest, void **outp) { varpd_files_t *vaf; char *str; int ret; uint64_t nmisses, narp; if (varpd_files_valid_dest(dest) == B_FALSE) return (EINVAL); ret = nvlist_lookup_string(nvp, varpd_files_props[0], &str); if (ret != 0 && ret != ENOENT) return (ret); else if (ret == ENOENT) str = NULL; if (nvlist_lookup_uint64(nvp, "files/vaf_nmisses", &nmisses) != 0) return (EINVAL); if (nvlist_lookup_uint64(nvp, "files/vaf_narp", &narp) != 0) return (EINVAL); vaf = umem_alloc(sizeof (varpd_files_t), UMEM_DEFAULT); if (vaf == NULL) return (ENOMEM); bzero(vaf, sizeof (varpd_files_t)); vaf->vaf_dest = dest; if (str != NULL) { size_t len = strlen(str) + 1; vaf->vaf_path = umem_alloc(len, UMEM_DEFAULT); if (vaf->vaf_path == NULL) { umem_free(vaf, sizeof (varpd_files_t)); return (ENOMEM); } (void) strlcpy(vaf->vaf_path, str, len); } vaf->vaf_hdl = hdl; *outp = vaf; return (0); } static void varpd_files_proxy_arp(void *arg, varpd_arp_handle_t *vah, int kind, const struct sockaddr *sock, uint8_t *out) { varpd_files_t *vaf = arg; const struct sockaddr_in *ip; const struct sockaddr_in6 *ip6; nvpair_t *pair; if (kind != VARPD_QTYPE_ETHERNET) { libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP); return; } if (sock->sa_family != AF_INET && sock->sa_family != AF_INET6) { libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP); return; } ip = (const struct sockaddr_in *)sock; ip6 = (const struct sockaddr_in6 *)sock; for (pair = nvlist_next_nvpair(vaf->vaf_nvl, NULL); pair != NULL; pair = nvlist_next_nvpair(vaf->vaf_nvl, pair)) { char *mac, *ipstr; nvlist_t *data; struct in_addr ia; struct in6_addr ia6; struct ether_addr ether, *e; e = ðer; if (nvpair_type(pair) != DATA_TYPE_NVLIST) continue; mac = nvpair_name(pair); if (nvpair_value_nvlist(pair, &data) != 0) continue; if (sock->sa_family == AF_INET) { if (nvlist_lookup_string(data, "arp", &ipstr) != 0) continue; if (inet_pton(AF_INET, ipstr, &ia) != 1) continue; if (bcmp(&ia, &ip->sin_addr, sizeof (struct in_addr)) != 0) continue; } else { if (nvlist_lookup_string(data, "ndp", &ipstr) != 0) continue; if (inet_pton(AF_INET6, ipstr, &ia6) != 1) continue; if (bcmp(&ia6, &ip6->sin6_addr, sizeof (struct in6_addr)) != 0) continue; } if (ether_aton_r(mac, e) == NULL) { libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP); return; } bcopy(e, out, ETHERADDRL); libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_OK); return; } libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP); } static void varpd_files_proxy_dhcp(void *arg, varpd_dhcp_handle_t *vdh, int type, const overlay_targ_lookup_t *otl, uint8_t *out) { varpd_files_t *vaf = arg; nvlist_t *nvl; char macstr[ETHERADDRSTRL], *mac; struct ether_addr a, *addr; addr = &a; if (type != VARPD_QTYPE_ETHERNET) { libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP); return; } if (ether_ntoa_r((struct ether_addr *)otl->otl_srcaddr, macstr) == NULL) { libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP); return; } if (nvlist_lookup_nvlist(vaf->vaf_nvl, macstr, &nvl) != 0) { libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP); return; } if (nvlist_lookup_string(nvl, "dhcp-proxy", &mac) != 0) { libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP); return; } if (ether_aton_r(mac, addr) == NULL) { libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP); return; } bcopy(addr, out, ETHERADDRL); libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_OK); } static const varpd_plugin_ops_t varpd_files_ops = { 0, varpd_files_create, varpd_files_start, varpd_files_stop, varpd_files_destroy, NULL, varpd_files_lookup, varpd_files_nprops, varpd_files_propinfo, varpd_files_getprop, varpd_files_setprop, varpd_files_save, varpd_files_restore, varpd_files_proxy_arp, varpd_files_proxy_dhcp }; #pragma init(varpd_files_init) static void varpd_files_init(void) { int err; varpd_plugin_register_t *vpr; vpr = libvarpd_plugin_alloc(VARPD_CURRENT_VERSION, &err); if (vpr == NULL) return; vpr->vpr_mode = OVERLAY_TARGET_DYNAMIC; vpr->vpr_name = "files"; vpr->vpr_ops = &varpd_files_ops; (void) libvarpd_plugin_register(vpr); libvarpd_plugin_free(vpr); }