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