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