summaryrefslogtreecommitdiff
path: root/contrib/lease-tools/dhcp_release.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/lease-tools/dhcp_release.c')
-rw-r--r--contrib/lease-tools/dhcp_release.c332
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;
+}