diff options
author | 2008-05-07 12:19:20 +0000 | |
---|---|---|
committer | 2008-05-07 12:19:20 +0000 | |
commit | 5f515bebfbc9b723b65ade175c407708da91f729 (patch) | |
tree | 0fa55cc0770ab12fb466af1503df536b0b0341a1 | |
parent | Ref to ripd instead of routed. (diff) | |
download | wireguard-openbsd-5f515bebfbc9b723b65ade175c407708da91f729.tar.xz wireguard-openbsd-5f515bebfbc9b723b65ade175c407708da91f729.zip |
Add synchronisation support for dhcpd - this allows for two dhcpd's
with the same configuration to be run on the same net and they will
keep their lease files/state in synch, and therefore allowing you to
run redundant dhcpd's. Synchronization code stolen from spamd, uses
an hmac key in /var/db/dhcpd.key if it exists.
ok krw@ deraadt@
-rw-r--r-- | etc/services | 3 | ||||
-rw-r--r-- | usr.sbin/dhcpd/Makefile | 5 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcp.c | 8 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcpd.8 | 83 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcpd.c | 39 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcpd.h | 3 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dispatch.c | 16 | ||||
-rw-r--r-- | usr.sbin/dhcpd/errwarn.c | 21 | ||||
-rw-r--r-- | usr.sbin/dhcpd/memory.c | 10 | ||||
-rw-r--r-- | usr.sbin/dhcpd/sync.c | 465 | ||||
-rw-r--r-- | usr.sbin/dhcpd/sync.h | 71 |
11 files changed, 698 insertions, 26 deletions
diff --git a/etc/services b/etc/services index 68602c5133d..a05047c5db2 100644 --- a/etc/services +++ b/etc/services @@ -1,4 +1,4 @@ -# $OpenBSD: services,v 1.68 2008/05/06 22:52:03 sthen Exp $ +# $OpenBSD: services,v 1.69 2008/05/07 12:19:20 beck Exp $ # # Network services, Internet style # @@ -277,6 +277,7 @@ icb 7326/tcp # Internet Citizen's Band spamd 8025/tcp # spamd(8) spamd-sync 8025/udp # spamd(8) synchronisation spamd-cfg 8026/tcp # spamd(8) configuration +dhcpd-sync 8067/udp # dhcpd(8) synchronisation hunt 26740/udp # hunt(6) # # Appletalk diff --git a/usr.sbin/dhcpd/Makefile b/usr.sbin/dhcpd/Makefile index 2429bf89325..341ed8a31d4 100644 --- a/usr.sbin/dhcpd/Makefile +++ b/usr.sbin/dhcpd/Makefile @@ -1,13 +1,14 @@ -# $OpenBSD: Makefile,v 1.3 2006/05/31 02:43:15 ckuethe Exp $ +# $OpenBSD: Makefile,v 1.4 2008/05/07 12:19:20 beck Exp $ .include <bsd.own.mk> SRCS= bootp.c confpars.c db.c dhcp.c dhcpd.c bpf.c packet.c errwarn.c \ dispatch.c print.c memory.c options.c inet.c conflex.c parse.c \ - alloc.c tables.c tree.c hash.c convert.c icmp.c pfutils.c + alloc.c tables.c tree.c hash.c convert.c icmp.c pfutils.c sync.c PROG= dhcpd MAN= dhcpd.8 dhcpd.conf.5 dhcpd.leases.5 dhcp-options.5 +LDADD+=-lcrypto CFLAGS+=-Wall .include <bsd.prog.mk> diff --git a/usr.sbin/dhcpd/dhcp.c b/usr.sbin/dhcpd/dhcp.c index b81e1e2330a..c3f5df5901a 100644 --- a/usr.sbin/dhcpd/dhcp.c +++ b/usr.sbin/dhcpd/dhcp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcp.c,v 1.25 2006/12/15 16:03:16 stevesk Exp $ */ +/* $OpenBSD: dhcp.c,v 1.26 2008/05/07 12:19:20 beck Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998, 1999 @@ -39,6 +39,7 @@ */ #include "dhcpd.h" +#include "sync.h" int outstanding_pings; @@ -334,6 +335,7 @@ dhcprequest(struct packet *packet) !memcmp(lease->hardware_addr.haddr, packet->raw->chaddr, packet->raw->hlen)))) { ack_lease(packet, lease, DHCPACK, 0); + sync_lease(lease); return; } @@ -346,8 +348,10 @@ dhcprequest(struct packet *packet) * The thing we probably should not do is to remain silent. * For now, we'll just assign the lease to the client anyway. */ - if (lease) + if (lease) { ack_lease(packet, lease, DHCPACK, 0); + sync_lease(lease); + } } void diff --git a/usr.sbin/dhcpd/dhcpd.8 b/usr.sbin/dhcpd/dhcpd.8 index 89121f57ada..6931a2f4157 100644 --- a/usr.sbin/dhcpd/dhcpd.8 +++ b/usr.sbin/dhcpd/dhcpd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: dhcpd.8,v 1.15 2007/05/31 19:20:23 jmc Exp $ +.\" $OpenBSD: dhcpd.8,v 1.16 2008/05/07 12:19:20 beck Exp $ .\" .\" Copyright (c) 1995, 1996 The Internet Software Consortium. .\" All rights reserved. @@ -36,7 +36,7 @@ .\" see ``http://www.isc.org/''. To learn more about Vixie .\" Enterprises, see ``http://www.vix.com''. .\" -.Dd $Mdocdate: May 31 2007 $ +.Dd $Mdocdate: May 7 2008 $ .Dt DHCPD 8 .Os .Sh NAME @@ -51,6 +51,8 @@ .Op Fl c Ar config-file .Op Fl L Ar leased_ip_table .Op Fl l Ar lease-file +.Op Fl Y Ar synctarget +.Op Fl y Ar synclisten .Op Ar if0 Op Ar ... ifN .Ek .Sh DESCRIPTION @@ -237,6 +239,32 @@ for testing lease files in a non-production environment. .It Fl n Only test configuration, do not run .Nm . +.It Fl Y Ar synctarget +Add target +.Ar synctarget +to receive synchronisation messages. +.Ar synctarget +can be either an IPv4 address for unicast messages +or a network interface name followed optionally by a colon and a numeric TTL +value for multicast messages to the group 224.0.1.240. +If the multicast TTL is not specified, a default value of 1 is used. +This option can be specified multiple times. +See also +.Sx SYNCHRONISATION +below. +.It Fl y Ar synclisten +Listen on +.Ar synclisten +for incoming synchronisation messages. +The format for +.Ar synclisten +is the same as for +.Ar synctarget , +above. +This option can be specified only once. +See also +.Sx SYNCHRONISATION +below. .El .Sh CONFIGURATION The syntax of the @@ -366,6 +394,57 @@ A more complete description of the .Pa dhcpd.conf file syntax is provided in .Xr dhcpd.conf 5 . +.Sh SYNCHRONISATION +.Nm +supports realtime synchronisation of the lease allocations to +a number of +.Nm +daemons running on multiple machines, +using the +.Fl Y +and +.Fl y +options. +.Pp +The following example will accept incoming multicast and unicast +synchronisation messages, and send outgoing multicast messages through +the network interface +.Ar em0 : +.Bd -literal -offset indent +# /usr/sbin/dhcpd -y em0 -Y em0 +.Ed +.Pp +The second example will increase the multicast TTL to a value of 2, +add the unicast targets +.Ar foo.somewhere.org +and +.Ar bar.somewhere.org , +and accept incoming unicast messages sent to +.Ar example.somewhere.org +only. +.Bd -literal -offset indent +# /usr/sbin/dhcpd -y example.somewhere.org -Y em0:2 \e + -Y foo.somewhere.org -Y bar.somewhere.org +.Ed +.Pp +If the file +.Pa /var/db/dhcpd.key +exists, +.Nm +will calculate the message-digest fingerprint (checksum) for the file +and use it as a shared key to authenticate the synchronisation messages. +The file itself can contain any data. +For example, to create a secure random key: +.Bd -literal -offset indent +# dd if=/dev/arandom of=/var/db/dhcpd.key bs=2048 count=1 +.Ed +.Pp +The file needs to be copied to all hosts +sending or receiving synchronisation messages. +.Pp +All hosts using synchronisation must use the same configuration in the +.Pa /etc/dhcpd.conf +file. .Sh FILES .Bl -tag -width "/var/db/dhcpd.leases~ " -compact .It /etc/dhcpd.conf diff --git a/usr.sbin/dhcpd/dhcpd.c b/usr.sbin/dhcpd/dhcpd.c index ede422d2266..163cdb59035 100644 --- a/usr.sbin/dhcpd/dhcpd.c +++ b/usr.sbin/dhcpd/dhcpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpd.c,v 1.34 2007/12/30 13:38:47 sobrado Exp $ */ +/* $OpenBSD: dhcpd.c,v 1.35 2008/05/07 12:19:20 beck Exp $ */ /* * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org> @@ -40,7 +40,9 @@ */ #include "dhcpd.h" +#include "sync.h" +#include <err.h> #include <pwd.h> void usage(void); @@ -56,24 +58,35 @@ int log_priority; int log_perror = 0; int pfpipe[2]; int gotpipe = 0; +int syncrecv; +int syncsend; +int syncfd = -1; pid_t pfproc_pid = -1; +u_short sync_port; char *path_dhcpd_conf = _PATH_DHCPD_CONF; char *path_dhcpd_db = _PATH_DHCPD_DB; char *abandoned_tab = NULL; char *changedmac_tab = NULL; char *leased_tab = NULL; +struct syslog_data sdata = SYSLOG_DATA_INIT; int main(int argc, char *argv[]) { int ch, cftest = 0, daemonize = 1; extern char *__progname; + char *sync_iface = NULL; + char *sync_baddr = NULL; + struct servent *ent; /* Initially, log errors to stderr as well as to syslogd. */ - openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY); - setlogmask(LOG_UPTO(LOG_INFO)); + openlog_r(__progname, LOG_PID | LOG_NDELAY, DHCPD_LOG_FACILITY, &sdata); - while ((ch = getopt(argc, argv, "A:C:L:c:dfl:n")) != -1) + if ((ent = getservbyname("dhcpd-sync", "udp")) == NULL) + errx(1, "Can't find service \"dhcpd-sync\" in /etc/services"); + sync_port = ntohs(ent->s_port); + + while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nY:y:")) != -1) switch (ch) { case 'A': abandoned_tab = optarg; @@ -102,6 +115,15 @@ main(int argc, char *argv[]) cftest = 1; log_perror = 1; break; + case 'Y': + if (sync_addhost(optarg, sync_port) != 0) + sync_iface = optarg; + syncsend++; + break; + case 'y': + sync_baddr = optarg; + syncrecv++; + break; default: usage(); } @@ -133,10 +155,17 @@ main(int argc, char *argv[]) if (cftest) exit(0); + if (syncsend || syncrecv) { + syncfd = sync_init(sync_iface, sync_baddr, sync_port); + if (syncfd == -1) + err(1, "sync init"); + } + db_startup(); discover_interfaces(); icmp_startup(1, lease_pinged); + if ((pw = getpwnam("_dhcp")) == NULL) error("user \"_dhcp\" not found"); @@ -190,7 +219,7 @@ usage(void) fprintf(stderr, "usage: %s [-dfn] [-A abandoned_ip_table]", __progname); fprintf(stderr, " [-C changed_ip_table]\n"); fprintf(stderr, "\t[-c config-file] [-L leased_ip_table]"); - fprintf(stderr, " [-l lease-file]\n"); + fprintf(stderr, " [-l lease-file] [-Y synctarget] [-y synclisten]\n"); fprintf(stderr, "\t[if0 [... ifN]]\n"); exit(1); } diff --git a/usr.sbin/dhcpd/dhcpd.h b/usr.sbin/dhcpd/dhcpd.h index 3472ab40f27..46e0f77e3aa 100644 --- a/usr.sbin/dhcpd/dhcpd.h +++ b/usr.sbin/dhcpd/dhcpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpd.h,v 1.35 2008/01/18 20:14:03 krw Exp $ */ +/* $OpenBSD: dhcpd.h,v 1.36 2008/05/07 12:19:20 beck Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998, 1999 @@ -721,3 +721,4 @@ void pf_kill_state(int, struct in_addr); size_t atomicio(ssize_t (*)(int, void *, size_t), int, void *, size_t); #define vwrite (ssize_t (*)(int, void *, size_t))write void pfmsg(char, struct lease *); +extern struct syslog_data sdata; diff --git a/usr.sbin/dhcpd/dispatch.c b/usr.sbin/dhcpd/dispatch.c index 1d371e3c153..35bedebdbef 100644 --- a/usr.sbin/dhcpd/dispatch.c +++ b/usr.sbin/dhcpd/dispatch.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dispatch.c,v 1.22 2006/12/12 19:38:55 stevesk Exp $ */ +/* $OpenBSD: dispatch.c,v 1.23 2008/05/07 12:19:20 beck Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998, 1999 @@ -39,11 +39,14 @@ */ #include "dhcpd.h" +#include "sync.h" #include <ifaddrs.h> #include <sys/ioctl.h> #include <poll.h> #include <net/if_media.h> +extern int syncfd; + struct interface_info *interfaces; struct protocol *protocols; struct dhcpd_timeout *timeouts; @@ -273,6 +276,8 @@ dispatch(void) for (nfds = 0, l = protocols; l; l = l->next) nfds++; + if (syncfd != -1) + nfds++; if (nfds > nfds_max) { fds = realloc(fds, nfds * sizeof(struct pollfd)); if (fds == NULL) @@ -321,9 +326,16 @@ another: ++i; } } + if (i == 0) error("No live interfaces to poll on - exiting."); + if (syncfd != -1) { + /* add syncer */ + fds[i].fd = syncfd; + fds[i].events = POLLIN; + } + /* Wait for a packet or a timeout... */ switch (poll(fds, nfds, to_msec)) { case -1: @@ -346,6 +358,8 @@ another: } ++i; } + if ((syncfd != -1) && (fds[i].revents & (POLLIN | POLLHUP))) + sync_recv(); interfaces_invalidated = 0; } } diff --git a/usr.sbin/dhcpd/errwarn.c b/usr.sbin/dhcpd/errwarn.c index 70bc1830594..050eb230076 100644 --- a/usr.sbin/dhcpd/errwarn.c +++ b/usr.sbin/dhcpd/errwarn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: errwarn.c,v 1.7 2007/03/02 11:37:53 henning Exp $ */ +/* $OpenBSD: errwarn.c,v 1.8 2008/05/07 12:19:20 beck Exp $ */ /* Errors and warnings... */ @@ -73,13 +73,13 @@ error(char *fmt, ...) write(STDERR_FILENO, mbuf, strlen(mbuf)); write(STDERR_FILENO, "\n", 1); } else - syslog(log_priority | LOG_ERR, "%s", mbuf); + syslog_r(log_priority | LOG_ERR, &sdata, "%s", mbuf); if (log_perror) { fprintf(stderr, "exiting.\n"); fflush(stderr); } else - syslog(LOG_CRIT, "exiting."); + syslog_r(LOG_CRIT, &sdata, "exiting."); exit(1); } @@ -102,7 +102,7 @@ warning(char *fmt, ...) write(STDERR_FILENO, mbuf, strlen(mbuf)); write(STDERR_FILENO, "\n", 1); } else - syslog(log_priority | LOG_ERR, "%s", mbuf); + syslog_r(log_priority | LOG_ERR, &sdata, "%s", mbuf); return (0); } @@ -125,7 +125,7 @@ note(char *fmt, ...) write(STDERR_FILENO, mbuf, strlen(mbuf)); write(STDERR_FILENO, "\n", 1); } else - syslog(log_priority | LOG_INFO, "%s", mbuf); + syslog_r(log_priority | LOG_INFO, &sdata, "%s", mbuf); return (0); } @@ -148,7 +148,7 @@ debug(char *fmt, ...) write(STDERR_FILENO, mbuf, strlen(mbuf)); write(STDERR_FILENO, "\n", 1); } else - syslog(log_priority | LOG_DEBUG, "%s", mbuf); + syslog_r(log_priority | LOG_DEBUG, &sdata, "%s", mbuf); return (0); } @@ -168,7 +168,7 @@ do_percentm(char *obuf, size_t size, char *ibuf) /* * We wouldn't need this mess if printf handled %m, or if - * strerror() had been invented before syslog(). + * strerror() had been invented before syslog_r(). */ for (fmt_left = size; (ch = *s); ++s) { if (ch == '%' && s[1] == 'm') { @@ -226,10 +226,11 @@ parse_warn(char *fmt, ...) } writev(STDERR_FILENO, iov, iovcnt); } else { - syslog(log_priority | LOG_ERR, "%s", mbuf); - syslog(log_priority | LOG_ERR, "%s", token_line); + syslog_r(log_priority | LOG_ERR, &sdata, "%s", mbuf); + syslog_r(log_priority | LOG_ERR, &sdata, "%s", token_line); if (lexchar < 81) - syslog(log_priority | LOG_ERR, "%*c", lexchar, '^'); + syslog_r(log_priority | LOG_ERR, &sdata, "%*c", lexchar, + '^'); } warnings_occurred = 1; diff --git a/usr.sbin/dhcpd/memory.c b/usr.sbin/dhcpd/memory.c index a2884ba4689..84714112e7a 100644 --- a/usr.sbin/dhcpd/memory.c +++ b/usr.sbin/dhcpd/memory.c @@ -1,4 +1,4 @@ -/* $OpenBSD: memory.c,v 1.14 2006/08/09 22:23:53 cloder Exp $ */ +/* $OpenBSD: memory.c,v 1.15 2008/05/07 12:19:20 beck Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. @@ -39,6 +39,7 @@ */ #include "dhcpd.h" +#include "sync.h" struct subnet *subnets; static struct shared_network *shared_networks; @@ -52,6 +53,8 @@ static struct lease *dangling_leases; static struct hash_table *vendor_class_hash; static struct hash_table *user_class_hash; +extern int syncsend; + void enter_host(struct host_decl *hd) { @@ -843,9 +846,12 @@ write_leases(void) for (s = shared_networks; s; s = s->next) { for (l = s->leases; l; l = l->next) { if (l->hardware_addr.hlen || l->uid_len || - (l->flags & ABANDONED_LEASE)) + (l->flags & ABANDONED_LEASE)) { if (!write_lease(l)) error("Can't rewrite lease database"); + if (syncsend) + sync_lease(l); + } } } if (!commit_leases()) diff --git a/usr.sbin/dhcpd/sync.c b/usr.sbin/dhcpd/sync.c new file mode 100644 index 00000000000..58c776608c1 --- /dev/null +++ b/usr.sbin/dhcpd/sync.c @@ -0,0 +1,465 @@ +/* $OpenBSD: sync.c,v 1.1 2008/05/07 12:19:20 beck 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/param.h> +#include <sys/stdint.h> +#include <sys/file.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <sys/resource.h> +#include <sys/uio.h> +#include <sys/ioctl.h> +#include <sys/queue.h> + + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sha1.h> +#include <syslog.h> + +#include <netdb.h> + +#include <openssl/hmac.h> + +#include "dhcpd.h" +#include "sync.h" + +int sync_debug; + +u_int32_t sync_counter; +int syncfd; +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)) < 0) { + 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; + + /* 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[3]; + struct dhcp_synchdr hdr; + struct dhcp_synctlv_lease ld; + struct dhcp_synctlv_hdr end; + int i = 0; + HMAC_CTX ctx; + u_int hmac_len; + + bzero(&hdr, sizeof(hdr)); + bzero(&ld, sizeof(ld)); + + HMAC_CTX_init(&ctx); + HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1()); + + /* 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(sizeof(ld)); + 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++; + + /* 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); +} diff --git a/usr.sbin/dhcpd/sync.h b/usr.sbin/dhcpd/sync.h new file mode 100644 index 00000000000..af564712919 --- /dev/null +++ b/usr.sbin/dhcpd/sync.h @@ -0,0 +1,71 @@ +/* $OpenBSD: sync.h,v 1.1 2008/05/07 12:19:20 beck 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. + */ + +#ifndef _DHCPD_SYNC +#define _DHCPD_SYNC + +/* + * dhcpd(8) synchronisation protocol. + * + * This protocol has been designed for realtime synchronisation between + * multiple machines running dhcpd(8), running the same config. + * It is a simple Type-Length-Value based protocol, it allows easy + * extension with future subtypes and bulk transfers by sending multiple + * entries at once. The unencrypted messages will be authenticated using + * HMAC-SHA1. + * + */ + +#define DHCP_SYNC_VERSION 1 +#define DHCP_SYNC_MCASTADDR "224.0.1.240" /* XXX choose valid address */ +#define DHCP_SYNC_MCASTTTL IP_DEFAULT_MULTICAST_TTL +#define DHCP_SYNC_HMAC_LEN 20 /* SHA1 */ +#define DHCP_SYNC_MAXSIZE 1408 +#define DHCP_SYNC_KEY "/var/db/dhcpd.key" + +struct dhcp_synchdr { + u_int8_t sh_version; + u_int8_t sh_af; + u_int16_t sh_length; + u_int32_t sh_counter; + u_int8_t sh_hmac[DHCP_SYNC_HMAC_LEN]; + u_int16_t sh_pad[2]; +} __packed; + +struct dhcp_synctlv_hdr { + u_int16_t st_type; + u_int16_t st_length; +} __packed; + +struct dhcp_synctlv_lease { + u_int16_t type; + u_int16_t length; + u_int32_t starts, ends, timestamp; + struct iaddr ip_addr; + struct hardware hardware_addr; +} __packed; + +#define DHCP_SYNC_END 0x0000 +#define DHCP_SYNC_LEASE 0x0001 + +extern int sync_init(const char *, const char *, u_short); +extern int sync_addhost(const char *, u_short); +extern void sync_recv(void); +extern void sync_lease(struct lease *); +#endif /* _DHCPD_SYNC */ |