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