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