diff options
Diffstat (limited to 'contrib/wrt')
-rw-r--r-- | contrib/wrt/Makefile | 7 | ||||
-rw-r--r-- | contrib/wrt/README | 81 | ||||
-rw-r--r-- | contrib/wrt/dhcp_release.c | 184 | ||||
-rwxr-xr-x | contrib/wrt/lease_update.sh | 57 |
4 files changed, 329 insertions, 0 deletions
diff --git a/contrib/wrt/Makefile b/contrib/wrt/Makefile new file mode 100644 index 0000000..abda96a --- /dev/null +++ b/contrib/wrt/Makefile @@ -0,0 +1,7 @@ +CFLAGS?= -O2 + +all: dhcp_release.c + $(CC) $(CFLAGS) $(RPM_OPT_FLAGS) -Wall -W dhcp_release.c -o dhcp_release + +clean: + rm -f *~ *.o core dhcp_release diff --git a/contrib/wrt/README b/contrib/wrt/README new file mode 100644 index 0000000..862046f --- /dev/null +++ b/contrib/wrt/README @@ -0,0 +1,81 @@ +This script can be used to implement persistent leases on openWRT, DD-WRT +etc. Persistent leases are good: if the lease database is lost on a +reboot, then it will eventually be restored as hosts renew their +leases. Until a host renews (which may take hours/days) it will +not exist in the DNS if dnsmasq's DDNS function is in use. + +*WRT systems remount all non-volatile fileystems read-only after boot, +so the normal leasefile will not work. They do, however have NV +storage, accessed with the nvram command: + +/usr/lib # nvram +usage: nvram [get name] [set name=value] [unset name] [show] + +The principle is that leases are kept in NV variable with data +corresponding to the line in a leasefile: + +dnsmasq_lease_192.168.1.56=3600 00:41:4a:05:80:74 192.168.1.56 * * + +By giving dnsmasq the leasefile-ro command, it no longer creates or writes a +leasefile; responsibility for maintaining the lease database transfers +to the lease change script. At startup, in leasefile-ro mode, +dnsmasq will run + +"<lease_change_script> init" + +and read whatever that command spits out, expecting it to +be in dnsmasq leasefile format. + +So the lease change script, given "init" as argv[1] will +suck existing leases out of the NVRAM and emit them from +stdout in the correct format. + +The second part of the problem is keeping the NVRAM up-to-date: this +is done by the lease-change script which dnsmasq runs when a lease is +updated. When it is called with argv[1] as "old", "add", or "del" +it updates the relevant nvram entry. + +So, dnsmasq should be run as : + +dnsmasq --leasefile-ro --dhcp-script=/path/to/lease_update.sh + +or the same flags added to /etc/dnsmasq.conf + + + +Notes: + +This needs dnsmasq-2.33 or later to work. + +This technique will work with, or without, compilation with +HAVE_BROKEN_RTC. Compiling with HAVE_BROKEN_RTC is +_highly_recommended_ for this application since is avoids problems +with the system clock being warped by NTP, and it vastly reduces the +number of writes to the NVRAM. With HAVE_BROKEN_RTC, NVRAM is updated +only when a lease is created or destroyed; without it, a write occurs +every time a lease is renewed. + +It probably makes sense to restrict the number of active DHCP leases +to an appropriate number using dhcp-lease-max. On a new DD_WRT system, +there are about 10K bytes free in the NVRAM. Each lease record is +about 100 bytes, so restricting the number of leases to 50 will limit +use to half that. (The default limit in the distributed source is 150) + +Any UI script which reads the dnsmasq leasefile will have to be +ammended, probably by changing it to read the output of +`lease_update init` instead. + + +Thanks: + +To Steve Horbachuk for checks on the script and debugging beyond the +call of duty. + + +Simon Kelley +Fri Jul 28 11:51:13 BST 2006 + + + + + diff --git a/contrib/wrt/dhcp_release.c b/contrib/wrt/dhcp_release.c new file mode 100644 index 0000000..2ee98c2 --- /dev/null +++ b/contrib/wrt/dhcp_release.c @@ -0,0 +1,184 @@ +/* Copyright (c) 2006 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +/* dhcp_release <interface> <address> <MAC address> <client_id> + MUST be run as root - will fail otherwise. */ + +/* Send a DHCPRELEASE message via the specified interface + to tell the local DHCP server to delete a particular lease. + + The interface argument is the interface in which a DHCP + request _would_ be received if it was coming from the client, + rather than being faked up here. + + The address argument is a dotted-quad IP addresses and mandatory. + + The MAC address is colon separated hex, and is mandatory. It may be + prefixed by an address-type byte followed by -, eg + + 10-11:22:33:44:55:66 + + but if the address-type byte is missing it is assumed to be 1, the type + for ethernet. This encoding is the one used in dnsmasq lease files. + + The client-id is optional. If it is "*" then it treated as being missing. +*/ + +#include <sys/types.h> +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <net/if_arp.h> +#include <sys/ioctl.h> + +#define DHCP_CHADDR_MAX 16 +#define BOOTREQUEST 1 +#define DHCP_COOKIE 0x63825363 +#define OPTION_SERVER_IDENTIFIER 54 +#define OPTION_CLIENT_ID 61 +#define OPTION_MESSAGE_TYPE 53 +#define OPTION_END 255 +#define DHCPRELEASE 7 +#define DHCP_SERVER_PORT 67 + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; + +struct dhcp_packet { + u8 op, htype, hlen, hops; + u32 xid; + u16 secs, flags; + struct in_addr ciaddr, yiaddr, siaddr, giaddr; + u8 chaddr[DHCP_CHADDR_MAX], sname[64], file[128]; + u32 cookie; + unsigned char options[308]; +}; + +static int parse_hex(char *in, unsigned char *out, int maxlen, int *mac_type) +{ + int i = 0; + char *r; + + if (mac_type) + *mac_type = 0; + + while (maxlen == -1 || i < maxlen) + { + for (r = in; *r != 0 && *r != ':' && *r != '-'; r++); + if (*r == 0) + maxlen = i; + + if (r != in ) + { + if (*r == '-' && i == 0 && mac_type) + { + *r = 0; + *mac_type = strtol(in, NULL, 16); + mac_type = NULL; + } + else + { + *r = 0; + out[i] = strtol(in, NULL, 16); + i++; + } + } + in = r+1; + } + return i; +} + +int main(int argc, char **argv) +{ + struct in_addr server, lease; + int mac_type; + struct dhcp_packet packet; + unsigned char *p = packet.options; + struct sockaddr_in dest; + struct ifreq ifr; + int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (argc < 4 || argc > 5) + { + fprintf(stderr, "usage: dhcp_release <interface> <addr> <mac> [<client_id>]\n"); + exit(1); + } + + if (fd == -1) + { + perror("cannot create socket"); + exit(1); + } + + /* This voodoo fakes up a packet coming from the correct interface, which really matters for + a DHCP server */ + strcpy(ifr.ifr_name, argv[1]); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(fd, SIOCGIFADDR, &ifr) == -1 || setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + { + perror("cannot setup interface"); + exit(1); + } + + server = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + lease.s_addr = inet_addr(argv[2]); + + memset(&packet, 0, sizeof(packet)); + + packet.hlen = parse_hex(argv[3], packet.chaddr, DHCP_CHADDR_MAX, &mac_type); + if (mac_type == 0) + packet.htype = ARPHRD_ETHER; + else + packet.htype = mac_type; + + packet.op = BOOTREQUEST; + packet.ciaddr = lease; + packet.cookie = htonl(DHCP_COOKIE); + + *(p++) = OPTION_MESSAGE_TYPE; + *(p++) = 1; + *(p++) = DHCPRELEASE; + + *(p++) = OPTION_SERVER_IDENTIFIER; + *(p++) = sizeof(server); + memcpy(p, &server, sizeof(server)); + p += sizeof(server); + + if (argc == 5 && strcmp(argv[4], "*") != 0) + { + unsigned int clid_len = parse_hex(argv[4], p+2, 255, NULL); + *(p++) = OPTION_CLIENT_ID; + *(p++) = clid_len; + p += clid_len; + } + + *(p++) = OPTION_END; + + dest.sin_family = AF_INET; + dest.sin_port = ntohs(DHCP_SERVER_PORT); + dest.sin_addr = server; + + if (sendto(fd, &packet, sizeof(packet), 0, + (struct sockaddr *)&dest, sizeof(dest)) == 1) + { + perror("sendto failed"); + exit(1); + } + + return 0; +} diff --git a/contrib/wrt/lease_update.sh b/contrib/wrt/lease_update.sh new file mode 100755 index 0000000..2171d6b --- /dev/null +++ b/contrib/wrt/lease_update.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +# Copyright (c) 2006 Simon Kelley +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 dated June, 1991. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + + +# if $1 is add del or old, this is a dnsmasq-called lease-change +# script, update the nvram database. if $1 is init, emit a +# dnsmasq-format lease file to stdout representing the current state of the +# database, this is called by dnsmasq at startup. + +NVRAM=/usr/sbin/nvram +PREFIX=dnsmasq_lease_ + +# Arguments. +# $1 is action (add, del, old) +# $2 is MAC +# $3 is address +# $4 is hostname (optional, may be unset) + +# env. +# DNSMASQ_LEASE_LENGTH or DNSMASQ_LEASE_EXPIRES (which depends on HAVE_BROKEN_RTC) +# DNSMASQ_CLIENT_ID (optional, may be unset) + +# File. +# length|expires MAC addr hostname|* CLID|* + +# Primary key is address. + +NVRAM=/usr/sbin/nvram +PREFIX=dnsmasq_lease_ + +if [ ${1} = init ] ; then + ${NVRAM} show | sed -n -e "/^${PREFIX}.*/ s/^.*=//p" +else + if [ ${1} = del ] ; then + ${NVRAM} unset ${PREFIX}${3} + fi + + if [ ${1} = old ] || [ ${1} = add ] ; then + ${NVRAM} set ${PREFIX}${3}="${DNSMASQ_LEASE_LENGTH:-}${DNSMASQ_LEASE_EXPIRES:-} ${2} ${3} ${4:-*} ${DNSMASQ_CLIENT_ID:-*}" + fi + ${NVRAM} commit +fi + + + + + |