File: [local] / src / usr.sbin / dhcpd / sync.c (download)
Revision 1.24, Wed Jan 5 11:01:59 2022 UTC (2 years, 5 months ago) by tb
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, HEAD Changes since 1.23: +19 -10 lines
dhcpd: straightforward conversion to HMAC_CTX on the heap, similar
to what was done in spamd a while back.
ok florian
|
/* $OpenBSD: sync.c,v 1.24 2022/01/05 11:01:59 tb Exp $ */
/*
* Copyright (c) 2008 Bob Beck <beck@openbsd.org>
* Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
*
* 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 <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <openssl/hmac.h>
#include <errno.h>
#include <netdb.h>
#include <sha1.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "dhcp.h"
#include "tree.h"
#include "dhcpd.h"
#include "log.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);
int
sync_addhost(const char *name, u_short port)
{
struct addrinfo hints, *res, *res0;
struct sync_host *shost;
struct sockaddr_in *addr = NULL;
memset(&hints, 0, 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);
}
shost->h_name = strdup(name);
if (shost->h_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)
log_info("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++;
memset(&ina, 0, 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) {
log_warn("failed to open sync key");
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;
memset(&sync_out, 0, 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;
}
}
memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name));
if (ioctl(syncfd, SIOCGIFADDR, &ifr) == -1)
goto fail;
memset(&sync_in, 0, 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);
memset(&mreq, 0, 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) {
log_warn("failed to add multicast membership to %s",
DHCP_SYNC_MCASTADDR);
goto fail;
}
if (setsockopt(syncfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
sizeof(ttl)) == -1) {
log_warn("failed to set multicast ttl to %u", ttl);
setsockopt(syncfd, IPPROTO_IP,
IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
goto fail;
}
if (sync_debug)
log_debug("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;
memset(&addr, 0, sizeof(addr));
memset(buf, 0, 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 */
memcpy(hmac[0], hdr->sh_hmac, DHCP_SYNC_HMAC_LEN);
explicit_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)
log_info("%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;
lease = find_lease_by_hw_addr(
lv->lv_hardware_addr.haddr,
lv->lv_hardware_addr.hlen);
if (lease == NULL)
lease = find_lease_by_ip_addr(lv->lv_ip_addr);
lp = &l;
memset(lp, 0, sizeof(*lp));
lp->timestamp = ntohl(lv->lv_timestamp);
lp->starts = ntohl(lv->lv_starts);
lp->ends = ntohl(lv->lv_ends);
memcpy(&lp->ip_addr, &lv->lv_ip_addr,
sizeof(lp->ip_addr));
memcpy(&lp->hardware_addr, &lv->lv_hardware_addr,
sizeof(lp->hardware_addr));
log_info("DHCP_SYNC_LEASE from %s for hw %s -> ip %s, "
"start %lld, end %lld",
inet_ntoa(addr.sin_addr),
print_hw_addr(lp->hardware_addr.htype,
lp->hardware_addr.hlen, lp->hardware_addr.haddr),
piaddr(lp->ip_addr),
(long long)lp->starts, (long long)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)
log_info("%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 */
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = iovlen;
if (sendmcast) {
if (sync_debug)
log_info("sending multicast sync message\n");
msg.msg_name = &sync_out;
msg.msg_namelen = sizeof(sync_out);
if (sendmsg(syncfd, &msg, 0) == -1)
log_warn("sending multicast sync message failed");
}
LIST_FOREACH(shost, &sync_hosts, h_entry) {
if (sync_debug)
log_info("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);
if (sendmsg(syncfd, &msg, 0) == -1)
log_warn("sending sync message failed");
}
}
void
sync_lease(struct lease *lease)
{
struct iovec iov[4];
struct dhcp_synchdr hdr;
struct dhcp_synctlv_lease lv;
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;
memset(&hdr, 0, sizeof(hdr));
memset(&lv, 0, sizeof(lv));
memset(&pad, 0, sizeof(pad));
if ((ctx = HMAC_CTX_new()) == NULL)
goto bad;
if (!HMAC_Init_ex(ctx, sync_key, strlen(sync_key), EVP_sha1(), NULL))
goto bad;
leaselen = sizeof(lv);
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(lv) + padlen + sizeof(end));
iov[i].iov_base = &hdr;
iov[i].iov_len = sizeof(hdr);
if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
goto bad;
i++;
/* Add single DHCP sync address entry */
lv.lv_type = htons(DHCP_SYNC_LEASE);
lv.lv_length = htons(leaselen + padlen);
lv.lv_timestamp = htonl(lease->timestamp);
lv.lv_starts = htonl(lease->starts);
lv.lv_ends = htonl(lease->ends);
memcpy(&lv.lv_ip_addr, &lease->ip_addr, sizeof(lv.lv_ip_addr));
memcpy(&lv.lv_hardware_addr, &lease->hardware_addr,
sizeof(lv.lv_hardware_addr));
log_info("sending DHCP_SYNC_LEASE for hw %s -> ip %s, start %d, "
"end %d", print_hw_addr(lv.lv_hardware_addr.htype,
lv.lv_hardware_addr.hlen, lv.lv_hardware_addr.haddr),
piaddr(lease->ip_addr), ntohl(lv.lv_starts), ntohl(lv.lv_ends));
iov[i].iov_base = &lv;
iov[i].iov_len = sizeof(lv);
if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
goto bad;
i++;
iov[i].iov_base = pad;
iov[i].iov_len = padlen;
if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
goto bad;
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);
if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
goto bad;
i++;
if (!HMAC_Final(ctx, hdr.sh_hmac, &hmac_len))
goto bad;
/* Send message to the target hosts */
sync_send(iov, i);
bad:
HMAC_CTX_free(ctx);
}