diff options
Diffstat (limited to 'contrib/lease-tools/dhcp_release.c')
-rw-r--r-- | contrib/lease-tools/dhcp_release.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/contrib/lease-tools/dhcp_release.c b/contrib/lease-tools/dhcp_release.c new file mode 100644 index 0000000..201fcd3 --- /dev/null +++ b/contrib/lease-tools/dhcp_release.c @@ -0,0 +1,332 @@ +/* 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> +#include <linux/types.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <errno.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 struct iovec iov; + +static int expand_buf(struct iovec *iov, size_t size) +{ + void *new; + + if (size <= iov->iov_len) + return 1; + + if (!(new = malloc(size))) + { + errno = ENOMEM; + return 0; + } + + if (iov->iov_base) + { + memcpy(new, iov->iov_base, iov->iov_len); + free(iov->iov_base); + } + + iov->iov_base = new; + iov->iov_len = size; + + return 1; +} + +static ssize_t netlink_recv(int fd) +{ + struct msghdr msg; + ssize_t rc; + + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + while (1) + { + msg.msg_flags = 0; + while ((rc = recvmsg(fd, &msg, MSG_PEEK)) == -1 && errno == EINTR); + + /* 2.2.x doesn't support MSG_PEEK at all, returning EOPNOTSUPP, so we just grab a + big buffer and pray in that case. */ + if (rc == -1 && errno == EOPNOTSUPP) + { + if (!expand_buf(&iov, 2000)) + return -1; + break; + } + + if (rc == -1 || !(msg.msg_flags & MSG_TRUNC)) + break; + + if (!expand_buf(&iov, iov.iov_len + 100)) + return -1; + } + + /* finally, read it for real */ + while ((rc = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR); + + return rc; +} + +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; +} + +static int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask) +{ + return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); +} + +static struct in_addr find_interface(struct in_addr client, int fd, unsigned int index) +{ + struct sockaddr_nl addr; + struct nlmsghdr *h; + ssize_t len; + + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + + addr.nl_family = AF_NETLINK; + addr.nl_pad = 0; + addr.nl_groups = 0; + addr.nl_pid = 0; /* address to kernel */ + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = RTM_GETADDR; + req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = 1; + req.g.rtgen_family = AF_INET; + + if (sendto(fd, (void *)&req, sizeof(req), 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + perror("sendto failed"); + exit(1); + } + + while (1) + { + if ((len = netlink_recv(fd)) == -1) + { + perror("netlink"); + exit(1); + } + + for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) + if (h->nlmsg_type == NLMSG_DONE) + exit(0); + else if (h->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg *ifa = NLMSG_DATA(h); + struct rtattr *rta; + unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)); + + if (ifa->ifa_index == index && ifa->ifa_family == AF_INET) + { + struct in_addr netmask, addr; + + netmask.s_addr = htonl(0xffffffff << (32 - ifa->ifa_prefixlen)); + addr.s_addr = 0; + + for (rta = IFA_RTA(ifa); RTA_OK(rta, len1); rta = RTA_NEXT(rta, len1)) + if (rta->rta_type == IFA_LOCAL) + addr = *((struct in_addr *)(rta+1)); + + if (addr.s_addr && is_same_net(addr, client, netmask)) + return addr; + } + } + } + + exit(0); +} + +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); + int nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + + if (argc < 4 || argc > 5) + { + fprintf(stderr, "usage: dhcp_release <interface> <addr> <mac> [<client_id>]\n"); + exit(1); + } + + if (fd == -1 || nl == -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]); + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + { + perror("cannot setup interface"); + exit(1); + } + + if (inet_addr(argv[2]) == INADDR_NONE) + { + perror("invalid ip address"); + exit(1); + } + + lease.s_addr = inet_addr(argv[2]); + server = find_interface(lease, nl, if_nametoindex(argv[1])); + + 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; +} |