sync.c revision 1.12
1/*	$OpenBSD: sync.c,v 1.12 2013/04/13 18:08:47 krw Exp $	*/
2
3/*
4 * Copyright (c) 2008 Bob Beck <beck@openbsd.org>
5 * Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <sys/param.h>
21#include <sys/stdint.h>
22#include <sys/file.h>
23#include <sys/wait.h>
24#include <sys/socket.h>
25#include <sys/resource.h>
26#include <sys/uio.h>
27#include <sys/ioctl.h>
28#include <sys/queue.h>
29
30
31#include <net/if.h>
32#include <netinet/in.h>
33#include <arpa/inet.h>
34
35#include <err.h>
36#include <errno.h>
37#include <pwd.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <unistd.h>
42#include <sha1.h>
43
44#include <netdb.h>
45
46#include <openssl/hmac.h>
47
48#include "dhcpd.h"
49#include "sync.h"
50
51int sync_debug;
52
53u_int32_t sync_counter;
54int syncfd = -1;
55int sendmcast;
56
57struct sockaddr_in sync_in;
58struct sockaddr_in sync_out;
59static char *sync_key;
60
61struct sync_host {
62	LIST_ENTRY(sync_host)	h_entry;
63
64	char			*h_name;
65	struct sockaddr_in	sh_addr;
66};
67LIST_HEAD(synchosts, sync_host) sync_hosts = LIST_HEAD_INITIALIZER(sync_hosts);
68
69void	 sync_send(struct iovec *, int);
70
71int
72sync_addhost(const char *name, u_short port)
73{
74	struct addrinfo hints, *res, *res0;
75	struct sync_host *shost;
76	struct sockaddr_in *addr = NULL;
77
78	bzero(&hints, sizeof(hints));
79	hints.ai_family = PF_UNSPEC;
80	hints.ai_socktype = SOCK_STREAM;
81	if (getaddrinfo(name, NULL, &hints, &res0) != 0)
82		return (EINVAL);
83	for (res = res0; res != NULL; res = res->ai_next) {
84		if (addr == NULL && res->ai_family == AF_INET) {
85			addr = (struct sockaddr_in *)res->ai_addr;
86			break;
87		}
88	}
89	if (addr == NULL) {
90		freeaddrinfo(res0);
91		return (EINVAL);
92	}
93	if ((shost = (struct sync_host *)
94	    calloc(1, sizeof(struct sync_host))) == NULL) {
95		freeaddrinfo(res0);
96		return (ENOMEM);
97	}
98	shost->h_name = strdup(name);
99	if (shost->h_name == NULL) {
100		free(shost);
101		freeaddrinfo(res0);
102		return (ENOMEM);
103	}
104
105	shost->sh_addr.sin_family = AF_INET;
106	shost->sh_addr.sin_port = htons(port);
107	shost->sh_addr.sin_addr.s_addr = addr->sin_addr.s_addr;
108	freeaddrinfo(res0);
109
110	LIST_INSERT_HEAD(&sync_hosts, shost, h_entry);
111
112	if (sync_debug)
113		note("added dhcp sync host %s (address %s, port %d)\n",
114		    shost->h_name, inet_ntoa(shost->sh_addr.sin_addr), port);
115
116	return (0);
117}
118
119int
120sync_init(const char *iface, const char *baddr, u_short port)
121{
122	int one = 1;
123	u_int8_t ttl;
124	struct ifreq ifr;
125	struct ip_mreq mreq;
126	struct sockaddr_in *addr;
127	char ifnam[IFNAMSIZ], *ttlstr;
128	const char *errstr;
129	struct in_addr ina;
130
131	if (iface != NULL)
132		sendmcast++;
133
134	bzero(&ina, sizeof(ina));
135	if (baddr != NULL) {
136		if (inet_pton(AF_INET, baddr, &ina) != 1) {
137			ina.s_addr = htonl(INADDR_ANY);
138			if (iface == NULL)
139				iface = baddr;
140			else if (iface != NULL && strcmp(baddr, iface) != 0) {
141				fprintf(stderr, "multicast interface does "
142				    "not match");
143				return (-1);
144			}
145		}
146	}
147
148	sync_key = SHA1File(DHCP_SYNC_KEY, NULL);
149	if (sync_key == NULL) {
150		if (errno != ENOENT) {
151			fprintf(stderr, "failed to open sync key: %s\n",
152			    strerror(errno));
153			return (-1);
154		}
155		/* Use empty key by default */
156		sync_key = "";
157	}
158
159	syncfd = socket(AF_INET, SOCK_DGRAM, 0);
160	if (syncfd == -1)
161		return (-1);
162
163	if (setsockopt(syncfd, SOL_SOCKET, SO_REUSEADDR, &one,
164	    sizeof(one)) == -1)
165		goto fail;
166
167	bzero(&sync_out, sizeof(sync_out));
168	sync_out.sin_family = AF_INET;
169	sync_out.sin_len = sizeof(sync_out);
170	sync_out.sin_addr.s_addr = ina.s_addr;
171	if (baddr == NULL && iface == NULL)
172		sync_out.sin_port = 0;
173	else
174		sync_out.sin_port = htons(port);
175
176	if (bind(syncfd, (struct sockaddr *)&sync_out, sizeof(sync_out)) == -1)
177		goto fail;
178
179	/* Don't use multicast messages */
180	if (iface == NULL)
181		return (syncfd);
182
183	strlcpy(ifnam, iface, sizeof(ifnam));
184	ttl = DHCP_SYNC_MCASTTTL;
185	if ((ttlstr = strchr(ifnam, ':')) != NULL) {
186		*ttlstr++ = '\0';
187		ttl = (u_int8_t)strtonum(ttlstr, 1, UINT8_MAX, &errstr);
188		if (errstr) {
189			fprintf(stderr, "invalid multicast ttl %s: %s",
190			    ttlstr, errstr);
191			goto fail;
192		}
193	}
194
195	bzero(&ifr, sizeof(ifr));
196	strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name));
197	if (ioctl(syncfd, SIOCGIFADDR, &ifr) == -1)
198		goto fail;
199
200	bzero(&sync_in, sizeof(sync_in));
201	addr = (struct sockaddr_in *)&ifr.ifr_addr;
202	sync_in.sin_family = AF_INET;
203	sync_in.sin_len = sizeof(sync_in);
204	sync_in.sin_addr.s_addr = addr->sin_addr.s_addr;
205	sync_in.sin_port = htons(port);
206
207	bzero(&mreq, sizeof(mreq));
208	sync_out.sin_addr.s_addr = inet_addr(DHCP_SYNC_MCASTADDR);
209	mreq.imr_multiaddr.s_addr = inet_addr(DHCP_SYNC_MCASTADDR);
210	mreq.imr_interface.s_addr = sync_in.sin_addr.s_addr;
211
212	if (setsockopt(syncfd, IPPROTO_IP,
213	    IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
214		fprintf(stderr, "failed to add multicast membership to %s: %s",
215		    DHCP_SYNC_MCASTADDR, strerror(errno));
216		goto fail;
217	}
218	if (setsockopt(syncfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
219	    sizeof(ttl)) == -1) {
220		fprintf(stderr, "failed to set multicast ttl to "
221		    "%u: %s\n", ttl, strerror(errno));
222		setsockopt(syncfd, IPPROTO_IP,
223		    IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
224		goto fail;
225	}
226
227	if (sync_debug)
228		syslog_r(LOG_DEBUG, &sdata, "using multicast dhcp sync %smode "
229		    "(ttl %u, group %s, port %d)\n",
230		    sendmcast ? "" : "receive ",
231		    ttl, inet_ntoa(sync_out.sin_addr), port);
232
233	return (syncfd);
234
235 fail:
236	close(syncfd);
237	return (-1);
238}
239
240void
241sync_recv(void)
242{
243	struct dhcp_synchdr *hdr;
244	struct sockaddr_in addr;
245	struct dhcp_synctlv_hdr *tlv;
246	struct dhcp_synctlv_lease *lv;
247	struct lease	*lease;
248	u_int8_t buf[DHCP_SYNC_MAXSIZE];
249	u_int8_t hmac[2][DHCP_SYNC_HMAC_LEN];
250	struct lease l, *lp;
251	u_int8_t *p;
252	socklen_t addr_len;
253	ssize_t len;
254	u_int hmac_len;
255
256	bzero(&addr, sizeof(addr));
257	bzero(buf, sizeof(buf));
258
259	addr_len = sizeof(addr);
260	if ((len = recvfrom(syncfd, buf, sizeof(buf), 0,
261	    (struct sockaddr *)&addr, &addr_len)) < 1)
262		return;
263	if (addr.sin_addr.s_addr != htonl(INADDR_ANY) &&
264	    bcmp(&sync_in.sin_addr, &addr.sin_addr,
265	    sizeof(addr.sin_addr)) == 0)
266		return;
267
268	/* Ignore invalid or truncated packets */
269	hdr = (struct dhcp_synchdr *)buf;
270	if (len < sizeof(struct dhcp_synchdr) ||
271	    hdr->sh_version != DHCP_SYNC_VERSION ||
272	    hdr->sh_af != AF_INET ||
273	    len < ntohs(hdr->sh_length))
274		goto trunc;
275	len = ntohs(hdr->sh_length);
276
277	/* Compute and validate HMAC */
278	bcopy(hdr->sh_hmac, hmac[0], DHCP_SYNC_HMAC_LEN);
279	bzero(hdr->sh_hmac, DHCP_SYNC_HMAC_LEN);
280	HMAC(EVP_sha1(), sync_key, strlen(sync_key), buf, len,
281	    hmac[1], &hmac_len);
282	if (bcmp(hmac[0], hmac[1], DHCP_SYNC_HMAC_LEN) != 0)
283		goto trunc;
284
285	if (sync_debug)
286		note("%s(sync): received packet of %d bytes\n",
287		    inet_ntoa(addr.sin_addr), (int)len);
288
289	p = (u_int8_t *)(hdr + 1);
290	while (len) {
291		tlv = (struct dhcp_synctlv_hdr *)p;
292
293		if (len < sizeof(struct dhcp_synctlv_hdr) ||
294		    len < ntohs(tlv->st_length))
295			goto trunc;
296
297		switch (ntohs(tlv->st_type)) {
298		case DHCP_SYNC_LEASE:
299			lv = (struct dhcp_synctlv_lease *)tlv;
300			if (sizeof(*lv) > ntohs(tlv->st_length))
301				goto trunc;
302			if ((lease = find_lease_by_hw_addr(
303				    lv->lv_hardware_addr.haddr,
304				    lv->lv_hardware_addr.hlen)) == NULL) {
305				if ((lease = find_lease_by_hw_addr(
306					    lv->lv_hardware_addr.haddr,
307					    lv->lv_hardware_addr.hlen)) == NULL)
308				    {
309					lp = &l;
310					memset(lp, 0, sizeof(*lp));
311				} else
312					lp = lease;
313			} else
314				lp = lease;
315
316			lp = &l;
317			memset(lp, 0, sizeof(*lp));
318			lp->timestamp = ntohl(lv->lv_timestamp);
319			lp->starts = ntohl(lv->lv_starts);
320			lp->ends = ntohl(lv->lv_ends);
321			memcpy(&lp->ip_addr, &lv->lv_ip_addr,
322			    sizeof(lp->ip_addr));
323			memcpy(&lp->hardware_addr, &lv->lv_hardware_addr,
324			    sizeof(lp->hardware_addr));
325			note("DHCP_SYNC_LEASE from %s for hw %s -> ip %s, "
326			    "start %d, end %d",
327			    inet_ntoa(addr.sin_addr),
328			    print_hw_addr(lp->hardware_addr.htype,
329			    lp->hardware_addr.hlen, lp->hardware_addr.haddr),
330			    piaddr(lp->ip_addr), lp->starts, lp->ends);
331			/* now whack the lease in there */
332			if (lease == NULL) {
333				enter_lease(lp);
334				write_leases();
335			}
336			else if (lease->ends < lp->ends)
337				supersede_lease(lease, lp, 1);
338			else if (lease->ends > lp->ends)
339				/*
340				 * our partner sent us a lease
341				 * that is older than what we have,
342				 * so re-educate them with what we
343				 * know is newer.
344				 */
345				sync_lease(lease);
346			break;
347		case DHCP_SYNC_END:
348			goto done;
349		default:
350			printf("invalid type: %d\n", ntohs(tlv->st_type));
351			goto trunc;
352		}
353		len -= ntohs(tlv->st_length);
354		p = ((u_int8_t *)tlv) + ntohs(tlv->st_length);
355	}
356
357 done:
358	return;
359
360 trunc:
361	if (sync_debug)
362		note("%s(sync): truncated or invalid packet\n",
363		    inet_ntoa(addr.sin_addr));
364}
365
366void
367sync_send(struct iovec *iov, int iovlen)
368{
369	struct sync_host *shost;
370	struct msghdr msg;
371
372	if (syncfd == -1)
373		return;
374
375	/* setup buffer */
376	bzero(&msg, sizeof(msg));
377	msg.msg_iov = iov;
378	msg.msg_iovlen = iovlen;
379
380	if (sendmcast) {
381		if (sync_debug)
382			note("sending multicast sync message\n");
383		msg.msg_name = &sync_out;
384		msg.msg_namelen = sizeof(sync_out);
385		if (sendmsg(syncfd, &msg, 0) == -1)
386			warning("sending multicast sync message failed: %m");
387	}
388
389	LIST_FOREACH(shost, &sync_hosts, h_entry) {
390		if (sync_debug)
391			note("sending sync message to %s (%s)\n",
392			    shost->h_name, inet_ntoa(shost->sh_addr.sin_addr));
393		msg.msg_name = &shost->sh_addr;
394		msg.msg_namelen = sizeof(shost->sh_addr);
395		if (sendmsg(syncfd, &msg, 0) == -1)
396			warning("sending sync message failed: %m");
397	}
398}
399
400void
401sync_lease(struct lease *lease)
402{
403	struct iovec iov[4];
404	struct dhcp_synchdr hdr;
405	struct dhcp_synctlv_lease lv;
406	struct dhcp_synctlv_hdr end;
407	char pad[DHCP_ALIGNBYTES];
408	u_int16_t leaselen, padlen;
409	int i = 0;
410	HMAC_CTX ctx;
411	u_int hmac_len;
412
413	if (sync_key == NULL)
414		return;
415
416	bzero(&hdr, sizeof(hdr));
417	bzero(&lv, sizeof(lv));
418	bzero(&pad, sizeof(pad));
419
420	HMAC_CTX_init(&ctx);
421	HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1());
422
423	leaselen = sizeof(lv);
424	padlen = DHCP_ALIGN(leaselen) - leaselen;
425
426	/* Add DHCP sync packet header */
427	hdr.sh_version = DHCP_SYNC_VERSION;
428	hdr.sh_af = AF_INET;
429	hdr.sh_counter = sync_counter++;
430	hdr.sh_length = htons(sizeof(hdr) + sizeof(lv) + padlen + sizeof(end));
431	iov[i].iov_base = &hdr;
432	iov[i].iov_len = sizeof(hdr);
433	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
434	i++;
435
436	/* Add single DHCP sync address entry */
437	lv.lv_type = htons(DHCP_SYNC_LEASE);
438	lv.lv_length = htons(leaselen + padlen);
439	lv.lv_timestamp = htonl(lease->timestamp);
440	lv.lv_starts = htonl(lease->starts);
441	lv.lv_ends =  htonl(lease->ends);
442	memcpy(&lv.lv_ip_addr, &lease->ip_addr, sizeof(lv.lv_ip_addr));
443	memcpy(&lv.lv_hardware_addr, &lease->hardware_addr,
444	    sizeof(lv.lv_hardware_addr));
445	note("sending DHCP_SYNC_LEASE for hw %s -> ip %s, start %d, end %d",
446	    print_hw_addr(lv.lv_hardware_addr.htype, lv.lv_hardware_addr.hlen,
447	    lv.lv_hardware_addr.haddr), piaddr(lease->ip_addr),
448	    ntohl(lv.lv_starts), ntohl(lv.lv_ends));
449	iov[i].iov_base = &lv;
450	iov[i].iov_len = sizeof(lv);
451	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
452	i++;
453
454	iov[i].iov_base = pad;
455	iov[i].iov_len = padlen;
456	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
457	i++;
458
459	/* Add end marker */
460	end.st_type = htons(DHCP_SYNC_END);
461	end.st_length = htons(sizeof(end));
462	iov[i].iov_base = &end;
463	iov[i].iov_len = sizeof(end);
464	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
465	i++;
466
467	HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len);
468
469	/* Send message to the target hosts */
470	sync_send(iov, i);
471	HMAC_CTX_cleanup(&ctx);
472}
473