1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2015, Joyent, Inc.
14  */
15 
16 /*
17  * Files based plug-in for varpd
18  *
19  * This is a dynamic varpd plug-in that has a static backing store. It's really
20  * nothing more than a glorified version of /etc/ethers, though it facilitates
21  * a bit more. The files module allows for the full set of mappings to be fixed
22  * at creation time. In addition, it also provides support for proxying ARP,
23  * NDP, and DHCP.
24  *
25  * At this time, the plugin requires that the destination type involve both an
26  * IP address and a port; however, there's no reason that this cannot be made
27  * more flexible as we have additional encapsulation algorithms that support it.
28  * The plug-in only has a single property, which is the location of the JSON
29  * file. The JSON file itself looks something like:
30  *
31  *	{
32  *		"aa:bb:cc:dd:ee:ff": {
33  *			"arp": "10.23.69.1",
34  *			"ndp": "2600:3c00::f03c:91ff:fe96:a264",
35  *			"ip": "192.168.1.1",
36  *			"port": 8080
37  *		},
38  *		...
39  *	}
40  */
41 
42 #include <libvarpd_provider.h>
43 #include <umem.h>
44 #include <errno.h>
45 #include <thread.h>
46 #include <synch.h>
47 #include <strings.h>
48 #include <assert.h>
49 #include <limits.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <fcntl.h>
53 #include <libnvpair.h>
54 #include <unistd.h>
55 #include <sys/mman.h>
56 #include <sys/ethernet.h>
57 #include <sys/socket.h>
58 #include <netinet/in.h>
59 #include <arpa/inet.h>
60 
61 #include <libvarpd_files_json.h>
62 
63 typedef struct varpd_files {
64 	overlay_plugin_dest_t	vaf_dest;	/* RO */
65 	varpd_provider_handle_t	*vaf_hdl;	/* RO */
66 	char			*vaf_path;	/* WO */
67 	nvlist_t		*vaf_nvl;	/* WO */
68 	uint64_t		vaf_nmisses;	/* Atomic */
69 	uint64_t		vaf_narp;	/* Atomic */
70 } varpd_files_t;
71 
72 static const char *varpd_files_props[] = {
73 	"files/config"
74 };
75 
76 static boolean_t
varpd_files_valid_dest(overlay_plugin_dest_t dest)77 varpd_files_valid_dest(overlay_plugin_dest_t dest)
78 {
79 	if (dest & ~(OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT))
80 		return (B_FALSE);
81 
82 	if (!(dest & (OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT)))
83 		return (B_FALSE);
84 
85 	return (B_TRUE);
86 }
87 
88 static int
varpd_files_create(varpd_provider_handle_t * hdl,void ** outp,overlay_plugin_dest_t dest)89 varpd_files_create(varpd_provider_handle_t *hdl, void **outp,
90     overlay_plugin_dest_t dest)
91 {
92 	varpd_files_t *vaf;
93 
94 	if (varpd_files_valid_dest(dest) == B_FALSE)
95 		return (ENOTSUP);
96 
97 	vaf = umem_alloc(sizeof (varpd_files_t), UMEM_DEFAULT);
98 	if (vaf == NULL)
99 		return (ENOMEM);
100 
101 	bzero(vaf, sizeof (varpd_files_t));
102 	vaf->vaf_dest = dest;
103 	vaf->vaf_path = NULL;
104 	vaf->vaf_nvl = NULL;
105 	vaf->vaf_hdl = hdl;
106 	*outp = vaf;
107 	return (0);
108 }
109 
110 static int
varpd_files_normalize_nvlist(varpd_files_t * vaf,nvlist_t * nvl)111 varpd_files_normalize_nvlist(varpd_files_t *vaf, nvlist_t *nvl)
112 {
113 	int ret;
114 	nvlist_t *out;
115 	nvpair_t *pair;
116 
117 	if ((ret = nvlist_alloc(&out, NV_UNIQUE_NAME, 0)) != 0)
118 		return (ret);
119 
120 	for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL;
121 	    pair = nvlist_next_nvpair(nvl, pair)) {
122 		char *name, fname[ETHERADDRSTRL];
123 		nvlist_t *data;
124 		struct ether_addr ether, *e;
125 		e = &ether;
126 
127 		if (nvpair_type(pair) != DATA_TYPE_NVLIST) {
128 			nvlist_free(out);
129 			return (EINVAL);
130 		}
131 
132 		name = nvpair_name(pair);
133 		if ((ret = nvpair_value_nvlist(pair, &data)) != 0) {
134 			nvlist_free(out);
135 			return (EINVAL);
136 		}
137 
138 		if (ether_aton_r(name, e) == NULL) {
139 			nvlist_free(out);
140 			return (EINVAL);
141 		}
142 
143 		if (ether_ntoa_r(e, fname) == NULL) {
144 			nvlist_free(out);
145 			return (ENOMEM);
146 		}
147 
148 		if ((ret = nvlist_add_nvlist(out, fname, data)) != 0) {
149 			nvlist_free(out);
150 			return (EINVAL);
151 		}
152 	}
153 
154 	vaf->vaf_nvl = out;
155 	return (0);
156 }
157 
158 static int
varpd_files_start(void * arg)159 varpd_files_start(void *arg)
160 {
161 	int fd, ret;
162 	void *maddr;
163 	struct stat st;
164 	nvlist_t *nvl;
165 	varpd_files_t *vaf = arg;
166 
167 	if (vaf->vaf_path == NULL)
168 		return (EAGAIN);
169 
170 	if ((fd = open(vaf->vaf_path, O_RDONLY)) < 0)
171 		return (errno);
172 
173 	if (fstat(fd, &st) != 0) {
174 		ret = errno;
175 		if (close(fd) != 0)
176 			abort();
177 		return (ret);
178 	}
179 
180 	maddr = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE,
181 	    fd, 0);
182 	if (maddr == NULL) {
183 		ret = errno;
184 		if (close(fd) != 0)
185 			abort();
186 		return (ret);
187 	}
188 
189 	ret = nvlist_parse_json(maddr, st.st_size, &nvl,
190 	    NVJSON_FORCE_INTEGER, NULL);
191 	if (ret == 0) {
192 		ret = varpd_files_normalize_nvlist(vaf, nvl);
193 		nvlist_free(nvl);
194 	}
195 	if (munmap(maddr, st.st_size) != 0)
196 		abort();
197 	if (close(fd) != 0)
198 		abort();
199 
200 	return (ret);
201 }
202 
203 static void
varpd_files_stop(void * arg)204 varpd_files_stop(void *arg)
205 {
206 	varpd_files_t *vaf = arg;
207 
208 	nvlist_free(vaf->vaf_nvl);
209 	vaf->vaf_nvl = NULL;
210 }
211 
212 static void
varpd_files_destroy(void * arg)213 varpd_files_destroy(void *arg)
214 {
215 	varpd_files_t *vaf = arg;
216 
217 	assert(vaf->vaf_nvl == NULL);
218 	if (vaf->vaf_path != NULL) {
219 		umem_free(vaf->vaf_path, strlen(vaf->vaf_path) + 1);
220 		vaf->vaf_path = NULL;
221 	}
222 	umem_free(vaf, sizeof (varpd_files_t));
223 }
224 
225 static void
varpd_files_lookup(void * arg,varpd_query_handle_t * qh,const overlay_targ_lookup_t * otl,overlay_target_point_t * otp)226 varpd_files_lookup(void *arg, varpd_query_handle_t *qh,
227     const overlay_targ_lookup_t *otl, overlay_target_point_t *otp)
228 {
229 	char macstr[ETHERADDRSTRL], *ipstr;
230 	nvlist_t *nvl;
231 	varpd_files_t *vaf = arg;
232 	int32_t port;
233 	static const uint8_t bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
234 
235 	/* We don't support a default */
236 	if (otl == NULL) {
237 		libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
238 		return;
239 	}
240 
241 	if (otl->otl_sap == ETHERTYPE_ARP) {
242 		libvarpd_plugin_proxy_arp(vaf->vaf_hdl, qh, otl);
243 		return;
244 	}
245 
246 	if (otl->otl_sap == ETHERTYPE_IPV6 &&
247 	    otl->otl_dstaddr[0] == 0x33 &&
248 	    otl->otl_dstaddr[1] == 0x33) {
249 		libvarpd_plugin_proxy_ndp(vaf->vaf_hdl, qh, otl);
250 		return;
251 	}
252 
253 	if (otl->otl_sap == ETHERTYPE_IP &&
254 	    bcmp(otl->otl_dstaddr, bcast, ETHERADDRL) == 0) {
255 		char *mac;
256 		struct ether_addr a, *addr;
257 
258 		addr = &a;
259 		if (ether_ntoa_r((struct ether_addr *)otl->otl_srcaddr,
260 		    macstr) == NULL) {
261 			libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
262 			return;
263 		}
264 
265 		if (nvlist_lookup_nvlist(vaf->vaf_nvl, macstr, &nvl) != 0) {
266 			libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
267 			return;
268 		}
269 
270 		if (nvlist_lookup_string(nvl, "dhcp-proxy", &mac) != 0) {
271 			libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
272 			return;
273 		}
274 
275 		if (ether_aton_r(mac, addr) == NULL) {
276 			libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
277 			return;
278 		}
279 
280 		libvarpd_plugin_proxy_dhcp(vaf->vaf_hdl, qh, otl);
281 		return;
282 	}
283 
284 	if (ether_ntoa_r((struct ether_addr *)otl->otl_dstaddr,
285 	    macstr) == NULL) {
286 		libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
287 		return;
288 	}
289 
290 	if (nvlist_lookup_nvlist(vaf->vaf_nvl, macstr, &nvl) != 0) {
291 		libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
292 		return;
293 	}
294 
295 	if (nvlist_lookup_int32(nvl, "port", &port) != 0) {
296 		libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
297 		return;
298 	}
299 
300 	if (port <= 0 || port > UINT16_MAX) {
301 		libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
302 		return;
303 	}
304 	otp->otp_port = port;
305 
306 	if (nvlist_lookup_string(nvl, "ip", &ipstr) != 0) {
307 		libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
308 		return;
309 	}
310 
311 	/*
312 	 * Try to parse it as a v6 address and then if it's not, try to
313 	 * transform it into a v4 address which we'll then wrap it into a v4
314 	 * mapped address.
315 	 */
316 	if (inet_pton(AF_INET6, ipstr, &otp->otp_ip) != 1) {
317 		uint32_t v4;
318 		if (inet_pton(AF_INET, ipstr, &v4) != 1) {
319 			libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_DROP);
320 			return;
321 		}
322 		IN6_IPADDR_TO_V4MAPPED(v4, &otp->otp_ip);
323 	}
324 
325 	libvarpd_plugin_query_reply(qh, VARPD_LOOKUP_OK);
326 }
327 
328 /* ARGSUSED */
329 static int
varpd_files_nprops(void * arg,uint_t * nprops)330 varpd_files_nprops(void *arg, uint_t *nprops)
331 {
332 	*nprops = 1;
333 	return (0);
334 }
335 
336 /* ARGSUSED */
337 static int
varpd_files_propinfo(void * arg,uint_t propid,varpd_prop_handle_t * vph)338 varpd_files_propinfo(void *arg, uint_t propid, varpd_prop_handle_t *vph)
339 {
340 	if (propid != 0)
341 		return (EINVAL);
342 
343 	libvarpd_prop_set_name(vph, varpd_files_props[0]);
344 	libvarpd_prop_set_prot(vph, OVERLAY_PROP_PERM_RRW);
345 	libvarpd_prop_set_type(vph, OVERLAY_PROP_T_STRING);
346 	libvarpd_prop_set_nodefault(vph);
347 	return (0);
348 }
349 
350 static int
varpd_files_getprop(void * arg,const char * pname,void * buf,uint32_t * sizep)351 varpd_files_getprop(void *arg, const char *pname, void *buf, uint32_t *sizep)
352 {
353 	varpd_files_t *vaf = arg;
354 
355 	if (strcmp(pname, varpd_files_props[0]) != 0)
356 		return (EINVAL);
357 
358 	if (vaf->vaf_path != NULL) {
359 		size_t len = strlen(vaf->vaf_path) + 1;
360 		if (*sizep < len)
361 			return (EOVERFLOW);
362 		*sizep = len;
363 		(void) strlcpy(buf, vaf->vaf_path, *sizep);
364 
365 	} else {
366 		*sizep = 0;
367 	}
368 
369 	return (0);
370 }
371 
372 static int
varpd_files_setprop(void * arg,const char * pname,const void * buf,const uint32_t size)373 varpd_files_setprop(void *arg, const char *pname, const void *buf,
374     const uint32_t size)
375 {
376 	varpd_files_t *vaf = arg;
377 
378 	if (strcmp(pname, varpd_files_props[0]) != 0)
379 		return (EINVAL);
380 
381 	if (vaf->vaf_path != NULL)
382 		umem_free(vaf->vaf_path, strlen(vaf->vaf_path) + 1);
383 
384 	vaf->vaf_path = umem_alloc(size, UMEM_DEFAULT);
385 	if (vaf->vaf_path == NULL)
386 		return (ENOMEM);
387 	(void) strlcpy(vaf->vaf_path, buf, size);
388 	return (0);
389 }
390 
391 static int
varpd_files_save(void * arg,nvlist_t * nvp)392 varpd_files_save(void *arg, nvlist_t *nvp)
393 {
394 	int ret;
395 	varpd_files_t *vaf = arg;
396 
397 	if (vaf->vaf_path == NULL)
398 		return (0);
399 
400 	if ((ret = nvlist_add_string(nvp, varpd_files_props[0],
401 	    vaf->vaf_path)) != 0)
402 		return (ret);
403 
404 	if ((ret = nvlist_add_uint64(nvp, "files/vaf_nmisses",
405 	    vaf->vaf_nmisses)) != 0)
406 		return (ret);
407 
408 	if ((ret = nvlist_add_uint64(nvp, "files/vaf_narp",
409 	    vaf->vaf_narp)) != 0)
410 		return (ret);
411 	return (0);
412 }
413 
414 static int
varpd_files_restore(nvlist_t * nvp,varpd_provider_handle_t * hdl,overlay_plugin_dest_t dest,void ** outp)415 varpd_files_restore(nvlist_t *nvp, varpd_provider_handle_t *hdl,
416     overlay_plugin_dest_t dest, void **outp)
417 {
418 	varpd_files_t *vaf;
419 	char *str;
420 	int ret;
421 	uint64_t nmisses, narp;
422 
423 	if (varpd_files_valid_dest(dest) == B_FALSE)
424 		return (EINVAL);
425 
426 	ret = nvlist_lookup_string(nvp, varpd_files_props[0], &str);
427 	if (ret != 0 && ret != ENOENT)
428 		return (ret);
429 	else if (ret == ENOENT)
430 		str = NULL;
431 
432 	if (nvlist_lookup_uint64(nvp, "files/vaf_nmisses", &nmisses) != 0)
433 		return (EINVAL);
434 	if (nvlist_lookup_uint64(nvp, "files/vaf_narp", &narp) != 0)
435 		return (EINVAL);
436 
437 	vaf = umem_alloc(sizeof (varpd_files_t), UMEM_DEFAULT);
438 	if (vaf == NULL)
439 		return (ENOMEM);
440 
441 	bzero(vaf, sizeof (varpd_files_t));
442 	vaf->vaf_dest = dest;
443 	if (str != NULL) {
444 		size_t len = strlen(str) + 1;
445 		vaf->vaf_path = umem_alloc(len, UMEM_DEFAULT);
446 		if (vaf->vaf_path == NULL) {
447 			umem_free(vaf, sizeof (varpd_files_t));
448 			return (ENOMEM);
449 		}
450 		(void) strlcpy(vaf->vaf_path, str, len);
451 	}
452 
453 	vaf->vaf_hdl = hdl;
454 	*outp = vaf;
455 	return (0);
456 }
457 
458 static void
varpd_files_proxy_arp(void * arg,varpd_arp_handle_t * vah,int kind,const struct sockaddr * sock,uint8_t * out)459 varpd_files_proxy_arp(void *arg, varpd_arp_handle_t *vah, int kind,
460     const struct sockaddr *sock, uint8_t *out)
461 {
462 	varpd_files_t *vaf = arg;
463 	const struct sockaddr_in *ip;
464 	const struct sockaddr_in6 *ip6;
465 	nvpair_t *pair;
466 
467 	if (kind != VARPD_QTYPE_ETHERNET) {
468 		libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP);
469 		return;
470 	}
471 
472 	if (sock->sa_family != AF_INET && sock->sa_family != AF_INET6) {
473 		libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP);
474 		return;
475 	}
476 
477 	ip = (const struct sockaddr_in *)sock;
478 	ip6 = (const struct sockaddr_in6 *)sock;
479 	for (pair = nvlist_next_nvpair(vaf->vaf_nvl, NULL); pair != NULL;
480 	    pair = nvlist_next_nvpair(vaf->vaf_nvl, pair)) {
481 		char *mac, *ipstr;
482 		nvlist_t *data;
483 		struct in_addr ia;
484 		struct in6_addr ia6;
485 		struct ether_addr ether, *e;
486 		e = &ether;
487 
488 		if (nvpair_type(pair) != DATA_TYPE_NVLIST)
489 			continue;
490 
491 		mac = nvpair_name(pair);
492 		if (nvpair_value_nvlist(pair, &data) != 0)
493 			continue;
494 
495 
496 		if (sock->sa_family == AF_INET) {
497 			if (nvlist_lookup_string(data, "arp", &ipstr) != 0)
498 				continue;
499 
500 			if (inet_pton(AF_INET, ipstr, &ia) != 1)
501 				continue;
502 
503 			if (bcmp(&ia, &ip->sin_addr,
504 			    sizeof (struct in_addr)) != 0)
505 				continue;
506 		} else {
507 			if (nvlist_lookup_string(data, "ndp", &ipstr) != 0)
508 				continue;
509 
510 			if (inet_pton(AF_INET6, ipstr, &ia6) != 1)
511 				continue;
512 
513 			if (bcmp(&ia6, &ip6->sin6_addr,
514 			    sizeof (struct in6_addr)) != 0)
515 				continue;
516 		}
517 
518 		if (ether_aton_r(mac, e) == NULL) {
519 			libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP);
520 			return;
521 		}
522 
523 		bcopy(e, out, ETHERADDRL);
524 		libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_OK);
525 		return;
526 	}
527 
528 	libvarpd_plugin_arp_reply(vah, VARPD_LOOKUP_DROP);
529 }
530 
531 static void
varpd_files_proxy_dhcp(void * arg,varpd_dhcp_handle_t * vdh,int type,const overlay_targ_lookup_t * otl,uint8_t * out)532 varpd_files_proxy_dhcp(void *arg, varpd_dhcp_handle_t *vdh, int type,
533     const overlay_targ_lookup_t *otl, uint8_t *out)
534 {
535 	varpd_files_t *vaf = arg;
536 	nvlist_t *nvl;
537 	char macstr[ETHERADDRSTRL], *mac;
538 	struct ether_addr a, *addr;
539 
540 	addr = &a;
541 	if (type != VARPD_QTYPE_ETHERNET) {
542 		libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP);
543 		return;
544 	}
545 
546 	if (ether_ntoa_r((struct ether_addr *)otl->otl_srcaddr,
547 	    macstr) == NULL) {
548 		libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP);
549 		return;
550 	}
551 
552 	if (nvlist_lookup_nvlist(vaf->vaf_nvl, macstr, &nvl) != 0) {
553 		libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP);
554 		return;
555 	}
556 
557 	if (nvlist_lookup_string(nvl, "dhcp-proxy", &mac) != 0) {
558 		libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP);
559 		return;
560 	}
561 
562 	if (ether_aton_r(mac, addr) == NULL) {
563 		libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_DROP);
564 		return;
565 	}
566 
567 	bcopy(addr, out, ETHERADDRL);
568 	libvarpd_plugin_dhcp_reply(vdh, VARPD_LOOKUP_OK);
569 }
570 
571 static const varpd_plugin_ops_t varpd_files_ops = {
572 	0,
573 	varpd_files_create,
574 	varpd_files_start,
575 	varpd_files_stop,
576 	varpd_files_destroy,
577 	NULL,
578 	varpd_files_lookup,
579 	varpd_files_nprops,
580 	varpd_files_propinfo,
581 	varpd_files_getprop,
582 	varpd_files_setprop,
583 	varpd_files_save,
584 	varpd_files_restore,
585 	varpd_files_proxy_arp,
586 	varpd_files_proxy_dhcp
587 };
588 
589 #pragma init(varpd_files_init)
590 static void
varpd_files_init(void)591 varpd_files_init(void)
592 {
593 	int err;
594 	varpd_plugin_register_t *vpr;
595 
596 	vpr = libvarpd_plugin_alloc(VARPD_CURRENT_VERSION, &err);
597 	if (vpr == NULL)
598 		return;
599 
600 	vpr->vpr_mode = OVERLAY_TARGET_DYNAMIC;
601 	vpr->vpr_name = "files";
602 	vpr->vpr_ops = &varpd_files_ops;
603 	(void) libvarpd_plugin_register(vpr);
604 	libvarpd_plugin_free(vpr);
605 }
606