pax_global_header00006660000000000000000000000064116702364760014526gustar00rootroot0000000000000052 comment=690c927eb22d8b0759391024b1d02ac57fcaad7f ahcpd-0.53/000077500000000000000000000000001167023647600125345ustar00rootroot00000000000000ahcpd-0.53/CHANGES000066400000000000000000000042151167023647600135310ustar00rootroot000000000000008 December 2011: ahcpd 0.53 * Fixed a longstanding bug that would cause a client to immediately deconfigure itself after successfully renewing a lease. * Set AHCP packets' TOS field as network control (thanks to Dave Taht). 28 July 2011: ahcpd 0.52 * Port to OpenBSD, by Vincent Gross. * Tweaks for compatibility Mac OS X, thanks to Matthieu Boutier. * Fixed incorrect testing of the prefix length of received IPv6 prefixes, thanks to Gabriel Kerneis. 29 January 2010: ahcpd 0.51 * Servers are now configured with a plain-text configuration file. * Implemented client-side support for prefix delegation (-P). * Made requesting state more persistent, to deal with packet loss. * Fixed a typo that prevented the -I option from working. * Fixed compilation on BSD systems. * Fixed a bug that could cause an expired lease to be discarded when older ones are available. 11 August 2009: ahcpd 0.50 * Complete redesign of AHCP as a client-server protocol layered over a built-in multicast routing layer. This protocol (version 1) is completely incompatible with the old (version 0) protocol. 25 May 2008: ahcpd 0.5 * Made ahcpd gracefully survive interfaces going up/down or being renumbered, as usually happens when tunnels or VPNs are started or stopped. * Implemented the ability to run as a daemon. * Implemented the ability to try multiple stateful servers. * Implemented the flag -r, which prevents the routing protocol from being started. 17 February 2008: ahcpd 0.4 * Implemented stateful configuration for IPv4. * Tweaked timing to avoid hopping between competing configurations. * Fixed conversion of Babel hello intervals to seconds. 28 October 2007: ahcpd 0.3 * Moved user-serviceable parts to /etc/ahcp/ * Added support for configuring an NTP server. * Made ahcp print warnings when a clock inconsistency is detected. * Fixed error handling when the configuration script dies unexpectedly. 27 September 2007: ahcpd 0.2 * Fixed a bug in computation of validity time that would cause ahcpd to be much more chatty than necessary. 22 August 2007: ahcpd 0.1 * Initial public release. ahcpd-0.53/LICENCE000066400000000000000000000020571167023647600135250ustar00rootroot00000000000000Copyright (c) 2007-2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ahcpd-0.53/Makefile000066400000000000000000000025721167023647600142020ustar00rootroot00000000000000PREFIX = /usr/local CDEBUGFLAGS = -Os -g -Wall DEFINES = $(PLATFORM_DEFINES) CFLAGS = $(CDEBUGFLAGS) $(DEFINES) $(EXTRA_DEFINES) SRCS = ahcpd.c monotonic.c transport.c prefix.c configure.c config.c lease.c OBJS = ahcpd.o monotonic.o transport.o prefix.o configure.o config.o lease.o LDLIBS = -lrt ahcpd: $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o ahcpd $(OBJS) $(LDLIBS) .SUFFIXES: .man .html .man.html: rman -f html $< | \ sed -e "s|\\(.*(8)\\)|\1|" \ > $@ ahcpd.html: ahcpd.man .PHONY: all install.minimal install all: ahcpd install.minimal: all mkdir -p $(TARGET)$(PREFIX)/bin/ -rm -f $(TARGET)$(PREFIX)/bin/ahcpd cp ahcpd $(TARGET)$(PREFIX)/bin/ mkdir -p $(TARGET)/etc/ahcp/ -rm -f $(TARGET)/etc/ahcp/ahcp-config.sh cp ahcp-config.sh $(TARGET)/etc/ahcp/ chmod +x $(TARGET)/etc/ahcp/ahcp-config.sh install: all install.minimal mkdir -p $(TARGET)$(PREFIX)/man/man8/ cp -f ahcpd.man $(TARGET)$(PREFIX)/man/man8/ahcpd.8 .PHONY: uninstall uninstall: -rm -f $(TARGET)$(PREFIX)/bin/ahcpd -rm -f $(TARGET)$(PREFIX)/bin/ahcp-config.sh -rm -f $(TARGET)$(PREFIX)/bin/ahcp-dummy-config.sh -rm -f $(TARGET)$(PREFIX)/man/man8/ahcpd.8 .PHONY: clean clean: -rm -f ahcpd -rm -f *.o *~ core TAGS gmon.out -rm -f ahcpd.html ahcpd-0.53/README000066400000000000000000000036721167023647600134240ustar00rootroot00000000000000Ahcpd ===== Ahcpd is a daemon for configuring an IPv6 network using the Ad-Hoc Configuration Protocol (AHCP). AHCP is designed for wireless mesh networks, where DHCP and IPv6 autoconfiguration do not work, but could in principle also be used on wired networks. Installation ============ $ make # make install If compiling for OpenWRT, you will probably want to say something like $ make CC=mipsel-linux-gcc PLATFORM_DEFINES='-march=mips32' You can reduce the size of the ahcpd binary by omitting server functionality; to do so, specify $ make EXTRA_DEFINES=-DNO_SERVER Setting up a server =================== You need to set up one or more authoritative servers in your network. The server should be run as $ ahcpd -c /etc/ahcpd.conf wlan0 where the configuration file /etc/ahcpd.conf should say something like mode server prefix fde6:20f5:c9ac:358::/64 prefix 192.168.4.128/25 lease-dir /var/lib/leases name-server fde6:20f5:c9ac:358::1 name-server 192.168.4.1 ntp-server 192.168.4.2 If you want ahcpd to fork into the background, pass it the ``-D'' flag. The server does not need to run ahcpd as root, as long as it can write the lease database and the pidfile. The server should synchronise its clock using NTP. If ahcpd doesn't detect time synchronisation, it will only give out leases for short periods of time, and will be extremely conservative about releasing them. Note that ahcpd actually checks with the kernel for time synchronisation, so most SNTP clients will not do; you will most probably need Mills' implementation of NTP. For redundancy, you may set up multiple servers in a single network as long as they serve disjoint IPv4 address ranges. Setting up a client =================== Unlike the server, the client needs to run ahcpd as root. No configuration file is needed, just run # ahcpd wlan0 and you should get IPv4 and IPv6 addresses in a few seconds. --Juliusz Chroboczek ahcpd-0.53/ahcp-config.sh000077500000000000000000000050771167023647600152620ustar00rootroot00000000000000#!/bin/sh die() { echo "$@" >&2 exit 1 } findcmd() { type "$1" > /dev/null 2>&1 || \ die "Couldn't find $1, please install ${2:-it} or fix your path." } first() { echo "$1" } nl=' ' usage="Usage: $0 (start|stop)" debuglevel=${AHCP_DEBUG_LEVEL:-1} if [ $debuglevel -ge 2 ]; then env | grep ^AHCP fi [ $debuglevel -ge 3 ] && set -x interfaces="$AHCP_INTERFACES" if [ -z "$interfaces" ]; then die "No interface set"; fi first_if=$(first $interfaces) ipv4_address="$AHCP_IPv4_ADDRESS" ipv6_address="$AHCP_IPv6_ADDRESS" platform=`uname` if [ "$platform" = "Linux" ]; then findcmd ip iproute (ip -6 addr show dev lo | grep -q 'inet6') || \ die "No IPv6 address on lo, please modprobe ipv6" add_ipv6_address() { ip -6 addr add "$2/$3" dev "$1" } del_ipv6_address() { ip -6 addr del "$2/$3" dev "$1" } add_ipv4_address() { ip addr add "$2/$3" dev "$1" } del_ipv4_address() { ip addr del "$2/$3" dev "$1" } else add_ipv6_address() { ifconfig "$1" inet6 "$2/$3" } del_ipv6_address() { ifconfig "$1" inet6 "$2/$3" delete } add_ipv4_address() { ifconfig "$1" inet "$2/$3" } del_ipv4_address() { ifconfig "$1" inet "$2/$3" delete } fi add_addresses() { for i in $interfaces; do for a in $ipv6_address; do add_ipv6_address $i $a 128 done for a in $ipv4_address; do add_ipv4_address $i $a 32 done done } del_addresses() { for i in $interfaces; do for a in $ipv6_address; do del_ipv6_address $i $a 128 done for a in $ipv4_address; do del_ipv4_address $i $a 32 done done } nameserver_start() { if [ ! -z "$AHCP_NAMESERVER" ]; then info='' for n in $AHCP_NAMESERVER; do info="${info}nameserver $n$nl" done if [ -x /sbin/resolvconf ]; then echo -n "$info" | /sbin/resolvconf -a "$first_if" else mv /etc/resolv.conf /etc/resolv.conf.orig echo -n "$info" > /etc/resolv.conf fi fi } nameserver_stop() { if [ ! -z "$AHCP_NAMESERVER" ]; then if [ -x /sbin/resolvconf ]; then /sbin/resolvconf -d "$first_if" else mv /etc/resolv.conf.orig /etc/resolv.conf fi fi } case $1 in start) add_addresses nameserver_start ;; stop) nameserver_stop del_addresses ;; *) die "$usage" ;; esac [ -x /etc/ahcp/ahcp-local.sh ] && /etc/ahcp/ahcp-local.sh $1 exit 0 ahcpd-0.53/ahcpd.c000066400000000000000000001063671167023647600137740ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ahcpd.h" #include "monotonic.h" #include "protocol.h" #include "transport.h" #include "prefix.h" #include "config.h" #include "configure.h" #include "lease.h" #define BUFFER_SIZE 2048 struct timeval now; const struct timeval zero = {0, 0}; static volatile sig_atomic_t exiting = 0, dumping = 0, changed = 0; struct in6_addr protocol_group; unsigned int protocol_port = 5359; int protocol_socket = -1; unsigned char myid[8]; char *unique_id_file = "/var/lib/ahcpd-unique-id"; int nodns = 0, af = 3, request_prefix_delegation = 0; char *config_script = "/etc/ahcp/ahcp-config.sh"; int debug = 1; int do_daemonise = 0; char *logfile = NULL, *pidfile = "/var/run/ahcpd.pid"; #define CHECK_NETWORKS 1 #define MESSAGE 2 struct network networks[MAXNETWORKS]; int numnetworks; char *interfaces[MAXNETWORKS + 1]; struct timeval check_networks_time = {0, 0}; struct timeval message_time = {0, 0}; const unsigned char zeroes[16] = {0}; const unsigned char ones[16] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; static void init_signals(void); static int check_network(struct network *net); int ahcp_socket(int port); int ahcp_recv(int s, void *buf, int buflen, struct sockaddr *sin, int slen); static int send_unicast_packet(unsigned char *server_id, struct config_data *config, int index, void *buf, int buflen); int timeval_compare(const struct timeval *s1, const struct timeval *s2); static int reopen_logfile(void); static int daemonise(void); static void set_timeout(int which, int msecs, int override); unsigned roughly(unsigned value); /* Client states */ enum state { STATE_IDLE, /* we're not a client */ STATE_INIT, /* searching for server */ STATE_REQUESTING, /* found a server */ STATE_RENEWING_UNICAST, /* configured and starting to get nervous */ STATE_RENEWING, /* configured but panicking */ STATE_BOUND /* configured and cool */ }; int main(int argc, char **argv) { char *multicast = "ff02::cca6:c0f9:e182:5359"; char *config_file = NULL; struct sockaddr_in6 sin6; int opt, fd, rc, i, net; unsigned int seed; enum state state = STATE_IDLE; int count = 0; int server_hopcount = 0; unsigned char selected_server[8]; unsigned char last_ipv4[4] = {0}; unsigned lease_time = 3666; while(1) { opt = getopt(argc, argv, "m:p:nN46s:d:i:t:P:c:C:DL:I:"); if(opt < 0) break; switch(opt) { case 'm': multicast = optarg; break; case 'p': protocol_port = atoi(optarg); if(protocol_port <= 0 || protocol_port > 0xFFFF) goto usage; break; case 'n': client_config = 0; break; case 'N': nodns = 1; break; case '4': af = 1; break; case '6': af = 2; break; case 's': config_script = optarg; break; case 'd': debug = atoi(optarg); break; case 'i': unique_id_file = optarg; break; case 't': lease_time = atoi(optarg); if(lease_time < 300 || lease_time > 365 * 24 * 3600) goto usage; break; case 'P': request_prefix_delegation = 1; break; case 'c': config_file = optarg; break; case 'C': rc = parse_config_from_string(optarg); if(rc < 0) { fprintf(stderr, "Couldn't parse configuration from command line.\n"); exit(1); } break; case 'D': do_daemonise = 1; break; case 'L': logfile = optarg; break; case 'I': pidfile = optarg; break; default: goto usage; } } if(optind >= argc) goto usage; if(config_file) { rc = parse_config_from_file(config_file); if(rc < 0) { fprintf(stderr, "Couldn't parse configuration from file %s.\n", config_file); exit(1); } } for(i = optind; i < argc; i++) { if(i - optind >= MAXNETWORKS) { fprintf(stderr, "Too many interfaces.\n"); exit(1); } interfaces[i - optind] = argv[i]; } numnetworks = i - optind; interfaces[i - optind] = NULL; rc = inet_pton(AF_INET6, multicast, &protocol_group); if(rc <= 0) goto usage; time_init(); if(do_daemonise) { if(logfile == NULL) logfile = "/var/log/ahcpd.log"; } rc = reopen_logfile(); if(rc < 0) { perror("reopen_logfile()"); exit(1); } fd = open("/dev/null", O_RDONLY); if(fd < 0) { perror("open(null)"); exit(1); } rc = dup2(fd, 0); if(rc < 0) { perror("dup2(null, 0)"); exit(1); } close(fd); if(do_daemonise) { rc = daemonise(); if(rc < 0) { perror("daemonise"); exit(1); } } if(pidfile) { int pfd, len; char buf[100]; len = snprintf(buf, 100, "%lu", (unsigned long)getpid()); if(len < 0 || len >= 100) { perror("snprintf(getpid)"); exit(1); } pfd = open(pidfile, O_WRONLY | O_CREAT | O_EXCL, 0644); if(pfd < 0) { perror("creat(pidfile)"); exit(1); } rc = write(pfd, buf, len); if(rc < len) { perror("write(pidfile)"); goto fail; } close(pfd); } gettime(&now, NULL); fd = open("/dev/urandom", O_RDONLY); if(fd < 0) { struct timeval tv; perror("open(random)"); get_real_time(&tv, NULL); seed = tv.tv_sec ^ tv.tv_usec; } else { rc = read(fd, &seed, sizeof(unsigned int)); if(rc < sizeof(unsigned int)) { perror("read(random)"); goto fail; } close(fd); } srandom(seed); myseqno = random(); if(unique_id_file && unique_id_file[0] != '\0') { fd = open(unique_id_file, O_RDONLY); if(fd >= 0) { rc = read(fd, myid, 8); if(rc == 8) { close(fd); goto unique_id_done; } close(fd); } } for(i = 0; i < numnetworks; i++) { rc = if_eui64(interfaces[i], myid); if(rc >= 0) goto write_unique_id; } rc = random_eui64(myid); if(rc < 0) { fprintf(stderr, "Couldn't generate unique id.\n"); goto fail; } write_unique_id: if(unique_id_file && unique_id_file[0] != '\0') { fd = open(unique_id_file, O_WRONLY | O_TRUNC | O_CREAT, 0644); if(fd < 0) { perror("creat(unique_id)"); } else { rc = write(fd, myid, 8); if(rc != 8) { perror("write(unique_id)"); unlink(unique_id_file); } close(fd); } } unique_id_done: if(server_config) { #ifndef NO_SERVER if(server_config->lease_first[0] != 0) { if(server_config->lease_dir == NULL) { fprintf(stderr, "No lease directory configured!\n"); goto fail; } rc = lease_init(server_config->lease_dir, server_config->lease_first, server_config->lease_last, debug >= 2); if(rc < 0) { fprintf(stderr, "Couldn't initialise lease database.\n"); goto fail; } } #else abort(); #endif } protocol_socket = ahcp_socket(protocol_port); if(protocol_socket < 0) { perror("ahcp_socket"); goto fail; } for(i = 0; i < numnetworks; i++) { networks[i].ifname = interfaces[i]; check_network(&networks[i]); if(networks[i].ifindex <= 0) { fprintf(stderr, "Warning: unknown interface %s.\n", networks[i].ifname); continue; } } init_signals(); set_timeout(CHECK_NETWORKS, 30000, 1); /* The client state machine. */ #define SWITCH(new) \ do { \ debugf(2, "Switching to state %d.\n", new); \ state = new; \ if(state == STATE_IDLE || state == STATE_BOUND) \ set_timeout(MESSAGE, 0, 1); \ else \ set_timeout(MESSAGE, 300, 1); \ count = 0; \ } while(0) if(client_config) { server_hopcount = 0; SWITCH(STATE_INIT); } debugf(2, "Entering main loop.\n"); while(1) { fd_set readfds; struct timeval tv; assert((config_data != NULL) == (state == STATE_BOUND || state == STATE_RENEWING_UNICAST || state == STATE_RENEWING)); FD_ZERO(&readfds); tv = check_networks_time; timeval_min(&tv, &message_time); if(config_data) { timeval_min_sec(&tv, config_data->expires_m); if(state == STATE_BOUND) timeval_min_sec(&tv, config_renew_time()); } gettime(&now, NULL); if(timeval_compare(&tv, &now) > 0) { timeval_minus(&tv, &tv, &now); FD_SET(protocol_socket, &readfds); debugf(3, "Sleeping for %d.%03ds, state=%d.\n", (int)tv.tv_sec, (int)(tv.tv_usec / 1000), (int)state); rc = select(protocol_socket + 1, &readfds, NULL, NULL, &tv); if(rc < 0 && errno != EINTR) { perror("select"); sleep(5); continue; } } gettime(&now, NULL); if(exiting) break; if(dumping) { int clock_status; time_t stable; struct timeval tv, real; dumping = 0; get_real_time(&real, &clock_status); gettime(&tv, &stable); printf("Clock status %d, stable for at least %ld seconds.\n", (int)clock_status, (long)stable); printf("Forwarder forwarding.\n"); if(server_config) printf("Server serving.\n"); if(client_config) { printf("Client in state %d, ", (int)state); if(memcmp(selected_server, zeroes, 8) != 0) printf("server selected, "); if(config_data) { printf("configuration valid for " "%lds (originally %lds since %lds).", (long)((long)config_data->expires_m - now.tv_sec), (long)config_data->expires, (long)config_data->origin); } else { printf("not configured.\n"); } } printf("\n"); fflush(stdout); } if(changed) { changed = 0; for(i = 0; i < numnetworks; i++) check_network(&networks[i]); set_timeout(CHECK_NETWORKS, 30000, 1); rc = reopen_logfile(); if(rc < 0) { perror("reopen_logfile"); goto fail; } } if(FD_ISSET(protocol_socket, &readfds)) { unsigned char buf[BUFFER_SIZE]; int len; struct sockaddr *psin; int sinlen; len = ahcp_recv(protocol_socket, buf, BUFFER_SIZE, (struct sockaddr*)&sin6, sizeof(sin6)); if(len < 0) { if(errno != EAGAIN && errno != EINTR) { perror("recv"); sleep(5); } continue; } psin = (struct sockaddr*)&sin6; sinlen = sizeof(sin6); if(IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) { for(net = 0; net < numnetworks; net++) { if(networks[net].ifindex <= 0) continue; if(networks[net].ifindex == sin6.sin6_scope_id) break; } if(net >= numnetworks) { fprintf(stderr, "Received packet on unknown network.\n"); continue; } } else { net = -1; } rc = handle_packet(net >= 0, buf, len); gettime(&now, NULL); if(rc == 2) { unsigned char *body = buf + 24; int bodylen = len - 24; if(len < 28) { fprintf(stderr, "Received truncated packet (%d).\n", rc); continue; } debugf(2, "Received packet %d in state %d.\n", body[0], (int)state); switch(state) { case STATE_IDLE: break; case STATE_INIT: if(body[0] == AHCP_OFFER) { struct config_data *config; config = parse_message(0, body, bodylen, interfaces); if(config && (((af & 1) && config->ipv4_address) || ((af & 2) && config->ipv6_prefix))) { if((af & 1) && config->ipv4_address) prefix_list_extract4(last_ipv4, config->ipv4_address); server_hopcount = buf[3]; memcpy(selected_server, buf + 8, 8); SWITCH(STATE_REQUESTING); } if(config) free_config_data(config); } break; case STATE_REQUESTING: case STATE_RENEWING: case STATE_RENEWING_UNICAST: if(body[0] == AHCP_ACK && memcmp(buf + 8, selected_server, 8) == 0) { struct config_data *config; config = parse_message(1, body, bodylen, interfaces); if(!config_data) { /* Something went wrong, such as the config script failing. Reset everything, but set a large timeout. */ server_hopcount = 0; memset(selected_server, 0, 8); SWITCH(STATE_INIT); set_timeout(MESSAGE, 30000, 1); } else if(config) { if((af & 1) && config->ipv4_address) prefix_list_extract4(last_ipv4, config->ipv4_address); server_hopcount = buf[3]; free_config_data(config); SWITCH(STATE_BOUND); } else { fprintf(stderr, "Eek! Configured, but config null.\n"); } } else if(body[0] == AHCP_NACK && memcmp(buf + 8, selected_server, 8) == 0) { if(config_data) unconfigure(interfaces); server_hopcount = 0; memset(selected_server, 0, 8); SWITCH(STATE_INIT); } break; case STATE_BOUND: break; default: abort(); } #ifndef NO_SERVER if(server_config) { if(body[0] == AHCP_DISCOVER || body[0] == AHCP_REQUEST || body[0] == AHCP_RELEASE) { struct config_data *config; unsigned client_lease_time; unsigned char reply[BUFFER_SIZE]; int hopcount; unsigned char ipv4[4] = {0}; config = parse_message(-1, body, bodylen, interfaces); if(!config) { fprintf(stderr, "Unparseable client message.\n"); continue; } hopcount = buf[3]; if(config->ipv4_address) prefix_list_extract4(ipv4, config->ipv4_address); client_lease_time = config->expires ? config->expires + roughly(120) : 4 * 3600 + roughly(120); free_config_data(config); if(body[0] == AHCP_RELEASE) { if(memcmp(ipv4, zeroes, 4) != 0) { rc = release_lease(buf + 8, 8, ipv4); if(rc < 0) { char a[INET_ADDRSTRLEN]; inet_ntop(AF_INET, ipv4, a, INET_ADDRSTRLEN); fprintf(stderr, "Couldn't release lease for %s.\n", a); } } continue; } if((config->ipv4_mandatory && !server_config->lease_first[0]) || (config->ipv6_mandatory && !server_config->ipv6_prefix) || config->ipv4_delegation_mandatory || config->ipv6_delegation_mandatory) { /* We won't be able to satisfy the client's mandatory constraints. */ rc = -1; } else if(server_config->lease_first[0] && config->ipv4_address) { rc = take_lease(buf + 8, 8, memcmp(ipv4, zeroes, 4) == 0 ? ipv4 : NULL, ipv4, &client_lease_time, body[0] == AHCP_REQUEST); } else { rc = 0; } /* If the client is in the initial state, there's no point in notifying it about failures -- it will time out and fall back to another server */ if(rc < 0 && body[0] == AHCP_DISCOVER) continue; config = make_config_data(client_lease_time, ipv4, server_config, interfaces); if(config == NULL) { fprintf(stderr, "Couldn't build config data.\n"); continue; } rc = server_body(rc < 0 ? AHCP_NACK : body[0] == AHCP_DISCOVER ? AHCP_OFFER : AHCP_ACK, config, reply, BUFFER_SIZE); if(rc < 0) { fprintf(stderr, "Couldn't build reply.\n"); } else { debugf(2, "Sending %d (%d bytes, %d hops).\n", reply[0], rc, hopcount); usleep(roughly(50000)); send_packet(psin, sinlen, buf + 8, hopcount, reply, rc); gettime(&now, NULL); } free_config_data(config); } } #endif } } if(config_data) { if(now.tv_sec >= config_data->expires_m) { unconfigure(interfaces); server_hopcount = 0; SWITCH(STATE_INIT); } } if(state == STATE_BOUND) { if(now.tv_sec >= config_renew_time()) SWITCH(STATE_RENEWING_UNICAST); } if(message_time.tv_sec > 0 && now.tv_sec >= message_time.tv_sec) { unsigned char buf[BUFFER_SIZE]; int i; switch(state) { case STATE_IDLE: fprintf(stderr, "Attempted to send message in IDLE state.\n"); set_timeout(MESSAGE, 0, 1); break; case STATE_INIT: server_hopcount++; i = query_body(AHCP_DISCOVER, lease_time + roughly(120), memcmp(last_ipv4, zeroes, 4) ? NULL : last_ipv4, buf, BUFFER_SIZE); if(i >= 0) { debugf(2, "Sending %d (%d bytes, %d hops).\n", buf[0], i, server_hopcount); send_packet(NULL, 0, NULL, server_hopcount, buf, i); } else { fprintf(stderr, "Couldn't build body.\n"); } count++; set_timeout(MESSAGE, 1000 * count, 1); break; case STATE_RENEWING: server_hopcount++; /* Fall through */ case STATE_REQUESTING: case STATE_RENEWING_UNICAST: i = query_body(AHCP_REQUEST, lease_time + roughly(120), memcmp(last_ipv4, zeroes, 4) ? NULL : last_ipv4, buf, BUFFER_SIZE); if(i < 0) { fprintf(stderr, "Couldn't build body.\n"); break; } if(state == STATE_REQUESTING && count > 7) { debugf(2, "Giving up on request.\n"); server_hopcount = 0; memset(selected_server, 0, 8); SWITCH(STATE_INIT); } else if(state == STATE_RENEWING_UNICAST) { debugf(2, "Sending %d (%d bytes, unicast)\n", buf[0], i); rc = send_unicast_packet(selected_server, config_data, count / 3, buf, i); if(rc == 0) { SWITCH(STATE_RENEWING); } else { /* A failure probably indicates we couldn't route to this address, no point in delaying. */ count++; set_timeout(MESSAGE, rc < 0 ? 300 : 10000, 1); } } else { debugf(2, "Sending %d (%d bytes, %d hops).\n", buf[0], i, server_hopcount); send_packet(NULL, 0, selected_server, server_hopcount, buf, i); count++; /* At this point we have a fair idea of the server hopcount, so we should get a reply in one, at most two requests. Use generous timeouts. */ set_timeout(MESSAGE, state == STATE_REQUESTING ? 2000 * count : 10000 * count, 1); } break; case STATE_BOUND: fprintf(stderr, "Attempted to send message in BOUND state.\n"); set_timeout(MESSAGE, 0, 1); break; default: abort(); } } if(check_networks_time.tv_sec > 0 && timeval_compare(&check_networks_time, &now) <= 0) { for(i = 0; i < numnetworks; i++) check_network(&networks[i]); set_timeout(CHECK_NETWORKS, 30000, 1); } } /* Clean up */ if(config_data) { unsigned char buf[BUFFER_SIZE]; int len; len = query_body(AHCP_RELEASE, 0, memcmp(last_ipv4, zeroes, 4) ? NULL : last_ipv4, buf, BUFFER_SIZE); if(len >= 0) { int index = 0; int success = 0; while(1) { debugf(2, "Sending %d (%d bytes, unicast).\n", buf[0], i); rc = send_unicast_packet(selected_server, config_data, index, buf, len); if(rc > 0) { success = 1; break; } else if(rc == 0) { break; } index++; } if(!success) { debugf(2, "Sending %d (%d bytes, %d hops).\n", buf[0], i, server_hopcount); send_packet(NULL, 0, NULL, server_hopcount + 2, buf, len); } } unconfigure(interfaces); SWITCH(STATE_INIT); } if(pidfile) unlink(pidfile); return 0; usage: fprintf(stderr, "Syntax: ahcpd " "[-m group] [-p port] [-n] [-4] [-6] [-N]\n" " " "[-i file] [-s script] [-D] [-I pidfile] [-L logfile]\n" " " "[-C statement] [-c filename]" "interface...\n"); exit(1); fail: if(pidfile) unlink(pidfile); exit(1); } unsigned roughly(unsigned value) { return value * 3 / 4 + random() % (value / 4); } static void set_timeout(int which, int msecs, int override) { struct timeval *tv; int ms = msecs == 0 ? 0 : roughly(msecs); switch(which) { case MESSAGE: tv = &message_time; break; case CHECK_NETWORKS: tv = &check_networks_time; break; default: abort(); } /* (0, 0) represents never */ if(override || tv->tv_sec == 0 || tv->tv_sec > now.tv_sec + ms / 1000) { if(msecs <= 0) { tv->tv_usec = 0; tv->tv_sec = 0; } else { tv->tv_usec = (now.tv_usec + ms * 1000) % 1000000 + random() % 1000; tv->tv_sec = now.tv_sec + (now.tv_usec / 1000 + ms) / 1000; } } } static int check_network(struct network *net) { int ifindex, rc; struct ipv6_mreq mreq; ifindex = if_nametoindex(net->ifname); if(ifindex != net->ifindex) { net->ifindex = ifindex; if(net->ifindex > 0) { memset(&mreq, 0, sizeof(mreq)); memcpy(&mreq.ipv6mr_multiaddr, &protocol_group, 16); mreq.ipv6mr_interface = net->ifindex; rc = setsockopt(protocol_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&mreq, sizeof(mreq)); if(rc < 0) { perror("setsockopt(IPV6_JOIN_GROUP)"); net->ifindex = 0; goto fail; } return 1; } } fail: return 0; } static void sigexit(int signo) { exiting = 1; } static void sigdump(int signo) { dumping = 1; } static void sigchanged(int signo) { changed = 1; } static void init_signals(void) { struct sigaction sa; sigset_t ss; sigemptyset(&ss); sa.sa_handler = sigexit; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGTERM, &sa, NULL); sigemptyset(&ss); sa.sa_handler = sigexit; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGHUP, &sa, NULL); sigemptyset(&ss); sa.sa_handler = sigexit; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); sigemptyset(&ss); sa.sa_handler = sigdump; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGUSR1, &sa, NULL); sigemptyset(&ss); sa.sa_handler = sigchanged; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGUSR2, &sa, NULL); #ifdef SIGINFO sigemptyset(&ss); sa.sa_handler = sigdump; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGINFO, &sa, NULL); #endif } int ahcp_socket(int port) { struct sockaddr_in6 sin6; int s, rc; int saved_errno; int one = 1, zero = 0; const int ds = 0xc0; /* CS6 - Network Control */ s = socket(PF_INET6, SOCK_DGRAM, 0); if(s < 0) return -1; rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if(rc < 0) perror("setsockopt(SO_REUSEADDR)"); rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)); if(rc < 0) perror("setsockopt(IPV6_MULTICAST_LOOP)"); rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &one, sizeof(one)); if(rc < 0) perror("setsockopt(IPV6_MULTICAST_HOPS)"); #ifdef IPV6_V6ONLY rc = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)); if(rc < 0) perror("setsockopt(IPV6_V6ONLY)"); #endif #ifdef IPV6_TCLASS rc = setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &ds, sizeof(ds)); #else rc = -1; errno = ENOSYS; #endif if (rc < 0) perror("setsockopt(IPV6_TCLASS)"); rc = fcntl(s, F_GETFD, 0); if(rc < 0) goto fail; rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC); if(rc < 0) goto fail; rc = fcntl(s, F_GETFL, 0); if(rc < 0) goto fail; rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); if(rc < 0) goto fail; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port); rc = bind(s, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) goto fail; return s; fail: saved_errno = errno; close(s); errno = saved_errno; return -1; } int ahcp_recv(int s, void *buf, int buflen, struct sockaddr *sin, int slen) { struct iovec iovec; struct msghdr msg; int rc; memset(&msg, 0, sizeof(msg)); iovec.iov_base = buf; iovec.iov_len = buflen; msg.msg_name = sin; msg.msg_namelen = slen; msg.msg_iov = &iovec; msg.msg_iovlen = 1; rc = recvmsg(s, &msg, 0); return rc; } /* Attempts to contact the server over unicast, on its indexth address. Returns 1 if a packet was successfully sent, 0 if index was beyond the number of addresses, -1 in case of failure to send the packet. */ static int send_unicast_packet(unsigned char *server_id, struct config_data *config, int index, void *buf, int buflen) { int rc; unsigned char *address; struct sockaddr_in6 sin; int num6 = config->server_ipv6 ? config->server_ipv6->n : 0; int num4 = config->server_ipv4 ? config->server_ipv4->n : 0; if(!config_data) return 0; if(index < num6) address = config->server_ipv6->l[index].p; else if(index < num6 + num4) address = config->server_ipv4->l[index - num6].p; else return 0; memset(&sin, 0, sizeof(sin)); sin.sin6_family = AF_INET6; memcpy(&sin.sin6_addr, address, 16); sin.sin6_port = htons(protocol_port); rc = send_packet((struct sockaddr*)&sin, sizeof(sin), server_id, 1, buf, buflen); return (rc >= 0) ? 1 : -1; } int timeval_compare(const struct timeval *s1, const struct timeval *s2) { if(s1->tv_sec < s2->tv_sec) return -1; else if(s1->tv_sec > s2->tv_sec) return 1; else if(s1->tv_usec < s2->tv_usec) return -1; else if(s1->tv_usec > s2->tv_usec) return 1; else return 0; } /* {0, 0} represents infinity */ void timeval_min(struct timeval *d, const struct timeval *s) { if(s->tv_sec == 0) return; if(d->tv_sec == 0 || timeval_compare(d, s) > 0) { *d = *s; } } void timeval_minus(struct timeval *d, const struct timeval *s1, const struct timeval *s2) { if(s1->tv_usec >= s2->tv_usec) { d->tv_usec = s1->tv_usec - s2->tv_usec; d->tv_sec = s1->tv_sec - s2->tv_sec; } else { d->tv_usec = s1->tv_usec + 1000000 - s2->tv_usec; d->tv_sec = s1->tv_sec - s2->tv_sec - 1; } } void timeval_min_sec(struct timeval *d, int secs) { if(d->tv_sec == 0 || d->tv_sec > secs) { d->tv_sec = secs; d->tv_usec = random() % 1000000; } } static int reopen_logfile() { int lfd, rc; if(logfile == NULL) return 0; lfd = open(logfile, O_CREAT | O_WRONLY | O_APPEND, 0644); if(lfd < 0) return -1; fflush(stdout); fflush(stderr); rc = dup2(lfd, 1); if(rc < 0) return -1; rc = dup2(lfd, 2); if(rc < 0) return -1; if(lfd == 0 || lfd > 2) close(lfd); return 1; } static int daemonise() { int rc; fflush(stdout); fflush(stderr); rc = fork(); if(rc < 0) return -1; if(rc > 0) exit(0); rc = setsid(); if(rc < 0) return -1; return 1; } void do_debugf(int level, const char *format, ...) { va_list args; va_start(args, format); if(debug >= level) { vfprintf(stderr, format, args); fflush(stderr); } va_end(args); } ahcpd-0.53/ahcpd.h000066400000000000000000000074701167023647600137740ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* nothing */ #elif defined(__GNUC__) #define inline __inline #if (__GNUC__ >= 3) #define restrict __restrict #else #define restrict /**/ #endif #else #define inline /**/ #define restrict /**/ #endif #if defined(__GNUC__) && (__GNUC__ >= 3) #define ATTRIBUTE(x) __attribute__ (x) #define LIKELY(_x) __builtin_expect(!!(_x), 1) #define UNLIKELY(_x) __builtin_expect(!!(_x), 0) #else #define ATTRIBUTE(x) /**/ #define LIKELY(_x) !!(_x) #define UNLIKELY(_x) !!(_x) #endif #if defined(__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) #define COLD __attribute__ ((cold)) #else #define COLD /**/ #endif #undef MAX #undef MIN #ifdef HAVE_VALGRIND #include #else #ifndef VALGRIND_MAKE_MEM_UNDEFINED #define VALGRIND_MAKE_MEM_UNDEFINED(a, b) do {} while(0) #endif #ifndef VALGRIND_CHECK_MEM_IS_DEFINED #define VALGRIND_CHECK_MEM_IS_DEFINED(a, b) do {} while(0) #endif #endif #define MAX(x,y) ((x)<=(y)?(y):(x)) #define MIN(x,y) ((x)<=(y)?(x):(y)) extern int nodns, af, request_prefix_delegation; extern char *config_script; extern int debug; extern unsigned char myid[8]; extern int numnetworks; struct network { char *ifname; int ifindex; }; #define MAXNETWORKS 20 extern struct network networks[MAXNETWORKS]; extern struct in6_addr protocol_group; extern unsigned int protocol_port; extern int protocol_socket; extern const unsigned char zeroes[16], ones[16]; void timeval_min(struct timeval *d, const struct timeval *s); void timeval_min_sec(struct timeval *d, int secs); void timeval_minus(struct timeval *d, const struct timeval *s1, const struct timeval *s2); int timeval_minus_msec(const struct timeval *s1, const struct timeval *s2); void timeval_plus_msec(struct timeval *d, const struct timeval *s, int msecs); int timeval_compare(const struct timeval *s1, const struct timeval *s2); int clock_stepped(); void do_debugf(int level, const char *format, ...) ATTRIBUTE ((format (printf, 2, 3))) COLD; #if defined NO_DEBUG #if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L #define debugf(_level, ...) do {} while(0) #elif defined __GNUC__ #define debugf(_level, _args...) do {} while(0) #else static inline void debugf(_level, const char *format, ...) { return; } #endif #else #if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L #define debugf(_level, ...) \ do { \ if(UNLIKELY(debug >= _level)) do_debugf(_level, __VA_ARGS__); \ } while(0) #elif defined __GNUC__ #define debugf(_level, _args...) \ do { \ if(UNLIKELY(debug >= _level)) do_debugf(_level, _args); \ } while(0) #else static inline void debugf(int _level, const char *format, ...) { return; } #endif #endif ahcpd-0.53/ahcpd.man000066400000000000000000000110541167023647600143110ustar00rootroot00000000000000.TH AHCPD 8 .SH NAME ahcpd \- ad-hoc configuration daemon .SH SYNOPSIS .B ahcpd .IR option ... [ .B \-\- ] .IR interface ... .SH DESCRIPTION AHCP is a configuration protocol that can replace DHCP on networks without transitive connectivity, such as mesh networks. .SH OPTIONS .TP .BI \-m " multicast-address" Specify the link-local multicast address to be used by AHCP. The default is ff02::cca6:c0f9:e182:5359. .TP .BI \-p " port" Specify the UDP port number to be used by AHCP. The default is 5359. .TP .B \-n Operate as a forwarder: participate in the flooding protocol, but don't actually perform any configuration. .TP .B \-4 Only attempt to configure IPv4 addresses. .TP .B \-6 Only attempt to configure IPv6 addresses. .TP .B \-N Do not configure DNS. .TP .BI \-t " time" Specify the time, in seconds, for which leases are requested. The default is slightly over one hour. Must be between five minutes and a year. .TP .BI \-s " script" Specify the configuration script to run. The default is .BR /etc/ahcp/ahcp-config.sh . .TP .BI \-d " level" Set the debug level to .I level (default 1). .TP .BI \-i " filename" Specify the filename containing this host's unique id. The default is .BR /var/lib/ahcp\-unique\-id . If it doesn't exist, it will be created by .BR ahcpd . .TP .BI \-c " filename" Specify the name of the configuration file. .TP .BI \-C " statement" Specify a configuration statement directly on the command line. .TP .B \-D Daemonise at startup. .TP .BI \-L " logfile" Specify a file to log random ``how do you do?'' messages to. This defaults to standard error if not daemonising, and to .B /var/log/ahcpd.log otherwise. .TP .BI \-I " pidfile" Specify a file to write our process id to. The default is .B /var/run/ahcpd.pid. .SH CONFIGURATION FILE FORMAT The configuration is a sequence of lines, each of which starts with one of the keywords below. Blank lines are ignored. Comments are introduced with an octothorp .RB `` # '' and terminate at the end of the line. The following keywords are recognised: .TP .BR mode " " server | client | forwarder Specifies whether the daemon operates as a server, a client, or a forwarder. If omitted, the default is to operate as a client, unless the .B \-n flag is present on the command line. If present, this must be the first line in the configuration file. .TP .BI prefix " prefix" Specifies a prefix to use for configuring clients. This keyword is only valid in server configurations, and may be specified twice, once for IPv4 and once for IPv6. .TP .BI lease-dir " directory" Specifies a directory to store lease files. This keyword is only valid in server configurations. .TP .BI name-server " address" Specifies the address of a DNS server to configure clients with. This keyword is only valid in server configurations, and may be repeated multiple times. .TP .BI ntp-server " address" Specifies the address of an NTP server to configure clients with. This keyword is only valid in server configurations, and may be repeated multiple times. .SH FILES .TP .B /var/lib/ahcp\-unique\-id An 8-byte long file containing this host's unique id. If it doesn't exist, a new unique id will be generated from an interface's MAC address. .TP .BR /etc/ahcp/ahcp\-config.sh The script that performs the actual configuration. It will be passed one argument, which is either .B start or .BR stop . .TP .B /etc/ahcp/ahcp\-local.sh If this is an executable script, it will be called by .B ahcp\-config.sh just after configuring or deconfiguring. It will be passed one argument, which is either .B start or .BR stop . .SH SIGNALS .TP .B SIGUSR1 Print .BR ahcpd 's status to standard output or to the log file. .TP .B SIGUSR2 Check all interfaces for status changes, then reopen the log file. .SH NOTES Since the AHCP protocol is designed for mesh networks, it doesn't have any provisions for setting routing parameters such as a default gateway, the IPv4 network mask and the IPv6 list of on-link prefixes; these are expected to be provided by a full-fledged routing protocol for mesh nodes, and by router advertisements for ordinary nodes. The AHCP server should use NTP to synchronise its clock. If a server instance of .B ahcpd doesn't detect time synchronisation, it will only give out leases for a short period of time, and be extremely conservative about releasing them. Note that an SNTP client is not enough \[em] .B ahcpd actually checks with the kernel for time synchronisation, so real NTP is necessary. .SH SEE ALSO .BR dhcpcd (8), .BR dhclient (8), .BR babeld (8), .BR olsrd (8), .BR ntpd (8), .IR "The Ad-Hoc Configuration Protocol" . .SH AUTHOR Juliusz Chroboczek. ahcpd-0.53/config.c000066400000000000000000000173531167023647600141560ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include "prefix.h" #include "config.h" int client_config = 1; struct server_config *server_config = NULL; /* get_next_char callback */ typedef int (*gnc_t)(void*); static int skip_whitespace(int c, gnc_t gnc, void *closure) { while(c == ' ' || c == '\t') c = gnc(closure); return c; } static int skip_to_eol(int c, gnc_t gnc, void *closure) { while(c != '\n' && c >= 0) c = gnc(closure); if(c == '\n') c = gnc(closure); return c; } static int skip_eol(int c, gnc_t gnc, void *closure) { c = skip_whitespace(c, gnc, closure); if(c == '\n' || c == '#') { c = skip_to_eol(c, gnc, closure); return c; } else if(c == -1) { return -1; } else { return -2; } } static int getword(int c, char **token_r, gnc_t gnc, void *closure) { char buf[256]; int i = 0; c = skip_whitespace(c, gnc, closure); if(c < 0) return c; if(c == '"' || c == '\n') return -2; do { if(i >= 255) return -2; buf[i++] = c; c = gnc(closure); } while(c != ' ' && c != '\t' && c != '\n' && c >= 0); buf[i] = '\0'; *token_r = strdup(buf); return c; } static int getstring(int c, char **token_r, gnc_t gnc, void *closure) { char buf[256]; int i = 0; c = skip_whitespace(c, gnc, closure); if(c < 0) return c; if(c == '\n') return -2; /* Unquoted strings have the same syntax as words. */ if(c != '"') return getword(c, token_r, gnc, closure); c = gnc(closure); while(1) { if(i >= 255 || c == '\n') return -2; if(c == '"') { c = gnc(closure); break; } if(c == '\\') c = gnc(closure); buf[i++] = c; c = gnc(closure); } buf[i] = '\0'; *token_r = strdup(buf); return c; } static int parse_config(gnc_t gnc, void *closure) { int c; char *token; c = gnc(closure); if(c < 2) return -1; while(c >= 0) { c = skip_whitespace(c, gnc, closure); if(c == '\n' || c == '#') { c = skip_to_eol(c, gnc, closure); continue; } if(c < 0) break; c = getword(c, &token, gnc, closure); if(c < -1) return -1; if(strcmp(token, "mode") == 0) { char *mtoken; c = getword(c, &mtoken, gnc, closure); if(c < -1) return -1; if(strcmp(mtoken, "server") == 0) { #ifndef NO_SERVER client_config = 0; if(!server_config) server_config = calloc(1, sizeof(struct server_config)); if(!server_config) return -1; #else return -1; #endif } else if(strcmp(mtoken, "client") == 0) { if(server_config) return -1; client_config = 1; } else if(strcmp(mtoken, "forwarder") == 0) { if(server_config) return -1; client_config = 0; } else { return -1; } free(mtoken); c = skip_eol(c, gnc, closure); if(c < -1) return -1; } else if(strcmp(token, "lease-dir") == 0) { char *dir; if(!server_config) return -1; c = getstring(c, &dir, gnc, closure); if(c < -1) return -1; if(dir[0] != '/') return -1; server_config->lease_dir = dir; c = skip_eol(c, gnc, closure); if(c < -1) return -1; } else if(strcmp(token, "prefix") == 0) { char *ptoken; struct prefix_list *prefix; if(!server_config) return -1; c = getword(c, &ptoken, gnc, closure); if(c < -1) return -1; prefix = parse_prefix(ptoken, PREFIX); if(prefix == NULL || prefix->n != 1) return -1; if(prefix_list_v4(prefix)) { unsigned const char zeroes[4] = {0}; unsigned mask, first, last; if(memcmp(server_config->lease_first, zeroes, 4) != 0) return -1; mask = 0xFFFFFFFF << (128 - prefix->l[0].plen); first = (prefix->l[0].p[12] << 24 | prefix->l[0].p[13] << 16 | prefix->l[0].p[14] << 8 | prefix->l[0].p[15] ) & mask; last = first | (~ mask); first = htonl(first + 1); last = htonl(last - 1); memcpy(server_config->lease_first, &first, 4); memcpy(server_config->lease_last, &last, 4); free(prefix); } else { server_config->ipv6_prefix = cat_prefix_list(server_config->ipv6_prefix, prefix); } free(ptoken); c = skip_eol(c, gnc, closure); if(c < -1) return -1; } else if(strcmp(token, "name-server") == 0 || strcmp(token, "ntp-server") == 0) { char *ptoken; struct prefix_list *prefix; if(!server_config) return -1; c = getword(c, &ptoken, gnc, closure); if(c < -1) return -1; prefix = parse_prefix(ptoken, ADDRESS); if(prefix == NULL) return -1; if(strcmp(token, "name-server") == 0) server_config->name_server = cat_prefix_list(server_config->name_server, prefix); else server_config->ntp_server = cat_prefix_list(server_config->ntp_server, prefix); free(ptoken); } else { return -1; } free(token); } return 1; } int parse_config_from_file(char *filename) { FILE *f; int rc; f = fopen(filename, "r"); if(f == NULL) return -1; rc = parse_config((gnc_t)fgetc, f); fclose(f); return rc; } struct string_state { char *string; int n; }; static int gnc_string(struct string_state *s) { if(s->string[s->n] == '\0') return -1; else return s->string[s->n++]; } int parse_config_from_string(char *string) { struct string_state s = { string, 0 }; return parse_config((gnc_t)gnc_string, &s); } ahcpd-0.53/config.h000066400000000000000000000025741167023647600141620ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ struct server_config { const char *lease_dir; struct prefix_list *name_server, *ntp_server, *ipv6_prefix; unsigned char lease_first[4], lease_last[4]; }; extern int client_config; extern struct server_config *server_config; int parse_config_from_string(char *string); int parse_config_from_file(char *filename); ahcpd-0.53/configure.c000066400000000000000000000615131167023647600146670ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #endif #include "ahcpd.h" #include "monotonic.h" #include "prefix.h" #include "config.h" #include "protocol.h" #include "configure.h" struct config_data *config_data = NULL; const unsigned char v4prefix[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 }; static int interface_ipv4(const char *ifname, unsigned char *addr_r) { struct ifreq req; int s, rc; s = socket(AF_INET, SOCK_DGRAM, 0); if(s < 0) return -1; memset(&req, 0, sizeof(req)); strncpy(req.ifr_name, ifname, sizeof(req.ifr_name)); req.ifr_addr.sa_family = AF_INET; rc = ioctl(s, SIOCGIFADDR, &req); if(rc < 0) { close(s); return -1; } if(req.ifr_addr.sa_family != AF_INET) return -1; memcpy(addr_r, &((struct sockaddr_in*)&req.ifr_addr)->sin_addr, 4); close(s); return 1; } static struct prefix_list * my_ipv4(char **interfaces) { struct prefix_list *l; int i, rc; l = calloc(1, sizeof(struct prefix_list)); if(l == NULL) return NULL; i = 0; while(l->n < MAX_PREFIX && interfaces[i]) { l->l[l->n].plen = 0xFF; memcpy(l->l[l->n].p, v4prefix, 12); rc = interface_ipv4(interfaces[i], l->l[l->n].p + 12); if(rc > 0) l->n++; i++; } if(l->n == 0) { free(l); return NULL; } return l; } static int run_script(const char *action, struct config_data *config, char **interfaces) { pid_t pid; pid = fork(); if(pid < 0) { perror("fork"); return -1; } else if(pid == 0) { char buf[200]; int i; snprintf(buf, 50, "%lu", (unsigned long)getppid()); setenv("AHCP_DAEMON_PID", buf, 1); buf[0] = '\0'; i = 0; while(interfaces[i]) { if(i > 0) strncat(buf, " ", 200); strncat(buf, interfaces[i], 200); i++; } setenv("AHCP_INTERFACES", buf, 1); snprintf(buf, 50, "%d", debug); setenv("AHCP_DEBUG_LEVEL", buf, 1); if(config->our_ipv6_address && (af & 2)) setenv("AHCP_IPv6_ADDRESS", format_prefix_list(config->our_ipv6_address, IPv6_ADDRESS), 1); if(config->ipv4_address && (af & 1)) setenv("AHCP_IPv4_ADDRESS", format_prefix_list(config->ipv4_address, IPv4_ADDRESS), 1); if(config->ipv6_prefix_delegation && (af & 2)) setenv("AHCP_IPv6_PREFIX_DELEGATION", format_prefix_list(config->ipv6_prefix_delegation, IPv6_PREFIX), 1); if(config->ipv4_prefix_delegation && (af & 2)) setenv("AHCP_IPv4_PREFIX_DELEGATION", format_prefix_list(config->ipv4_prefix_delegation, IPv4_PREFIX), 1); if(config->name_server && !nodns) setenv("AHCP_NAMESERVER", format_prefix_list(config->name_server, ADDRESS), 1); if(config->ntp_server) setenv("AHCP_NTP_SERVER", format_prefix_list(config->ntp_server, ADDRESS), 1); debugf(1, "Running ``%s %s''\n", config_script, action); execl(config_script, config_script, action, NULL); perror("exec failed"); exit(42); } else { int status; again: pid = waitpid(pid, &status, 0); if(pid < 0) { if(errno == EINTR) goto again; perror("wait"); return -1; } else if(!WIFEXITED(status)) { fprintf(stderr, "Child died violently (%d)\n", status); return -1; } else if(WEXITSTATUS(status) != 0) { fprintf(stderr, "Child returned error status %d\n", WEXITSTATUS(status)); return -1; } } return 1; } unsigned int config_renew_time(void) { return config_data->origin_m + config_data->expires * 5 / 6; } void free_config_data(struct config_data *config) { free_prefix_list(config->server_ipv6); free_prefix_list(config->server_ipv4); free_prefix_list(config->ipv6_prefix); free_prefix_list(config->ipv4_prefix); free_prefix_list(config->ipv6_address); free_prefix_list(config->ipv4_address); free_prefix_list(config->ipv6_prefix_delegation); free_prefix_list(config->ipv4_prefix_delegation); free_prefix_list(config->name_server); free_prefix_list(config->ntp_server); free_prefix_list(config->our_ipv6_address); free(config); } struct config_data * copy_config_data(struct config_data *config) { struct config_data *c = malloc(sizeof(struct config_data)); if(c == NULL) return NULL; *c = *config; #define COPY(field) \ do { if(c->field) c->field = copy_prefix_list(c->field); } while(0) COPY(server_ipv6); COPY(server_ipv4); COPY(ipv6_prefix); COPY(ipv4_prefix); COPY(ipv6_address); COPY(ipv4_address); COPY(ipv6_prefix_delegation); COPY(ipv4_prefix_delegation); COPY(name_server); COPY(ntp_server); COPY(our_ipv6_address); #undef COPY return c; } int config_data_compatible(struct config_data *config1, struct config_data *config2) { if(!!config1->ipv4_address != !!config2->ipv4_address || !!config1->ipv6_address != !!config2->ipv6_address || !!config1->ipv6_prefix != !!config2->ipv6_prefix || !!config1->ipv4_prefix_delegation != !!config2->ipv4_prefix_delegation || !!config1->ipv6_prefix_delegation != !!config2->ipv6_prefix_delegation) return 0; if(config1->ipv4_address) { if(!prefix_list_eq(config1->ipv4_address, config2->ipv4_address) != 0) return 0; } if(config1->ipv6_address) { if(!prefix_list_eq(config1->ipv6_address, config2->ipv6_address) != 0) return 0; } if(config1->ipv6_prefix) { if(!prefix_list_eq(config1->ipv6_prefix, config2->ipv6_prefix) != 0) return 0; } if(config1->ipv4_prefix_delegation) { if(!prefix_list_eq(config1->ipv4_prefix_delegation, config2->ipv4_prefix_delegation) != 0) return 0; } if(config1->ipv6_prefix_delegation) { if(!prefix_list_eq(config1->ipv6_prefix_delegation, config2->ipv6_prefix_delegation) != 0) return 0; } return 1; } struct config_data * make_config_data(int expires, unsigned char *ipv4, struct server_config *server_config, char **interfaces) { struct config_data *config; struct timeval now, real; int clock_status; struct prefix_list *my_address; gettime(&now, NULL); get_real_time(&real, &clock_status); config = calloc(1, sizeof(struct config_data)); if(config == NULL) return NULL; config->expires = expires; config->origin_m = now.tv_sec; if(clock_status != CLOCK_BROKEN) config->origin = real.tv_sec; config->expires_m = config->origin_m + config->expires; if(ipv4) config->ipv4_address = raw_prefix_list(ipv4, 4, IPv4_ADDRESS); if(server_config->ipv6_prefix) config->ipv6_prefix = copy_prefix_list(server_config->ipv6_prefix); if(server_config->name_server) config->name_server = copy_prefix_list(server_config->name_server); if(server_config->ntp_server) config->ntp_server = copy_prefix_list(server_config->ntp_server); my_address = my_ipv4(interfaces); if(my_address) config->server_ipv4 = my_address; return config; } /* Parse a message. Configure is 1 to configure, 0 to pretend, and -1 to omit a number of sanity checks when parsing client messages. */ struct config_data * parse_message(int configure, const unsigned char *data, int len, char **interfaces) { int bodylen, i, opt, olen, rc; int mandatory = 0; const unsigned char *body; struct config_data *config; unsigned origin = 0, expires = 0; struct timeval now, real; int clock_status; #define CAT(dst, src) \ do { \ struct prefix_list *pcat; \ pcat = cat_prefix_list(dst, src); \ if(pcat == NULL) \ goto fail; \ dst = pcat; \ } while(0) if(len < 4) return NULL; gettime(&now, NULL); get_real_time(&real, &clock_status); body = data + 4; bodylen = (data[2] << 8) | data[3]; if(bodylen > len - 4) return NULL; config = calloc(1, sizeof(struct config_data)); if(config == NULL) return NULL; i = 0; while(i < bodylen) { opt = body[i]; if(opt == OPT_PAD) { mandatory = 0; i++; continue; } else if(opt == OPT_MANDATORY) { mandatory = 1; i++; continue; } olen = body[i + 1]; if(olen + 2 + i > bodylen) { fprintf(stderr, "Truncated message.\n"); goto fail; } if(opt == OPT_ORIGIN_TIME) { unsigned int when; if(olen != 4) goto fail; memcpy(&when, body + i + 2, 4); when = ntohl(when); if(origin <= 0) origin = when; else origin = MIN(origin, when); } else if(opt == OPT_EXPIRES) { unsigned int secs; if(olen != 4) goto fail; memcpy(&secs, body + i + 2, 4); secs = ntohl(secs); if(expires <= 0) expires = secs; else expires = MIN(expires, secs); } else if(opt == OPT_IPv6_PREFIX || opt == OPT_IPv6_PREFIX_DELEGATION) { struct prefix_list *value; if(olen % 17 != 0) { fprintf(stderr, "Unexpected length for prefix.\n"); goto fail; } value = raw_prefix_list(body + i + 2, olen, IPv6_PREFIX); if(opt == OPT_IPv6_PREFIX) { CAT(config->ipv6_prefix, value); if(mandatory) config->ipv6_mandatory = 1; } else { CAT(config->ipv6_prefix_delegation, value); if(mandatory) config->ipv6_delegation_mandatory = 1; } } else if(opt == OPT_IPv4_PREFIX_DELEGATION) { struct prefix_list *value; if(olen % 5 != 0) { fprintf(stderr, "Unexpected length for prefix.\n"); goto fail; } value = raw_prefix_list(body + i + 2, olen, IPv4_PREFIX); CAT(config->ipv4_prefix_delegation, value); if(mandatory) config->ipv4_delegation_mandatory = 1; } else if(opt == OPT_MY_IPv6_ADDRESS || opt == OPT_IPv6_ADDRESS || opt == OPT_NAME_SERVER || opt == OPT_NTP_SERVER) { struct prefix_list *value; if(olen % 16 != 0) { fprintf(stderr, "Unexpected length for %s.\n", opt == OPT_IPv6_ADDRESS ? "address" : "server"); goto fail; } value = raw_prefix_list(body + i + 2, olen, IPv6_ADDRESS); if(opt == OPT_MY_IPv6_ADDRESS) { CAT(config->server_ipv6, value); } else if(opt == OPT_IPv6_ADDRESS) { CAT(config->ipv6_address, value); if(mandatory) config->ipv6_mandatory = 1; } else if(opt == OPT_NAME_SERVER) { CAT(config->name_server, value); } else if(opt == OPT_NTP_SERVER) { CAT(config->ntp_server, value); } else { abort(); } } else if(opt == OPT_MY_IPv4_ADDRESS || opt == OPT_IPv4_ADDRESS) { struct prefix_list *value; value = raw_prefix_list(body + i + 2, olen, IPv4_ADDRESS); if(opt == OPT_MY_IPv4_ADDRESS) { CAT(config->server_ipv4, value); } else if(opt == OPT_IPv4_ADDRESS) { CAT(config->ipv4_address, value); if(mandatory) config->ipv4_mandatory = 1; } else { abort(); } } else { if(mandatory) debugf(1, "Unsupported option %d\n", opt); if(mandatory && configure >= 0) goto fail; } mandatory = 0; i += olen + 2; } if(configure >= 0 && expires <= 0) goto fail; config->origin_m = now.tv_sec; config->expires = MIN(25 * 3600, expires); if(origin >= 0) { config->origin = origin; if(configure >= 0) { if(origin >= real.tv_sec - 300 && origin <= real.tv_sec + 300) { time_confirm(1); } else { fprintf(stderr, "Detected clock skew (client=%ld, server=%ld).\n", (long)now.tv_sec, (long)config->origin); time_confirm(0); } } } if(configure >= 0) { config->expires_m = config->origin_m + config->expires; if(clock_status != CLOCK_BROKEN && config->origin > 0) { config->expires_m = MIN(config->expires_m, config->origin_m + (real.tv_sec - config->origin) + config->expires); } if(config->ipv6_address) { config->our_ipv6_address = copy_prefix_list(config->ipv6_address); } else if(config->ipv6_prefix && config->ipv6_prefix->n > 0 && config->ipv6_prefix->l[0].plen <= 64) { unsigned char address[16]; int have_address = 0; memcpy(address, config->ipv6_prefix->l[0].p, 16); i = 0; while(interfaces[i]) { rc = if_eui64(interfaces[i], address + 8); if(rc >= 0) have_address = 1; i++; } if(!have_address) { rc = random_eui64(address + 8); if(rc >= 0) have_address = 1; } if(have_address) config->our_ipv6_address = raw_prefix_list(address, 16, IPv6_ADDRESS); if(!config->our_ipv6_address) fprintf(stderr, "Couldn't generate IPv6 address.\n"); } } if(configure > 0 && config_script[0] != '\0' && config->expires_m > now.tv_sec + 5) { if(config_data) { if(!config_data_compatible(config_data, config)) unconfigure(interfaces); } if(!config_data) { rc = run_script("start", config, interfaces); if(rc > 0) config_data = copy_config_data(config); } else { config_data->origin = config->origin; config_data->origin_m = config->origin_m; config_data->expires = config->expires; config_data->expires_m = config->expires_m; } } return config; fail: free_config_data(config); return NULL; #undef CAT } int unconfigure(char **interfaces) { int rc; rc = run_script("stop", config_data, interfaces); free_config_data(config_data); config_data = NULL; return rc; } int query_body(unsigned char opcode, int time, const unsigned char *ipv4, unsigned char *buf, int buflen) { int i, j; unsigned nowsecs, expires; struct timeval real; int clock_status; get_real_time(&real, &clock_status); /* Skip header */ i = 4; if(i >= buflen) goto fail; if(clock_status != CLOCK_BROKEN) { nowsecs = htonl(real.tv_sec); buf[i++] = OPT_ORIGIN_TIME; if(i >= buflen) goto fail; buf[i++] = 4; if(i >= buflen - 4) goto fail; memcpy(buf + i, &nowsecs, 4); i += 4; } if(time > 0) { expires = htonl(time); buf[i++] = OPT_EXPIRES; if(i >= buflen) goto fail; buf[i++] = 4; if(i >= buflen - 4) goto fail; memcpy(buf + i, &expires, 4); i += 4; } if((af & 1)) { buf[i++] = OPT_IPv4_ADDRESS; if(i >= buflen) goto fail; buf[i++] = ipv4 ? 4 : 0; if(i >= buflen) goto fail; if(ipv4) { if(i >= buflen - 4) goto fail; memcpy(buf + i, ipv4, 4); i += 4; } if(request_prefix_delegation) { buf[i++] = OPT_IPv4_PREFIX_DELEGATION; if(i >= buflen) goto fail; buf[i++] = 0; if(i >= buflen) goto fail; } } if((af & 2)) { buf[i++] = OPT_IPv6_PREFIX; if(i >= buflen) goto fail; buf[i++] = 0; if(i >= buflen) goto fail; if(request_prefix_delegation) { buf[i++] = OPT_IPv6_PREFIX_DELEGATION; if(i >= buflen) goto fail; buf[i++] = 0; if(i >= buflen) goto fail; } } if(opcode != AHCP_RELEASE) { if(!nodns) { buf[i++] = OPT_NAME_SERVER; if(i >= buflen) goto fail; buf[i++] = 0; if(i >= buflen) goto fail; } buf[i++] = OPT_NTP_SERVER; if(i >= buflen) goto fail; buf[i++] = 0; if(i >= buflen) goto fail; } /* Set up header */ j = i - 4; buf[0] = opcode; buf[1] = 0; buf[2] = (j >> 8) & 0xFF; buf[3] = j & 0xFF; return i; fail: return -1; } int server_body(unsigned char opcode, struct config_data *config, unsigned char *buf, int buflen) { int i, j; struct timeval now, real; int clock_status; unsigned nowsecs, expires; gettime(&now, NULL); get_real_time(&real, &clock_status); /* Skip header */ i = 4; if(i >= buflen) goto fail; if(now.tv_sec >= config->expires_m) return -1; expires = config->expires; if(clock_status != CLOCK_BROKEN) { nowsecs = htonl(real.tv_sec); buf[i++] = OPT_ORIGIN_TIME; if(i >= buflen) goto fail; buf[i++] = 4; if(i >= buflen - 4) goto fail; memcpy(buf + i, &nowsecs, 4); i += 4; } expires = htonl(expires); buf[i++] = OPT_MANDATORY; if(i >= buflen) goto fail; buf[i++] = OPT_EXPIRES; if(i >= buflen) goto fail; buf[i++] = 4; if(i >= buflen - 4) goto fail; memcpy(buf + i, &expires, 4); i += 4; if(config->ipv4_address) { int j; buf[i++] = OPT_IPv4_ADDRESS; if(i >= buflen) goto fail; buf[i++] = 4 * config->ipv4_address->n; if(i >= buflen) goto fail; for(j = 0; j < config->ipv4_address->n; j++) { if(i >= buflen - 4) goto fail; memcpy(buf + i, config->ipv4_address->l[j].p + 12, 4); i += 4; } } if(config->ipv6_prefix) { int j; buf[i++] = OPT_IPv6_PREFIX; if(i >= buflen) goto fail; buf[i++] = 17 * config->ipv6_prefix->n; if(i >= buflen) goto fail; for(j = 0; j < config->ipv6_prefix->n; j++) { if(i >= buflen - 17) goto fail; memcpy(buf + i, config->ipv6_prefix->l[j].p, 16); i += 16; buf[i++] = config->ipv6_prefix->l[j].plen; } } if(config->ipv6_address) { int j; buf[i++] = OPT_IPv6_ADDRESS; if(i >= buflen) goto fail; buf[i++] = 16 * config->ipv6_address->n; if(i >= buflen) goto fail; for(j = 0; j < config->ipv6_address->n; j++) { if(i >= buflen - 16) goto fail; memcpy(buf + i, config->ipv6_address->l[j].p, 16); i += 16; } } if(config->name_server) { int j; buf[i++] = OPT_NAME_SERVER; if(i >= buflen) goto fail; buf[i++] = 16 * config->name_server->n; if(i >= buflen) goto fail; for(j = 0; j < config->name_server->n; j++) { if(i >= buflen - 6) goto fail; memcpy(buf + i, config->name_server->l[j].p, 16); i += 16; } } if(config->ntp_server) { int j; buf[i++] = OPT_NTP_SERVER; if(i >= buflen) goto fail; buf[i++] = 16 * config->ntp_server->n; if(i >= buflen) goto fail; for(j = 0; j < config->ntp_server->n; j++) { if(i >= buflen - 6) goto fail; memcpy(buf + i, config->ntp_server->l[j].p, 16); i += 16; } } if(config->server_ipv6) { int j; buf[i++] = OPT_MY_IPv6_ADDRESS; if(i >= buflen) goto fail; buf[i++] = 16 * config->server_ipv6->n; if(i >= buflen) goto fail; for(j = 0; j < config->server_ipv6->n; j++) { if(i >= buflen - 16) goto fail; memcpy(buf + i, config->server_ipv6->l[j].p, 16); i += 16; } } if(config->server_ipv4) { int j; buf[i++] = OPT_MY_IPv4_ADDRESS; if(i >= buflen) goto fail; buf[i++] = 4 * config->server_ipv4->n; if(i >= buflen) goto fail; for(j = 0; j < config->server_ipv4->n; j++) { if(i >= buflen - 4) goto fail; memcpy(buf + i, config->server_ipv4->l[j].p + 12, 4); i += 4; } } /* Set up header */ j = i - 4; buf[0] = opcode; buf[1] = 0; buf[2] = (j >> 8) & 0xFF; buf[3] = j & 0xFF; return i; fail: return -1; } int address_conflict(struct prefix_list *a, struct prefix_list *b) { int i, j; for(i = 0; i < a->n; i++) for(j = 0; j < b->n; j++) if(memcmp(a->l[i].p, b->l[j].p, 16) == 0) return 1; return 0; } /* Determine an interface's hardware address, in modified EUI-64 format */ #ifdef SIOCGIFHWADDR int if_eui64(char *ifname, unsigned char *eui) { int s, rc; struct ifreq ifr; s = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); if(s < 0) return -1; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); rc = ioctl(s, SIOCGIFHWADDR, &ifr); if(rc < 0) { int saved_errno = errno; close(s); errno = saved_errno; return -1; } close(s); switch(ifr.ifr_hwaddr.sa_family) { case ARPHRD_ETHER: case ARPHRD_FDDI: case ARPHRD_IEEE802_TR: case ARPHRD_IEEE802: { unsigned char *mac; mac = (unsigned char *)ifr.ifr_hwaddr.sa_data; /* Check for null address and group and global bits */ if(memcmp(mac, zeroes, 6) == 0 || (mac[0] & 1) != 0 || (mac[0] & 2) != 0) { errno = ENOENT; return -1; } memcpy(eui, mac, 3); eui[3] = 0xFF; eui[4] = 0xFE; memcpy(eui + 5, mac + 3, 3); eui[0] ^= 2; return 1; } case ARPHRD_EUI64: case ARPHRD_IEEE1394: case ARPHRD_INFINIBAND: { unsigned char *mac; mac = (unsigned char *)ifr.ifr_hwaddr.sa_data; if(memcmp(mac, zeroes, 8) == 0 || (mac[0] & 1) != 0 || (mac[0] & 2) != 0) { errno = ENOENT; return -1; } memcpy(eui, mac, 64); eui[0] ^= 2; return 1; } default: errno = ENOENT; return -1; } } #else #warning Cannot determine MAC addresses on this platform int if_eui64(char *ifname, unsigned char *eui) { errno = ENOSYS; return -1; } #endif int random_eui64(unsigned char *eui) { int fd, rc; /* If there's no /dev/urandom, this will fail with ENOENT, which the caller will deal with gracefully. */ fd = open("/dev/urandom", O_RDONLY); if(fd < 0) return -1; rc = read(fd, eui, 8); if(rc < 8) return -1; close(fd); eui[0] &= ~3; return 1; } ahcpd-0.53/configure.h000066400000000000000000000055211167023647600146710ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* This is used both to hold the contents of a message and to hold our current configuration. */ struct config_data { /* The following fields come from a message */ unsigned origin, origin_m, expires, expires_m; struct prefix_list *server_ipv6, *server_ipv4; struct prefix_list *ipv6_prefix, *ipv4_prefix, *ipv6_address, *ipv4_address, *ipv6_prefix_delegation, *ipv4_prefix_delegation; int ipv4_mandatory, ipv6_mandatory, ipv4_delegation_mandatory, ipv6_delegation_mandatory; struct prefix_list *name_server, *ntp_server; /* This field is only in our configuration. */ struct prefix_list *our_ipv6_address; }; extern struct config_data *config_data; unsigned int config_renew_time(void); void free_config_data(struct config_data *config); int config_data_compatible(struct config_data *config1, struct config_data *config2); struct config_data *copy_config_data(struct config_data *config); struct config_data *make_config_data(int expires, unsigned char *ipv4, struct server_config *server_config, char **interfaces); struct config_data *parse_message(int configure, const unsigned char *data, int len, char **interfaces); int unconfigure(char **interfaces); int query_body(unsigned char opcode, int time, const unsigned char *ipv4, unsigned char *buf, int buflen); int server_body(unsigned char opcode, struct config_data *config, unsigned char *buf, int buflen); int address_conflict(struct prefix_list *a, struct prefix_list *b); int if_eui64(char *ifname, unsigned char *eui); int random_eui64(unsigned char *eui); ahcpd-0.53/lease.c000066400000000000000000000533621167023647600140020ustar00rootroot00000000000000/* Copyright (c) 2008, 2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ahcpd.h" #include "monotonic.h" #include "lease.h" #ifdef NO_SERVER int lease_init(const char *dir, const unsigned char *first, const unsigned char *last, int debug) { return -1; } int take_lease(const unsigned char *client_id, int client_len, const unsigned char *suggested_ipv4, unsigned char *ipv4_return, unsigned *lease_time, int commit) { return -1; } int release_lease(const unsigned char *client_id, int client_len, const unsigned char *ipv4) { return -1; } #else #define LEASE_GRACE_TIME 666 #define LEASE_PURGE_TIME (16 * 24 * 3600 + 666) static unsigned int first_address = 0, last_address = 0; const char *lease_directory = NULL; /* A table mapping known IPs to leases. If an entry is missing, everything is still safe, although we might be unable to give out leases in some cases; however, if it is incorrect, then we might incorrectly expire relative leases. */ #define MAX_LEASE_ENTRIES 16384 struct lease_entry { unsigned char *id; int id_len; unsigned address; unsigned lease_orig; /* real time, 0 if unknown */ unsigned lease_time; time_t lease_end_m; /* monotonic time, may be negative if expired */ }; static struct lease_entry *entries = NULL; static int numentries = 0; static int maxentries = 0; static unsigned char * address_ipv4(unsigned a, unsigned char *ipv4) { a = htonl(a); memcpy(ipv4, &a, 4); return ipv4; } static unsigned ipv4_address(const unsigned char *ipv4) { unsigned a; memcpy(&a, ipv4, 4); return ntohl(a); } static int entry_match(struct lease_entry *entry, const unsigned char *id, int id_len) { if(entry->id == NULL) return 0; return (entry->id_len == id_len && memcmp(entry->id, id, id_len) == 0); } static struct lease_entry * find_entry(unsigned address) { int i; for(i = 0; i < numentries; i++) { if(entries[i].address == address) return &entries[i]; } return NULL; } static struct lease_entry * find_entry_by_id(const unsigned char *id, int id_len) { int i; for(i = 0; i < numentries; i++) { if(entry_match(&entries[i], id, id_len)) return &entries[i]; } return NULL; } static unsigned int find_entryless(unsigned first, unsigned last) { unsigned a; int i; for(a = first; a <= last; a++) { for(i = 0; i < numentries; i++) { if(entries[i].address == a) break; } if(i >= numentries) return a; } return 0; } struct lease_entry * find_oldest_entry() { int i, j = -1; unsigned age = 0, a; struct timeval now; gettime(&now, NULL); for(i = 0; i < numentries; i++) { if(entries[i].id == NULL) continue; a = now.tv_sec - entries[i].lease_end_m; if(a > age) { age = a; j = i; } } return j >= 0 ? &entries[j] : NULL; } static struct lease_entry * add_entry(const unsigned char *id, int id_len, unsigned int address, unsigned lease_orig, unsigned lease_time, time_t lease_end_m) { struct lease_entry *entry; int i; for(i = 0; i < numentries; i++) { if(entries[i].address == address) { if(!entry_match(&entries[i], id, id_len)) return NULL; entry = &entries[i]; goto done; } } entry = NULL; for(i = 0; i < numentries; i++) { if(entries[i].id == NULL) { entry = &entries[i]; break; } } if(entry == NULL) { if(numentries >= maxentries) { if(maxentries < MAX_LEASE_ENTRIES) { int n = MIN(maxentries * 2, MAX_LEASE_ENTRIES); struct lease_entry *new = realloc(entries, n * sizeof(struct lease_entry)); if(new) { entries = new; maxentries = n; } } } if(numentries < maxentries) entry = &entries[numentries++]; } if(entry == NULL) { entry = find_oldest_entry(); if(entry == NULL) return NULL; free(entry->id); entry->id = NULL; entry->id_len = 0; entry->address = 0; entry->lease_orig = 0; entry->lease_time = 0; entry->lease_end_m = 0; } entry->id = malloc(id_len); if(entry->id == NULL) return NULL; memcpy(entry->id, id, id_len); entry->id_len = id_len; entry->address = address; done: entry->lease_orig = lease_orig; entry->lease_time = lease_time; entry->lease_end_m = lease_end_m; return entry; } static char * lease_file(const unsigned char *ipv4, char *buf, int bufsize) { const char *p; int n; n = strlen(lease_directory); if(n >= bufsize - 2) return NULL; memcpy(buf, lease_directory, n); buf[n++] = '/'; p = inet_ntop(AF_INET, ipv4, buf + n, bufsize - n); if(p == NULL) return NULL; return buf; } static int close_lease_file(int fd, int modified) { int rc; again: if(modified) { rc = fsync(fd); if(rc < 0) { int save; if(errno == EINTR) goto again; save = errno; close(fd); errno = save; return -1; } } rc = close(fd); if(rc < 1 && errno == EINTR) goto again; return rc; } static int read_lease_file(int fd, const unsigned char *ipv4, unsigned *lease_orig_return, unsigned *lease_time_return, unsigned char *ipv4_return, unsigned char *client_buf, int client_len) { int rc, i; struct iovec iov[2]; char head[20], name[INET_ADDRSTRLEN]; unsigned lease_orig, lease_time; i = 0; iov[i].iov_base = head; iov[i++].iov_len = 20; if(client_len > 0) { iov[i].iov_base = client_buf; iov[i++].iov_len = client_len; } rc = readv(fd, iov, i); if(rc < 0) { perror("read(lease_file)"); return -1; } if(rc < 20 || rc > 20 + client_len) { fprintf(stderr, "Truncated lease file for %s.\n", name); return -1; } if(memcmp("AHCP", head, 4) != 0) { fprintf(stderr, "Corrupted lease file for %s.\n", name); return -1; } if(memcmp("\1\0\0\0", head + 4, 4) != 0) { fprintf(stderr, "Lease file %s has wrong version.\n", name); return -1; } if(ipv4 && memcmp(ipv4, head + 8, 4) != 0) { fprintf(stderr, "Mismatched lease file for %s.\n", name); return -1; } memcpy(&lease_orig, head + 12, 4); lease_orig = ntohl(lease_orig); memcpy(&lease_time, head + 16, 4); lease_time = ntohl(lease_time); if(lease_orig_return) *lease_orig_return = lease_orig; if(lease_time_return) *lease_time_return = lease_time; if(ipv4_return) memcpy(ipv4_return, head + 8, 4); return rc - 20; } static int write_lease_file(int fd, const unsigned char *ipv4, unsigned lease_orig, unsigned lease_time, const unsigned char *client_id, int client_len) { struct iovec iov[5]; int i; int rc; if(client_len > 650) return -1; lease_orig = htonl(lease_orig); lease_time = htonl(lease_time); i = 0; iov[i].iov_base = "AHCP\1\0\0\0"; iov[i++].iov_len = 8; iov[i].iov_base = (void*)ipv4; iov[i++].iov_len = 4; iov[i].iov_base = &lease_orig; iov[i++].iov_len = 4; iov[i].iov_base = &lease_time; iov[i++].iov_len = 4; iov[i].iov_base = (void*)client_id; iov[i++].iov_len = client_len; rc = writev(fd, iov, i); if(rc < 20 + client_len) { perror("write(lease_file)"); return -1; } return 1; } static int update_lease_file(int fd, unsigned lease_orig, unsigned lease_time) { off_t lrc; int rc, i; struct iovec iov[2]; lease_orig = htonl(lease_orig); lease_time = htonl(lease_time); lrc = lseek(fd, 12, SEEK_SET); if(lrc < 0) { perror("lseek(lease_file)"); return -1; } i = 0; iov[i].iov_base = &lease_orig; iov[i++].iov_len = 4; iov[i].iov_base = &lease_time; iov[i++].iov_len = 4; rc = writev(fd, iov, i); if(rc < 8) { perror("write(lease_file)"); return -1; } return 1; } /* Return 1 if the file was removed. */ static int purge_lease_file(char *fn, unsigned char *ipv4) { int fd, rc; unsigned lease_orig, lease_time; struct timeval now, real; time_t stable; int clock_status; gettime(&now, &stable); get_real_time(&real, &clock_status); fd = open(fn, O_RDWR); if(fd < 0) return 0; rc = read_lease_file(fd, ipv4, &lease_orig, &lease_time, NULL, NULL, 0); if(rc < 0) { close_lease_file(fd, 0); return 0; } if(clock_status == CLOCK_TRUSTED && lease_orig > 0) { if(lease_orig + lease_time + LEASE_PURGE_TIME < real.tv_sec) { rc = unlink(fn); if(rc < 0) perror("unlink(lease_file)"); close_lease_file(fd, 1); return (rc >= 0); } } close_lease_file(fd, 0); return 0; } /* Make a relative lease absolute. */ static int mutate_lease(char *fn, const unsigned char *ipv4, struct lease_entry *entry) { int fd; unsigned lease_orig, lease_time; int rc; struct timeval now, real; time_t stable; int clock_status; time_t orig; gettime(&now, &stable); get_real_time(&real, &clock_status); if(clock_status != CLOCK_TRUSTED) return -1; if(entry && entry->address != ipv4_address(ipv4)) { fprintf(stderr, "Entry mismatch when mutating!\n"); return -1; } fd = open(fn, O_RDWR); if(fd < 0) return 0; rc = read_lease_file(fd, ipv4, &lease_orig, &lease_time, NULL, NULL, 0); if(rc < 0) goto fail; if(lease_orig != 0) goto fail; if(entry && stable >= lease_time) orig = real.tv_sec - (entry->lease_end_m - now.tv_sec); else orig = real.tv_sec; if(orig > 1000000000 && orig <= real.tv_sec + 300) lease_orig = orig; else lease_orig = real.tv_sec; rc = update_lease_file(fd, lease_orig, lease_time); if(rc < 0) goto fail; if(entry) entry->lease_orig = lease_orig; close_lease_file(fd, 1); return 1; fail: close_lease_file(fd, 0); return 0; } static int lease_expired(const unsigned char *ipv4, unsigned lease_orig, unsigned lease_time) { struct timeval now, real; time_t stable; int clock_status; struct lease_entry *entry; get_real_time(&real, &clock_status); if(clock_status == CLOCK_TRUSTED && lease_orig > 0) return lease_orig + LEASE_GRACE_TIME < real.tv_sec; gettime(&now, &stable); if(stable < lease_time) return 0; if(!ipv4) return 0; entry = find_entry(ipv4_address(ipv4)); if(!entry) return 0; return entry->lease_end_m + LEASE_GRACE_TIME > now.tv_sec; } static int get_lease(const unsigned char *client_id, int client_len, const unsigned char *ipv4, unsigned lease_time, int commit) { unsigned char buf[512]; char fn[256], *p; int fd, rc; unsigned lease_orig, old_orig, old_time; time_t lease_end_m; int clock_status; struct timeval now, real; get_real_time(&real, &clock_status); gettime(&now, NULL); lease_orig = clock_status == CLOCK_TRUSTED ? real.tv_sec : 0; lease_end_m = now.tv_sec + lease_time; p = lease_file(ipv4, fn, 256); if(p == NULL) return -1; fd = open(fn, commit ? O_RDWR : O_RDONLY); if(fd < 0) { if(errno == ENOENT) { if(commit) goto create; else return 1; } perror("open(lease_file)"); return -1; } rc = read_lease_file(fd, ipv4, &old_orig, &old_time, NULL, buf, 512); if(rc < 0) { fprintf(stderr, "Couldn't read lease file.\n"); goto fail; } if(rc == client_len && memcmp(buf, client_id, client_len) == 0) { struct lease_entry *entry; entry = find_entry(ipv4_address(ipv4)); if(!entry || !entry_match(entry, client_id, client_len)) { fprintf(stderr, "Eek! Inconsistent lease entry!\n"); goto fail; } /* It would be unsafe to shorten this lease's time. */ if(clock_status == CLOCK_TRUSTED && old_orig > 0 && lease_orig > 0) lease_time = MAX(lease_time, old_orig + old_time - lease_orig); else lease_time = MAX(lease_time, entry->lease_end_m - now.tv_sec); if(commit) { rc = update_lease_file(fd, lease_orig, lease_time); if(rc < 0) goto fail; entry->lease_orig = lease_orig; entry->lease_time = lease_time; entry->lease_end_m = lease_end_m; } } else { if(!lease_expired(ipv4, old_time, old_orig)) { if(old_orig == 0 && clock_status == CLOCK_TRUSTED) goto mutate; else goto fail; } if(commit) { rc = unlink(fn); if(rc < 0) { perror("unlink(lease_file)"); goto fail; } close_lease_file(fd, 1); goto create; } } return close_lease_file(fd, commit); fail: close_lease_file(fd, 0); return -1; mutate: close_lease_file(fd, 0); mutate_lease(fn, ipv4, find_entry(ipv4_address(ipv4))); return -1; create: fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0644); if(fd < 0) { perror("creat(lease_file)"); return -1; } rc = write_lease_file(fd, ipv4, lease_orig, lease_time, client_id, client_len); if(rc < 0) goto fail; add_entry(client_id, client_len, ipv4_address(ipv4), lease_orig, lease_time, lease_end_m); return close_lease_file(fd, 1); } int release_lease(const unsigned char *client_id, int client_len, const unsigned char *ipv4) { unsigned char buf[512]; char fn[256], *p; int fd, rc, clock_status; unsigned orig; struct timeval now, real; struct lease_entry *entry; if(first_address == 0 || lease_directory == NULL) return -1; p = lease_file(ipv4, fn, 256); if(p == NULL) return -1; fd = open(fn, O_RDWR); if(fd < 0) { perror("open(lease_file)"); return -1; } rc = read_lease_file(fd, ipv4, NULL, NULL, NULL, buf, 512); if(rc < 0) goto fail; if(client_id) { if(rc != client_len || memcmp(buf, client_id, rc) != 0) goto fail; } gettime(&now, NULL); get_real_time(&real, &clock_status); if(clock_status == CLOCK_TRUSTED) orig = real.tv_sec; else orig = 0; rc = update_lease_file(fd, orig, 0); if(rc < 0) { rc = unlink(fn); if(rc < 0) { perror("unlink(lease_file)"); goto fail; } } rc = close_lease_file(fd, 1); if(rc < 0) goto fail; entry = find_entry(ipv4_address(ipv4)); entry->lease_orig = orig; entry->lease_time = 0; entry->lease_end_m = now.tv_sec; return 1; fail: close_lease_file(fd, 0); return -1; } int lease_init(const char *dir, const unsigned char *first, const unsigned char *last, int debug) { DIR *d; struct timeval now, real; int clock_status; unsigned fa, la; fa = ipv4_address(first); la = ipv4_address(last); if(fa <= 0x1000000 || fa >= la) return -1; entries = malloc(16 * sizeof(struct lease_entry)); if(entries == NULL) return -1; numentries = 0; maxentries = 16; gettime(&now, NULL); get_real_time(&real, &clock_status); d = opendir(dir); if(d == NULL) { perror("open(lease_dir)"); return -1; } while(1) { struct dirent *e; unsigned char ipv4[4], client_buf[512]; char name[INET_ADDRSTRLEN], fn[256]; const char *p; struct lease_entry *entry; unsigned lease_orig, lease_time; int fd, rc, len; e = readdir(d); if(e == NULL) break; if(e->d_name[0] == '.') continue; rc = snprintf(fn, 256, "%s/%s", dir, e->d_name); if(rc < 0 || rc >= 256) { fprintf(stderr, "Couldn't format filename %s/%s.\n", dir, e->d_name); continue; } fd = open(fn, O_RDONLY); if(fd < 0) { fprintf(stderr, "Inaccessible lease file %s.\n", e->d_name); continue; } len = read_lease_file(fd, NULL, &lease_orig, &lease_time, ipv4, client_buf, 512); close(fd); if(len < 0) { fprintf(stderr, "Corrupted lease file %s.\n", fn); continue; } p = inet_ntop(AF_INET, ipv4, name, INET_ADDRSTRLEN); if(p == NULL) { fprintf(stderr, "Couldn't format address.\n"); continue; } if(strcmp(p, e->d_name) != 0) { fprintf(stderr, "Mis-named lease file %s (should be %s).\n", fn, p); continue; } debugf(1, "Lease file %s: %u %u.\n", e->d_name, lease_orig, lease_time); if(clock_status == CLOCK_TRUSTED) { if(lease_expired(NULL, lease_orig, lease_time)) { rc = purge_lease_file(fn, ipv4); if(rc > 0) continue; } } entry = add_entry(client_buf, len, ipv4_address(ipv4), lease_orig, lease_time, now.tv_sec + lease_time); if(entry && clock_status == CLOCK_TRUSTED) { if(lease_orig == 0) mutate_lease(fn, ipv4, entry); } } closedir(d); if(numentries >= MAX_LEASE_ENTRIES) { fprintf(stderr, "Warning: lease index full.\n" "Perhaps you should recompile " "with a larger value for MAX_LEASE_ENTRIES?"); } lease_directory = dir; first_address = fa; last_address = la; return 1; } int take_lease(const unsigned char *client_id, int client_len, const unsigned char *suggested_ipv4, unsigned char *ipv4_return, unsigned *lease_time, int commit) { unsigned int a, a0; unsigned time; struct lease_entry *entry; unsigned char ipv4[4]; struct timeval now, real; int clock_status; time_t stable; if(first_address == 0 || lease_directory == NULL) return -1; if(client_len < 1) return -1; gettime(&now, &stable); get_real_time(&real, &clock_status); time = *lease_time; if(time > MAX_LEASE_TIME) time = MAX_LEASE_TIME; if(clock_status != CLOCK_TRUSTED && time > MAX_RELATIVE_LEASE_TIME) time = MAX_RELATIVE_LEASE_TIME; a0 = 0; /* Client suggested an IP. If it is in range, try that. */ if(suggested_ipv4) { a0 = ipv4_address(suggested_ipv4); entry = find_entry(a0); if(entry) { if(!entry_match(entry, client_id, client_len) && !lease_expired(suggested_ipv4, entry->lease_orig, entry->lease_time)) a0 = 0; } } /* See if we have an old lease for this client. */ if(a0 < first_address || a0 > last_address) { entry = find_entry_by_id(client_id, client_len); if(entry) a0 = entry->address; } /* Choose a free slot. */ if(a0 < first_address || a0 > last_address) a0 = find_entryless(first_address, last_address); /* Choose the oldest slot. */ if(a0 < first_address || a0 > last_address) { entry = find_oldest_entry(); if(entry) a0 = entry->address; } /* Give up, take the first one. */ if(a0 < first_address || a0 > last_address) a0 = first_address; /* Now scan all addresses in range sequentially, starting at a0. */ a = a0; do { int rc; if(a > last_address) a = first_address; rc = get_lease(client_id, client_len, address_ipv4(a, ipv4), time, commit); if(rc >= 0) { memcpy(ipv4_return, ipv4, 4); *lease_time = time; return 1; } a++; } while (a != a0); return -1; } #endif ahcpd-0.53/lease.h000066400000000000000000000031251167023647600137770ustar00rootroot00000000000000/* Copyright (c) 2008, 2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define MAX_LEASE_TIME (8 * 24 * 3600) #define MAX_RELATIVE_LEASE_TIME (4 * 3600 + 7) int lease_init(const char *dir, const unsigned char *first, const unsigned char *last, int debug); int take_lease(const unsigned char *client_id, int client_id_len, const unsigned char *suggested_ipv4, unsigned char *ipv4_return, unsigned *lease_time, int commit); int release_lease(const unsigned char *client_id, int client_id_len, const unsigned char *ipv4); ahcpd-0.53/monotonic.c000066400000000000000000000121251167023647600147060ustar00rootroot00000000000000/* Copyright (c) 2008, 2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define _GNU_SOURCE 1 #define _POSIX_C_SOURCE 200112L #if defined(__linux__) #define HAVE_ADJTIMEX #endif #if defined(__NetBSD__) || defined(__FreeBSD__) #define HAVE_NTP_GETTIME #endif #include #include #include #include #include "monotonic.h" #if defined(__GNUC__) && (__GNUC__ >= 3) #define UNLIKELY(_x) __builtin_expect(!!(_x), 0) #else #define UNLIKELY(_x) !!(_x) #endif /* Whether we use CLOCK_MONOTONIC */ static int have_posix_clocks = -1; /* The clock status, broken, untrusted, or trusted. */ static int clock_status = CLOCK_BROKEN; /* The (monotonic) time since when monotonic time has been stable. If we have TIME_MONOTONIC, this is the time at which we inited. */ time_t clock_stable_time; /* The last (monotonic) time we checked for time sync. */ time_t ntp_check_time = 0; /* These variables are used for simulating monotonic time when we don't have CLOCK_MONOTONIC. Offset is the offset to add to real time to get monotonic time; previous is the previous real time. */ static time_t offset, previous; #if defined(HAVE_ADJTIMEX) #include static int ntp_sync(void) { int rc; struct timex timex; timex.modes = 0; rc = adjtimex(&timex); return (rc >= 0 && rc != TIME_ERROR); } #elif defined(HAVE_NTP_GETTIME) #include static int ntp_sync(void) { int rc; struct ntptimeval ntptv; rc = ntp_gettime(&ntptv); return (rc >= 0 && ntptv.time_state != TIME_ERROR); } #else static int ntp_sync(void) { return 0; } #endif static void fix_clock(struct timeval *real) { if(!have_posix_clocks) { if(previous > real->tv_sec) { offset += previous - real->tv_sec; } else if(previous + MAX_SLEEP < real->tv_sec) { offset += previous + MAX_SLEEP - real->tv_sec; } } if(real->tv_sec < 1200000000) clock_status = CLOCK_BROKEN; else if(ntp_sync()) clock_status = CLOCK_TRUSTED; else clock_status = CLOCK_UNTRUSTED; } void time_init() { struct timeval now, real; #if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(CLOCK_MONOTONIC) int rc; struct timespec ts; rc = clock_gettime(CLOCK_MONOTONIC, &ts); have_posix_clocks = (rc >= 0); #else have_posix_clocks = 0; #endif gettimeofday(&real, NULL); offset = 0; previous = real.tv_sec; fix_clock(&real); gettime(&now, NULL); clock_stable_time = now.tv_sec; } /* -1: no confirmation, just check NTP status. 0: confirm that our time is broken. 1: confirm that our time seems reasonable. */ void time_confirm(int confirm) { struct timeval tv; gettimeofday(&tv, NULL); if(tv.tv_sec < 1200000000 || confirm == 0) clock_status = CLOCK_BROKEN; else if(ntp_sync()) clock_status = CLOCK_TRUSTED; else if(confirm > 0) clock_status = CLOCK_UNTRUSTED; } int get_real_time(struct timeval *tv, int *status_return) { int rc; rc = gettimeofday(tv, NULL); if(rc < 0) return rc; if(UNLIKELY(tv->tv_sec < previous || tv->tv_sec > previous + MAX_SLEEP)) { fix_clock(tv); } previous = tv->tv_sec; if(status_return) *status_return = clock_status; return rc; } int gettime(struct timeval *tv, time_t *stable) { int rc; #if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(CLOCK_MONOTONIC) if(have_posix_clocks) { struct timespec ts; rc = clock_gettime(CLOCK_MONOTONIC, &ts); if(rc < 0) return rc; tv->tv_sec = ts.tv_sec; tv->tv_usec = ts.tv_nsec / 1000; if(stable) *stable = tv->tv_sec - clock_stable_time; } else #else #warning No CLOCK_MONOTONIC on this platform #endif { rc = get_real_time(tv, NULL); if(rc < 0) return rc; tv->tv_sec += offset; } if(stable) *stable = tv->tv_sec - clock_stable_time; if(UNLIKELY(tv->tv_sec - ntp_check_time > 3600)) { time_confirm(-1); ntp_check_time = tv->tv_sec; } return rc; } ahcpd-0.53/monotonic.h000066400000000000000000000024721167023647600147170ustar00rootroot00000000000000/* Copyright (c) 2008, 2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define MAX_SLEEP 120 #define CLOCK_BROKEN 0 #define CLOCK_UNTRUSTED 1 #define CLOCK_TRUSTED 2 void time_init(void); void time_confirm(int confirm); int get_real_time(struct timeval *tv, int *status_return); int gettime(struct timeval *tv, time_t *stable); ahcpd-0.53/prefix.c000066400000000000000000000167151167023647600142070ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include "prefix.h" void free_prefix_list(struct prefix_list *l) { free(l); } struct prefix_list * copy_prefix_list(struct prefix_list *l) { struct prefix_list *c = malloc(sizeof(struct prefix_list)); if(c == NULL) return NULL; memcpy(c, l, sizeof(struct prefix_list)); return c; } int prefix_list_eq(struct prefix_list *l1, struct prefix_list *l2) { return l1->n == l2->n && memcmp(l1->l, l2->l, l1->n * sizeof(struct prefix)) == 0; } int prefix_list_v4(struct prefix_list *l) { int i; for(i = 0; i < l->n; i++) { if(l->l[i].plen < 96 || memcmp(l->l[i].p, v4prefix, 12) != 0) return 0; } return 1; } void prefix_list_extract6(unsigned char *dest, struct prefix_list *p) { if(!p || p->n == 0) memset(dest, 0, 16); memcpy(dest, p->l[0].p, 16); } void prefix_list_extract4(unsigned char *dest, struct prefix_list *p) { if(!p || p->n == 0) memset(dest, 0, 4); memcpy(dest, p->l[0].p + 12, 4); } static void parse_a6(struct prefix *p, const unsigned char *data) { memcpy(p->p, data, 16); p->plen = 0xFF; } static void parse_a4(struct prefix *p, const unsigned char *data) { memcpy(p->p, v4prefix, 12); memcpy(p->p + 12, data, 4); p->plen = 0xFF; } static void parse_p6(struct prefix *p, const unsigned char *data) { memcpy(p->p, data, 16); p->plen = data[16]; } static void parse_p4(struct prefix *p, const unsigned char *data) { memcpy(p->p, v4prefix, 12); memcpy(p->p + 12, data, 4); p->plen = data[4] + 96; } /* Note that this returns an empty prefix list, rather than NULL, when len is 0. We rely on this in order to keep track of requested options. */ struct prefix_list * raw_prefix_list(const unsigned char *data, int len, int kind) { struct prefix_list *l = calloc(1, sizeof(struct prefix_list)); int i, size; void (*parser)(struct prefix *, const unsigned char*) = NULL; if(l == NULL) return NULL; switch(kind) { case IPv6_ADDRESS: size = 16; parser = parse_a6; break; case IPv4_ADDRESS: size = 4; parser = parse_a4; break; case IPv6_PREFIX: size = 17; parser = parse_p6; break; case IPv4_PREFIX: size = 5; parser = parse_p4; break; default: abort(); } if(len % size != 0) return NULL; i = 0; while(i < len) { if(l->n >= MAX_PREFIX) break; parser(&l->l[l->n], data + i); l->n++; i += size; } return l; } /* Uses a static buffer. */ char * format_prefix_list(struct prefix_list *p, int kind) { static char buf[120]; int i, j, k; const char *r; j = 0; for(i = 0; i < p->n; i++) { switch(kind) { case IPv6_ADDRESS: r = inet_ntop(AF_INET6, p->l[i].p, buf + j, 120 - j); if(r == NULL) return NULL; j += strlen(r); break; case IPv4_ADDRESS: r = inet_ntop(AF_INET, p->l[i].p + 12, buf + j, 120 - j); if(r == NULL) return NULL; j += strlen(r); break; case ADDRESS: if(memcmp(p->l[i].p, v4prefix, 12) == 0) r = inet_ntop(AF_INET, p->l[i].p + 12, buf + j, 120 - j); else r = inet_ntop(AF_INET6, p->l[i].p, buf + j, 120 - j); if(r == NULL) return NULL; j += strlen(r); break; case IPv6_PREFIX: r = inet_ntop(AF_INET6, p->l[i].p, buf + j, 120 - j); if(r == NULL) return NULL; j += strlen(r); k = snprintf(buf + j, 120 - j, "/%u", p->l[i].plen); if(k < 0 || k >= 120 - j) return NULL; j += k; break; case IPv4_PREFIX: r = inet_ntop(AF_INET, p->l[i].p + 12, buf + j, 120 - j); if(r == NULL) return NULL; j += strlen(r); k = snprintf(buf + j, 120 - j, "/%u", p->l[i].plen - 96); if(k < 0 || k >= 120 - j) return NULL; j += k; break; default: abort(); } if(j >= 119) return NULL; if(i + 1 < p->n) buf[j++] = ' '; } if(j >= 120) return NULL; buf[j++] = '\0'; return buf; } struct prefix_list * parse_prefix(char *address, int kind) { struct prefix_list *list; unsigned char ipv6[16], ipv4[4]; int plen, rc; plen = 0xFF; switch(kind) { case IPv6_ADDRESS: rc = inet_pton(AF_INET6, address, ipv6); if(rc > 0) goto return_ipv6; return NULL; case IPv4_ADDRESS: rc = inet_pton(AF_INET, address, ipv4); if(rc > 0) goto return_ipv4; case ADDRESS: rc = inet_pton(AF_INET, address, ipv4); if(rc > 0) goto return_ipv4; rc = inet_pton(AF_INET6, address, ipv6); if(rc > 0) goto return_ipv6; return NULL; case PREFIX: { char buf[30]; char *p; p = strchr(address, '/'); if(p == NULL || p - address >= 30) return NULL; plen = atoi(p + 1); if(plen <= 0 || plen > 128) return NULL; memcpy(buf, address, p - address); buf[p - address] = '\0'; rc = inet_pton(AF_INET, buf, ipv4); if(rc > 0) { if(plen > 32) return NULL; goto return_ipv4; } rc = inet_pton(AF_INET6, buf, ipv6); if(rc > 0) goto return_ipv6; return NULL; } default: abort(); } return_ipv6: list = calloc(1, sizeof(struct prefix_list)); if(list == NULL) return NULL; list->n = 1; memcpy(list->l[0].p, ipv6, 16); list->l[0].plen = plen; return list; return_ipv4: list = calloc(1, sizeof(struct prefix_list)); if(list == NULL) return NULL; list->n = 1; memcpy(list->l[0].p, v4prefix, 12); memcpy(list->l[0].p + 12, ipv4, 4); list->l[0].plen = plen == 0xFF ? 0xFF : plen + 96; return list; } struct prefix_list * cat_prefix_list(struct prefix_list *p1, struct prefix_list *p2) { int i; if(p1 == NULL) return p2; if(p2 == NULL) return p1; if(p1->n + p2->n > MAX_PREFIX) return NULL; for(i = 0; i < p2->n; i++) p1->l[p1->n + i] = p2->l[i]; p1->n += p2->n; free(p2); return p1; } ahcpd-0.53/prefix.h000066400000000000000000000043261167023647600142070ustar00rootroot00000000000000/* Copyright (c) 2007-2010 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define IPv6_ADDRESS 0 #define IPv4_ADDRESS 1 #define ADDRESS 2 #define IPv6_PREFIX 3 #define IPv4_PREFIX 4 #define PREFIX 5 extern const unsigned char v4prefix[16]; /* This holds both IPv6 and IPv4, and both prefixes and addresses. For IPv4, we use v4prefix above. For addresses, plen is 0xFF. */ struct prefix { unsigned char p[16]; unsigned char plen; }; #define MAX_PREFIX 8 struct prefix_list { int n; struct prefix l[MAX_PREFIX]; }; void free_prefix_list(struct prefix_list *l); struct prefix_list *copy_prefix_list(struct prefix_list *l); int prefix_list_eq(struct prefix_list *l1, struct prefix_list *l2); int prefix_list_v4(struct prefix_list *l); void prefix_list_extract4(unsigned char *dest, struct prefix_list *p); void prefix_list_extract6(unsigned char *dest, struct prefix_list *p); struct prefix_list *raw_prefix_list(const unsigned char *data, int len, int kind); char *format_prefix_list(struct prefix_list *p, int kind); struct prefix_list *parse_prefix(char *address, int kind); struct prefix_list *cat_prefix_list(struct prefix_list *p1, struct prefix_list *p2); ahcpd-0.53/protocol.h000066400000000000000000000031331167023647600145460ustar00rootroot00000000000000/* Copyright (c) 2007-2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Opcodes */ #define AHCP_DISCOVER 0 #define AHCP_OFFER 1 #define AHCP_REQUEST 2 #define AHCP_ACK 3 #define AHCP_NACK 4 #define AHCP_RELEASE 5 /* Options */ #define OPT_PAD 0 #define OPT_MANDATORY 1 #define OPT_ORIGIN_TIME 2 #define OPT_EXPIRES 3 #define OPT_MY_IPv6_ADDRESS 4 #define OPT_MY_IPv4_ADDRESS 5 #define OPT_IPv6_PREFIX 6 #define OPT_IPv4_PREFIX 7 #define OPT_IPv6_ADDRESS 8 #define OPT_IPv4_ADDRESS 9 #define OPT_IPv6_PREFIX_DELEGATION 10 #define OPT_IPv4_PREFIX_DELEGATION 11 #define OPT_NAME_SERVER 12 #define OPT_NTP_SERVER 13 ahcpd-0.53/transport.c000066400000000000000000000151711167023647600147410ustar00rootroot00000000000000/* Copyright (c) 2008, 2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "ahcpd.h" #include "monotonic.h" #include "transport.h" unsigned myseqno; #define NUMDUPLICATE 64 struct duplicate { struct timeval time; unsigned char src[8]; unsigned char dest[8]; unsigned char nonce[4]; }; struct duplicate duplicate[NUMDUPLICATE]; int next_duplicate = 0; static int really_send_packet(struct sockaddr *sin, int sinlen, unsigned char hopcount, unsigned char original_hopcount, const unsigned char *nonce, const unsigned char *src, const unsigned char *dest, const unsigned char *data, size_t datalen) { struct iovec iovec[2]; struct msghdr msg; unsigned char header[24]; int rc, ret; header[0] = 43; header[1] = 1; header[2] = hopcount; header[3] = original_hopcount; memcpy(header + 4, nonce, 4); memcpy(header + 8, src, 8); memcpy(header + 16, dest ? dest : ones, 8); iovec[0].iov_base = (void*)header; iovec[0].iov_len = sizeof(header); iovec[1].iov_base = (void*)data; iovec[1].iov_len = datalen; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iovec; msg.msg_iovlen = 2; if(sin) { msg.msg_name = sin; msg.msg_namelen = sinlen; rc = sendmsg(protocol_socket, &msg, 0); if(rc < 0) { int save = errno; perror("send"); errno = save; return -1; } return 1; } else { int i, saved_errno = EHOSTUNREACH; ret = -1; for(i = 0; i < numnetworks; i++) { struct sockaddr_in6 sin6; if(networks[i].ifindex <= 0) continue; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, &protocol_group, 16); sin6.sin6_port = htons(protocol_port); sin6.sin6_scope_id = networks[i].ifindex; msg.msg_name = (struct sockaddr*)&sin6; msg.msg_namelen = sizeof(sin6); rc = sendmsg(protocol_socket, &msg, 0); if(rc < 0) { saved_errno = errno; perror("send"); /* but don't reset ret -- only return -1 if all sends failed. */ } else { ret = 1; } errno = saved_errno; } return ret; } } int send_packet(struct sockaddr *sin, int sinlen, const unsigned char *dest, int hopcount, const unsigned char *buf, size_t bufsize) { unsigned char nonce[4]; if(hopcount <= 0) return 0; memcpy(nonce, &myseqno, 4); myseqno++; return really_send_packet(sin, sinlen, hopcount, hopcount, nonce, myid, dest, buf, bufsize); } static int check_duplicate(const unsigned char *header) { int i; struct timeval now; gettime(&now, NULL); for(i = 0; i < NUMDUPLICATE; i++) { if(duplicate[i].time.tv_sec < now.tv_sec - 120) continue; if(memcmp(header + 4, duplicate[i].nonce, 4) == 0 && memcmp(header + 8, duplicate[i].src, 8) == 0 && memcmp(header + 16, duplicate[i].dest, 8) == 0) return 1; } return 0; } static void record_duplicate(const unsigned char *header) { memcpy(duplicate[next_duplicate].nonce, header + 4, 4); memcpy(duplicate[next_duplicate].src, header + 8, 8); memcpy(duplicate[next_duplicate].dest, header + 16, 8); gettime(&duplicate[next_duplicate].time, NULL); next_duplicate = (next_duplicate + 1) % NUMDUPLICATE; } /* Take an incoming packet, forward it if necessary, return 2 if it needs to be handled by the local node. */ int handle_packet(int ll, const unsigned char *buf, size_t buflen) { if(buflen < 2) { debugf(1, "Received truncated packet.\n"); return 0; } if(buf[0] != 43) { debugf(1, "Received corrupted packet.\n"); return 0; } if(buf[1] != 1) { debugf(2, "Received packet with version %d.\n", buf[1]); return 0; } if(buflen < 24) { debugf(1, "Received truncated packet.\n"); return 0; } if(buf[2] <= 0 || buf[3] <= 0 || buf[2] > buf[3]) { debugf(1, "Received packet with zero hop count.\n"); return 0; } if(memcmp(buf + 8, zeroes, 8) == 0 || memcmp(buf + 8, ones, 8) == 0) { debugf(1, "Received packet with martian source.\n"); return 0; } if(memcmp(buf + 16, zeroes, 8) == 0) { debugf(1, "Received packet with martian destination.\n"); return 0; } /* The following tests do trigger in normal operation. */ if(memcmp(buf + 8, myid, 8) == 0) { debugf(3, "Suppressed packet from self.\n"); return 0; } if(check_duplicate(buf)) { debugf(3, "Suppressed duplicate.\n"); return 0; } record_duplicate(buf); if(memcmp(buf + 16, myid, 8) == 0) return 2; if(buf[2] >= 2) { debugf(2, "Forwarding packet, %d/%d hops left.\n", buf[2] - 1, buf[3]); usleep(random() % 50000); really_send_packet(NULL, 0, buf[2] - 1, buf[3], buf + 4, buf + 8, buf + 16, buf + 24, buflen - 24); } if(memcmp(buf + 16, ones, 8) == 0) return 2; else return 1; } ahcpd-0.53/transport.h000066400000000000000000000024731167023647600147470ustar00rootroot00000000000000/* Copyright (c) 2008, 2009 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extern unsigned myseqno; int send_packet(struct sockaddr *sin, int sinlen, const unsigned char *dest, int hopcount, const unsigned char *buf, size_t bufsize); int handle_packet(int ll, const unsigned char *buf, size_t buflen);