/* $OpenBSD: sync.c,v 1.7 2008/09/15 20:38:17 claudio Exp $ */ /* * Copyright (c) 2008 Bob Beck * Copyright (c) 2006, 2007 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dhcpd.h" #include "sync.h" int sync_debug; u_int32_t sync_counter; int syncfd = -1; int sendmcast; struct sockaddr_in sync_in; struct sockaddr_in sync_out; static char *sync_key; struct sync_host { LIST_ENTRY(sync_host) h_entry; char *h_name; struct sockaddr_in sh_addr; }; LIST_HEAD(synchosts, sync_host) sync_hosts = LIST_HEAD_INITIALIZER(sync_hosts); void sync_send(struct iovec *, int); void sync_addr(time_t, time_t, char *, u_int16_t); int sync_addhost(const char *name, u_short port) { struct addrinfo hints, *res, *res0; struct sync_host *shost; struct sockaddr_in *addr = NULL; bzero(&hints, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(name, NULL, &hints, &res0) != 0) return (EINVAL); for (res = res0; res != NULL; res = res->ai_next) { if (addr == NULL && res->ai_family == AF_INET) { addr = (struct sockaddr_in *)res->ai_addr; break; } } if (addr == NULL) { freeaddrinfo(res0); return (EINVAL); } if ((shost = (struct sync_host *) calloc(1, sizeof(struct sync_host))) == NULL) { freeaddrinfo(res0); return (ENOMEM); } if ((shost->h_name = strdup(name)) == NULL) { free(shost); freeaddrinfo(res0); return (ENOMEM); } shost->sh_addr.sin_family = AF_INET; shost->sh_addr.sin_port = htons(port); shost->sh_addr.sin_addr.s_addr = addr->sin_addr.s_addr; freeaddrinfo(res0); LIST_INSERT_HEAD(&sync_hosts, shost, h_entry); if (sync_debug) syslog_r(LOG_DEBUG, &sdata, "added dhcp sync host %s " "(address %s, port %d)\n", shost->h_name, inet_ntoa(shost->sh_addr.sin_addr), port); return (0); } int sync_init(const char *iface, const char *baddr, u_short port) { int one = 1; u_int8_t ttl; struct ifreq ifr; struct ip_mreq mreq; struct sockaddr_in *addr; char ifnam[IFNAMSIZ], *ttlstr; const char *errstr; struct in_addr ina; if (iface != NULL) sendmcast++; bzero(&ina, sizeof(ina)); if (baddr != NULL) { if (inet_pton(AF_INET, baddr, &ina) != 1) { ina.s_addr = htonl(INADDR_ANY); if (iface == NULL) iface = baddr; else if (iface != NULL && strcmp(baddr, iface) != 0) { fprintf(stderr, "multicast interface does " "not match"); return (-1); } } } sync_key = SHA1File(DHCP_SYNC_KEY, NULL); if (sync_key == NULL) { if (errno != ENOENT) { fprintf(stderr, "failed to open sync key: %s\n", strerror(errno)); return (-1); } /* Use empty key by default */ sync_key = ""; } syncfd = socket(AF_INET, SOCK_DGRAM, 0); if (syncfd == -1) return (-1); if (setsockopt(syncfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) goto fail; bzero(&sync_out, sizeof(sync_out)); sync_out.sin_family = AF_INET; sync_out.sin_len = sizeof(sync_out); sync_out.sin_addr.s_addr = ina.s_addr; if (baddr == NULL && iface == NULL) sync_out.sin_port = 0; else sync_out.sin_port = htons(port); if (bind(syncfd, (struct sockaddr *)&sync_out, sizeof(sync_out)) == -1) goto fail; /* Don't use multicast messages */ if (iface == NULL) return (syncfd); strlcpy(ifnam, iface, sizeof(ifnam)); ttl = DHCP_SYNC_MCASTTTL; if ((ttlstr = strchr(ifnam, ':')) != NULL) { *ttlstr++ = '\0'; ttl = (u_int8_t)strtonum(ttlstr, 1, UINT8_MAX, &errstr); if (errstr) { fprintf(stderr, "invalid multicast ttl %s: %s", ttlstr, errstr); goto fail; } } bzero(&ifr, sizeof(ifr)); strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); if (ioctl(syncfd, SIOCGIFADDR, &ifr) == -1) goto fail; bzero(&sync_in, sizeof(sync_in)); addr = (struct sockaddr_in *)&ifr.ifr_addr; sync_in.sin_family = AF_INET; sync_in.sin_len = sizeof(sync_in); sync_in.sin_addr.s_addr = addr->sin_addr.s_addr; sync_in.sin_port = htons(port); bzero(&mreq, sizeof(mreq)); sync_out.sin_addr.s_addr = inet_addr(DHCP_SYNC_MCASTADDR); mreq.imr_multiaddr.s_addr = inet_addr(DHCP_SYNC_MCASTADDR); mreq.imr_interface.s_addr = sync_in.sin_addr.s_addr; if (setsockopt(syncfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) { fprintf(stderr, "failed to add multicast membership to %s: %s", DHCP_SYNC_MCASTADDR, strerror(errno)); goto fail; } if (setsockopt(syncfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1) { fprintf(stderr, "failed to set multicast ttl to " "%u: %s\n", ttl, strerror(errno)); setsockopt(syncfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); goto fail; } if (sync_debug) syslog_r(LOG_DEBUG, &sdata, "using multicast dhcp sync %smode " "(ttl %u, group %s, port %d)\n", sendmcast ? "" : "receive ", ttl, inet_ntoa(sync_out.sin_addr), port); return (syncfd); fail: close(syncfd); return (-1); } void sync_recv(void) { struct dhcp_synchdr *hdr; struct sockaddr_in addr; struct dhcp_synctlv_hdr *tlv; struct dhcp_synctlv_lease *lv; struct lease *lease; u_int8_t buf[DHCP_SYNC_MAXSIZE]; u_int8_t hmac[2][DHCP_SYNC_HMAC_LEN]; struct lease l, *lp; u_int8_t *p; socklen_t addr_len; ssize_t len; u_int hmac_len; bzero(&addr, sizeof(addr)); bzero(buf, sizeof(buf)); addr_len = sizeof(addr); if ((len = recvfrom(syncfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addr_len)) < 1) return; if (addr.sin_addr.s_addr != htonl(INADDR_ANY) && bcmp(&sync_in.sin_addr, &addr.sin_addr, sizeof(addr.sin_addr)) == 0) return; /* Ignore invalid or truncated packets */ hdr = (struct dhcp_synchdr *)buf; if (len < sizeof(struct dhcp_synchdr) || hdr->sh_version != DHCP_SYNC_VERSION || hdr->sh_af != AF_INET || len < ntohs(hdr->sh_length)) goto trunc; len = ntohs(hdr->sh_length); /* Compute and validate HMAC */ bcopy(hdr->sh_hmac, hmac[0], DHCP_SYNC_HMAC_LEN); bzero(hdr->sh_hmac, DHCP_SYNC_HMAC_LEN); HMAC(EVP_sha1(), sync_key, strlen(sync_key), buf, len, hmac[1], &hmac_len); if (bcmp(hmac[0], hmac[1], DHCP_SYNC_HMAC_LEN) != 0) goto trunc; if (sync_debug) syslog_r(LOG_DEBUG, &sdata, "%s(sync): received packet of %d bytes\n", inet_ntoa(addr.sin_addr), (int)len); p = (u_int8_t *)(hdr + 1); while (len) { tlv = (struct dhcp_synctlv_hdr *)p; if (len < sizeof(struct dhcp_synctlv_hdr) || len < ntohs(tlv->st_length)) goto trunc; switch (ntohs(tlv->st_type)) { case DHCP_SYNC_LEASE: lv = (struct dhcp_synctlv_lease *)tlv; if (sizeof(*lv) > ntohs(tlv->st_length)) goto trunc; if ((lease = find_lease_by_hw_addr( lv->hardware_addr.haddr, lv->hardware_addr.hlen)) == NULL) { if ((lease = find_lease_by_hw_addr( lv->hardware_addr.haddr, lv->hardware_addr.hlen)) == NULL) { lp = &l; memset(lp, 0, sizeof(*lp)); } else lp = lease; } else lp = lease; lp = &l; memset(lp, 0, sizeof(*lp)); lp->timestamp = ntohl(lv->timestamp); lp->starts = ntohl(lv->starts); lp->ends = ntohl(lv->ends); memcpy(&lp->ip_addr, &lv->ip_addr, sizeof(lp->ip_addr)); memcpy(&lp->hardware_addr, &lv->hardware_addr, sizeof(lp->hardware_addr)); syslog_r(LOG_DEBUG, &sdata, "DHCP_SYNC_LEASE from %s for hw %s -> ip %s, " "start %d, end %d", inet_ntoa(addr.sin_addr), print_hw_addr(lp->hardware_addr.htype, lp->hardware_addr.hlen, lp->hardware_addr.haddr), piaddr(lp->ip_addr), lp->starts, lp->ends); /* now whack the lease in there */ if (lease == NULL) { enter_lease(lp); write_leases(); } else if (lease->ends < lp->ends) supersede_lease(lease, lp, 1); else if (lease->ends > lp->ends) /* * our partner sent us a lease * that is older than what we have, * so re-educate them with what we * know is newer. */ sync_lease(lease); break; case DHCP_SYNC_END: goto done; default: printf("invalid type: %d\n", ntohs(tlv->st_type)); goto trunc; } len -= ntohs(tlv->st_length); p = ((u_int8_t *)tlv) + ntohs(tlv->st_length); } done: return; trunc: if (sync_debug) syslog_r(LOG_INFO, &sdata, "%s(sync): truncated or invalid packet\n", inet_ntoa(addr.sin_addr)); } void sync_send(struct iovec *iov, int iovlen) { struct sync_host *shost; struct msghdr msg; if (syncfd == -1) return; /* setup buffer */ bzero(&msg, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = iovlen; if (sendmcast) { if (sync_debug) syslog_r(LOG_DEBUG, &sdata, "sending multicast sync message\n"); msg.msg_name = &sync_out; msg.msg_namelen = sizeof(sync_out); sendmsg(syncfd, &msg, 0); } LIST_FOREACH(shost, &sync_hosts, h_entry) { if (sync_debug) syslog_r(LOG_DEBUG, &sdata, "sending sync message to %s (%s)\n", shost->h_name, inet_ntoa(shost->sh_addr.sin_addr)); msg.msg_name = &shost->sh_addr; msg.msg_namelen = sizeof(shost->sh_addr); sendmsg(syncfd, &msg, 0); } } void sync_lease(struct lease *lease) { struct iovec iov[4]; struct dhcp_synchdr hdr; struct dhcp_synctlv_lease ld; struct dhcp_synctlv_hdr end; char pad[DHCP_ALIGNBYTES]; u_int16_t leaselen, padlen; int i = 0; HMAC_CTX ctx; u_int hmac_len; if (sync_key == NULL) return; bzero(&hdr, sizeof(hdr)); bzero(&ld, sizeof(ld)); bzero(&pad, sizeof(pad)); HMAC_CTX_init(&ctx); HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1()); leaselen = sizeof(ld); padlen = DHCP_ALIGN(leaselen) - leaselen; /* Add DHCP sync packet header */ hdr.sh_version = DHCP_SYNC_VERSION; hdr.sh_af = AF_INET; hdr.sh_counter = sync_counter++; hdr.sh_length = htons(sizeof(hdr) + sizeof(ld) + sizeof(end)); iov[i].iov_base = &hdr; iov[i].iov_len = sizeof(hdr); HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); i++; /* Add single DHCP sync address entry */ ld.type = htons(DHCP_SYNC_LEASE); ld.length = htons(leaselen + padlen); ld.timestamp = htonl(lease->timestamp); ld.starts = htonl(lease->starts); ld.ends = htonl(lease->ends); memcpy(&ld.ip_addr, &lease->ip_addr, sizeof(ld.ip_addr)); memcpy(&ld.hardware_addr, &lease->hardware_addr, sizeof(ld.hardware_addr)); syslog_r(LOG_DEBUG, &sdata, "sending DHCP_SYNC_LEASE for hw %s -> ip %s, start %d, end %d", print_hw_addr(ld.hardware_addr.htype, ld.hardware_addr.hlen, ld.hardware_addr.haddr), piaddr(lease->ip_addr), ntohl(ld.starts), ntohl(ld.ends)); iov[i].iov_base = &ld; iov[i].iov_len = sizeof(ld); HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); i++; iov[i].iov_base = pad; iov[i].iov_len = padlen; HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); i++; /* Add end marker */ end.st_type = htons(DHCP_SYNC_END); end.st_length = htons(sizeof(end)); iov[i].iov_base = &end; iov[i].iov_len = sizeof(end); HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); i++; HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len); /* Send message to the target hosts */ sync_send(iov, i); HMAC_CTX_cleanup(&ctx); }