1/* Copyright (c) 2006 Simon Kelley
2
3   This program is free software; you can redistribute it and/or modify
4   it under the terms of the GNU General Public License as published by
5   the Free Software Foundation; version 2 dated June, 1991.
6
7   This program is distributed in the hope that it will be useful,
8   but WITHOUT ANY WARRANTY; without even the implied warranty of
9   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10   GNU General Public License for more details.
11*/
12
13/* dhcp_release <interface> <address> <MAC address> <client_id>
14   MUST be run as root - will fail otherwise. */
15
16/* Send a DHCPRELEASE message via the specified interface
17   to tell the local DHCP server to delete a particular lease.
18
19   The interface argument is the interface in which a DHCP
20   request _would_ be received if it was coming from the client,
21   rather than being faked up here.
22
23   The address argument is a dotted-quad IP addresses and mandatory.
24
25   The MAC address is colon separated hex, and is mandatory. It may be
26   prefixed by an address-type byte followed by -, eg
27
28   10-11:22:33:44:55:66
29
30   but if the address-type byte is missing it is assumed to be 1, the type
31   for ethernet. This encoding is the one used in dnsmasq lease files.
32
33   The client-id is optional. If it is "*" then it treated as being missing.
34*/
35
36#include <sys/types.h>
37#include <netinet/in.h>
38#include <net/if.h>
39#include <arpa/inet.h>
40#include <sys/socket.h>
41#include <unistd.h>
42#include <stdio.h>
43#include <string.h>
44#include <stdlib.h>
45#include <net/if_arp.h>
46#include <sys/ioctl.h>
47#include <linux/types.h>
48#include <linux/netlink.h>
49#include <linux/rtnetlink.h>
50#include <errno.h>
51
52#define DHCP_CHADDR_MAX          16
53#define BOOTREQUEST              1
54#define DHCP_COOKIE              0x63825363
55#define OPTION_SERVER_IDENTIFIER 54
56#define OPTION_CLIENT_ID         61
57#define OPTION_MESSAGE_TYPE      53
58#define OPTION_END               255
59#define DHCPRELEASE              7
60#define DHCP_SERVER_PORT         67
61
62typedef unsigned char u8;
63typedef unsigned short u16;
64typedef unsigned int u32;
65
66struct dhcp_packet {
67  u8 op, htype, hlen, hops;
68  u32 xid;
69  u16 secs, flags;
70  struct in_addr ciaddr, yiaddr, siaddr, giaddr;
71  u8 chaddr[DHCP_CHADDR_MAX], sname[64], file[128];
72  u32 cookie;
73  unsigned char options[308];
74};
75
76static struct iovec iov;
77
78static int expand_buf(struct iovec *iov, size_t size)
79{
80  void *new;
81
82  if (size <= iov->iov_len)
83    return 1;
84
85  if (!(new = malloc(size)))
86    {
87      errno = ENOMEM;
88      return 0;
89    }
90
91  if (iov->iov_base)
92    {
93      memcpy(new, iov->iov_base, iov->iov_len);
94      free(iov->iov_base);
95    }
96
97  iov->iov_base = new;
98  iov->iov_len = size;
99
100  return 1;
101}
102
103static ssize_t netlink_recv(int fd)
104{
105  struct msghdr msg;
106  ssize_t rc;
107
108  msg.msg_control = NULL;
109  msg.msg_controllen = 0;
110  msg.msg_name = NULL;
111  msg.msg_namelen = 0;
112  msg.msg_iov = &iov;
113  msg.msg_iovlen = 1;
114
115  while (1)
116    {
117      msg.msg_flags = 0;
118      while ((rc = recvmsg(fd, &msg, MSG_PEEK)) == -1 && errno == EINTR);
119
120      /* 2.2.x doesn't suport MSG_PEEK at all, returning EOPNOTSUPP, so we just grab a
121         big buffer and pray in that case. */
122      if (rc == -1 && errno == EOPNOTSUPP)
123        {
124          if (!expand_buf(&iov, 2000))
125            return -1;
126          break;
127        }
128
129      if (rc == -1 || !(msg.msg_flags & MSG_TRUNC))
130        break;
131
132      if (!expand_buf(&iov, iov.iov_len + 100))
133        return -1;
134    }
135
136  /* finally, read it for real */
137  while ((rc = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR);
138
139  return rc;
140}
141
142static int parse_hex(char *in, unsigned char *out, int maxlen, int *mac_type)
143{
144  int i = 0;
145  char *r;
146
147  if (mac_type)
148    *mac_type = 0;
149
150  while (maxlen == -1 || i < maxlen)
151    {
152      for (r = in; *r != 0 && *r != ':' && *r != '-'; r++);
153      if (*r == 0)
154        maxlen = i;
155
156      if (r != in )
157        {
158          if (*r == '-' && i == 0 && mac_type)
159           {
160              *r = 0;
161              *mac_type = strtol(in, NULL, 16);
162              mac_type = NULL;
163           }
164          else
165            {
166              *r = 0;
167	      out[i] = strtol(in, NULL, 16);
168              i++;
169            }
170        }
171      in = r+1;
172    }
173    return i;
174}
175
176static int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask)
177{
178  return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr);
179}
180
181static struct in_addr find_interface(struct in_addr client, int fd, int index)
182{
183  struct sockaddr_nl addr;
184  struct nlmsghdr *h;
185  ssize_t len;
186
187  struct {
188    struct nlmsghdr nlh;
189    struct rtgenmsg g;
190  } req;
191
192  addr.nl_family = AF_NETLINK;
193  addr.nl_pad = 0;
194  addr.nl_groups = 0;
195  addr.nl_pid = 0; /* address to kernel */
196
197  req.nlh.nlmsg_len = sizeof(req);
198  req.nlh.nlmsg_type = RTM_GETADDR;
199  req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST | NLM_F_ACK;
200  req.nlh.nlmsg_pid = 0;
201  req.nlh.nlmsg_seq = 1;
202  req.g.rtgen_family = AF_INET;
203
204  if (sendto(fd, (void *)&req, sizeof(req), 0,
205	     (struct sockaddr *)&addr, sizeof(addr)) == -1)
206    {
207      perror("sendto failed");
208      exit(1);
209    }
210
211  while (1)
212    {
213      if ((len = netlink_recv(fd)) == -1)
214	{
215	  perror("netlink");
216	  exit(1);
217	}
218
219      for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
220	if (h->nlmsg_type == NLMSG_DONE)
221	  exit(0);
222	else if (h->nlmsg_type == RTM_NEWADDR)
223          {
224            struct ifaddrmsg *ifa = NLMSG_DATA(h);
225            struct rtattr *rta;
226            unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa));
227
228            if (ifa->ifa_index == index && ifa->ifa_family == AF_INET)
229              {
230                struct in_addr netmask, addr;
231
232                netmask.s_addr = htonl(0xffffffff << (32 - ifa->ifa_prefixlen));
233                addr.s_addr = 0;
234
235                for (rta = IFA_RTA(ifa); RTA_OK(rta, len1); rta = RTA_NEXT(rta, len1))
236		  if (rta->rta_type == IFA_LOCAL)
237		    addr = *((struct in_addr *)(rta+1));
238
239                if (addr.s_addr && is_same_net(addr, client, netmask))
240		  return addr;
241	      }
242	  }
243    }
244
245  exit(0);
246}
247
248int main(int argc, char **argv)
249{
250  struct in_addr server, lease;
251  int mac_type;
252  struct dhcp_packet packet;
253  unsigned char *p = packet.options;
254  struct sockaddr_in dest;
255  struct ifreq ifr;
256  int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
257  int nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
258  struct iovec iov;
259
260  iov.iov_len = 200;
261  iov.iov_base = malloc(iov.iov_len);
262
263  if (argc < 4 || argc > 5)
264    {
265      fprintf(stderr, "usage: dhcp_release <interface> <addr> <mac> [<client_id>]\n");
266      exit(1);
267    }
268
269  if (fd == -1 || nl == -1)
270    {
271      perror("cannot create socket");
272      exit(1);
273    }
274
275  /* This voodoo fakes up a packet coming from the correct interface, which really matters for
276     a DHCP server */
277  strcpy(ifr.ifr_name, argv[1]);
278  if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
279    {
280      perror("cannot setup interface");
281      exit(1);
282    }
283
284
285  lease.s_addr = inet_addr(argv[2]);
286  server = find_interface(lease, nl, if_nametoindex(argv[1]));
287
288  memset(&packet, 0, sizeof(packet));
289
290  packet.hlen = parse_hex(argv[3], packet.chaddr, DHCP_CHADDR_MAX, &mac_type);
291  if (mac_type == 0)
292    packet.htype = ARPHRD_ETHER;
293  else
294    packet.htype = mac_type;
295
296  packet.op = BOOTREQUEST;
297  packet.ciaddr = lease;
298  packet.cookie = htonl(DHCP_COOKIE);
299
300  *(p++) = OPTION_MESSAGE_TYPE;
301  *(p++) = 1;
302  *(p++) = DHCPRELEASE;
303
304  *(p++) = OPTION_SERVER_IDENTIFIER;
305  *(p++) = sizeof(server);
306  memcpy(p, &server, sizeof(server));
307  p += sizeof(server);
308
309  if (argc == 5 && strcmp(argv[4], "*") != 0)
310    {
311      unsigned int clid_len = parse_hex(argv[4], p+2, 255, NULL);
312      *(p++) = OPTION_CLIENT_ID;
313      *(p++) = clid_len;
314      p += clid_len;
315    }
316
317  *(p++) = OPTION_END;
318
319  dest.sin_family = AF_INET;
320  dest.sin_port = ntohs(DHCP_SERVER_PORT);
321  dest.sin_addr = server;
322
323  if (sendto(fd, &packet, sizeof(packet), 0,
324	     (struct sockaddr *)&dest, sizeof(dest)) == -1)
325    {
326      perror("sendto failed");
327      exit(1);
328    }
329
330  return 0;
331}
332