xref: /openbsd-src/usr.sbin/dhcpd/sync.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: sync.c,v 1.10 2010/12/23 17:38:04 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 
44 #include <netdb.h>
45 
46 #include <openssl/hmac.h>
47 
48 #include "dhcpd.h"
49 #include "sync.h"
50 
51 int sync_debug;
52 
53 u_int32_t sync_counter;
54 int syncfd = -1;
55 int sendmcast;
56 
57 struct sockaddr_in sync_in;
58 struct sockaddr_in sync_out;
59 static char *sync_key;
60 
61 struct sync_host {
62 	LIST_ENTRY(sync_host)	h_entry;
63 
64 	char			*h_name;
65 	struct sockaddr_in	sh_addr;
66 };
67 LIST_HEAD(synchosts, sync_host) sync_hosts = LIST_HEAD_INITIALIZER(sync_hosts);
68 
69 void	 sync_send(struct iovec *, int);
70 
71 int
72 sync_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 
119 int
120 sync_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 
240 void
241 sync_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->hardware_addr.haddr,
304 				    lv->hardware_addr.hlen)) == NULL) {
305 				if ((lease = find_lease_by_hw_addr(
306 					    lv->hardware_addr.haddr,
307 					    lv->hardware_addr.hlen)) == NULL) {
308 					lp = &l;
309 					memset(lp, 0, sizeof(*lp));
310 				} else
311 					lp = lease;
312 			} else
313 				lp = lease;
314 
315 			lp = &l;
316 			memset(lp, 0, sizeof(*lp));
317 			lp->timestamp = ntohl(lv->timestamp);
318 			lp->starts = ntohl(lv->starts);
319 			lp->ends = ntohl(lv->ends);
320 			memcpy(&lp->ip_addr, &lv->ip_addr,
321 			    sizeof(lp->ip_addr));
322 			memcpy(&lp->hardware_addr, &lv->hardware_addr,
323 			    sizeof(lp->hardware_addr));
324 			note("DHCP_SYNC_LEASE from %s for hw %s -> ip %s, "
325 			    "start %d, end %d",
326 			    inet_ntoa(addr.sin_addr),
327 			    print_hw_addr(lp->hardware_addr.htype,
328 			    lp->hardware_addr.hlen, lp->hardware_addr.haddr),
329 			    piaddr(lp->ip_addr), lp->starts, lp->ends);
330 			/* now whack the lease in there */
331 			if (lease == NULL) {
332 				enter_lease(lp);
333 				write_leases();
334 			}
335 			else if (lease->ends < lp->ends)
336 				supersede_lease(lease, lp, 1);
337 			else if (lease->ends > lp->ends)
338 				/*
339 				 * our partner sent us a lease
340 				 * that is older than what we have,
341 				 * so re-educate them with what we
342 				 * know is newer.
343 				 */
344 				sync_lease(lease);
345 			break;
346 		case DHCP_SYNC_END:
347 			goto done;
348 		default:
349 			printf("invalid type: %d\n", ntohs(tlv->st_type));
350 			goto trunc;
351 		}
352 		len -= ntohs(tlv->st_length);
353 		p = ((u_int8_t *)tlv) + ntohs(tlv->st_length);
354 	}
355 
356  done:
357 	return;
358 
359  trunc:
360 	if (sync_debug)
361 		note("%s(sync): truncated or invalid packet\n",
362 		    inet_ntoa(addr.sin_addr));
363 }
364 
365 void
366 sync_send(struct iovec *iov, int iovlen)
367 {
368 	struct sync_host *shost;
369 	struct msghdr msg;
370 
371 	if (syncfd == -1)
372 		return;
373 
374 	/* setup buffer */
375 	bzero(&msg, sizeof(msg));
376 	msg.msg_iov = iov;
377 	msg.msg_iovlen = iovlen;
378 
379 	if (sendmcast) {
380 		if (sync_debug)
381 			note("sending multicast sync message\n");
382 		msg.msg_name = &sync_out;
383 		msg.msg_namelen = sizeof(sync_out);
384 		if (sendmsg(syncfd, &msg, 0) == -1)
385 			warning("sending multicast sync message failed: %m");
386 	}
387 
388 	LIST_FOREACH(shost, &sync_hosts, h_entry) {
389 		if (sync_debug)
390 			note("sending sync message to %s (%s)\n",
391 			    shost->h_name, inet_ntoa(shost->sh_addr.sin_addr));
392 		msg.msg_name = &shost->sh_addr;
393 		msg.msg_namelen = sizeof(shost->sh_addr);
394 		if (sendmsg(syncfd, &msg, 0) == -1)
395 			warning("sending sync message failed: %m");
396 	}
397 }
398 
399 void
400 sync_lease(struct lease *lease)
401 {
402 	struct iovec iov[4];
403 	struct dhcp_synchdr hdr;
404 	struct dhcp_synctlv_lease ld;
405 	struct dhcp_synctlv_hdr end;
406 	char pad[DHCP_ALIGNBYTES];
407 	u_int16_t leaselen, padlen;
408 	int i = 0;
409 	HMAC_CTX ctx;
410 	u_int hmac_len;
411 
412 	if (sync_key == NULL)
413 		return;
414 
415 	bzero(&hdr, sizeof(hdr));
416 	bzero(&ld, sizeof(ld));
417 	bzero(&pad, sizeof(pad));
418 
419 	HMAC_CTX_init(&ctx);
420 	HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1());
421 
422 	leaselen = sizeof(ld);
423 	padlen = DHCP_ALIGN(leaselen) - leaselen;
424 
425 	/* Add DHCP sync packet header */
426 	hdr.sh_version = DHCP_SYNC_VERSION;
427 	hdr.sh_af = AF_INET;
428 	hdr.sh_counter = sync_counter++;
429 	hdr.sh_length = htons(sizeof(hdr) + sizeof(ld) + sizeof(end));
430 	iov[i].iov_base = &hdr;
431 	iov[i].iov_len = sizeof(hdr);
432 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
433 	i++;
434 
435 	/* Add single DHCP sync address entry */
436 	ld.type = htons(DHCP_SYNC_LEASE);
437 	ld.length = htons(leaselen + padlen);
438 	ld.timestamp = htonl(lease->timestamp);
439 	ld.starts = htonl(lease->starts);
440 	ld.ends =  htonl(lease->ends);
441 	memcpy(&ld.ip_addr, &lease->ip_addr, sizeof(ld.ip_addr));
442 	memcpy(&ld.hardware_addr, &lease->hardware_addr,
443 	    sizeof(ld.hardware_addr));
444 	note("sending DHCP_SYNC_LEASE for hw %s -> ip %s, start %d, end %d",
445 	    print_hw_addr(ld.hardware_addr.htype, ld.hardware_addr.hlen,
446 	    ld.hardware_addr.haddr), piaddr(lease->ip_addr), ntohl(ld.starts),
447 	    ntohl(ld.ends));
448 	iov[i].iov_base = &ld;
449 	iov[i].iov_len = sizeof(ld);
450 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
451 	i++;
452 
453 	iov[i].iov_base = pad;
454 	iov[i].iov_len = padlen;
455 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
456 	i++;
457 
458 	/* Add end marker */
459 	end.st_type = htons(DHCP_SYNC_END);
460 	end.st_length = htons(sizeof(end));
461 	iov[i].iov_base = &end;
462 	iov[i].iov_len = sizeof(end);
463 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
464 	i++;
465 
466 	HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len);
467 
468 	/* Send message to the target hosts */
469 	sync_send(iov, i);
470 	HMAC_CTX_cleanup(&ctx);
471 }
472