blob: 201fcd3acf2d03c51d7b01bc77ca91097cc67187 [file] [log] [blame]
Simon Kelley208b65c2006-08-05 21:41:37 +01001/* 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>
Simon Kelley16972692006-10-16 20:04:18 +010047#include <linux/types.h>
48#include <linux/netlink.h>
49#include <linux/rtnetlink.h>
50#include <errno.h>
Simon Kelley208b65c2006-08-05 21:41:37 +010051
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
Simon Kelley16972692006-10-16 20:04:18 +010076static 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
Josh Soref730c6742017-02-06 16:14:04 +0000120 /* 2.2.x doesn't support MSG_PEEK at all, returning EOPNOTSUPP, so we just grab a
Simon Kelley16972692006-10-16 20:04:18 +0100121 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
Simon Kelley208b65c2006-08-05 21:41:37 +0100142static 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
Simon Kelley16972692006-10-16 20:04:18 +0100176static 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
Simon Kelley7de060b2011-08-26 17:24:52 +0100181static struct in_addr find_interface(struct in_addr client, int fd, unsigned int index)
Simon Kelley16972692006-10-16 20:04:18 +0100182{
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
Simon Kelley208b65c2006-08-05 21:41:37 +0100248int 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);
Simon Kelley16972692006-10-16 20:04:18 +0100257 int nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
Simon Kelley208b65c2006-08-05 21:41:37 +0100258
259 if (argc < 4 || argc > 5)
260 {
261 fprintf(stderr, "usage: dhcp_release <interface> <addr> <mac> [<client_id>]\n");
262 exit(1);
263 }
264
Simon Kelley16972692006-10-16 20:04:18 +0100265 if (fd == -1 || nl == -1)
Simon Kelley208b65c2006-08-05 21:41:37 +0100266 {
267 perror("cannot create socket");
268 exit(1);
269 }
270
271 /* This voodoo fakes up a packet coming from the correct interface, which really matters for
272 a DHCP server */
273 strcpy(ifr.ifr_name, argv[1]);
Simon Kelley16972692006-10-16 20:04:18 +0100274 if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
Simon Kelley208b65c2006-08-05 21:41:37 +0100275 {
276 perror("cannot setup interface");
277 exit(1);
278 }
279
Moshe Levia006eb72015-04-19 22:10:40 +0100280 if (inet_addr(argv[2]) == INADDR_NONE)
281 {
282 perror("invalid ip address");
283 exit(1);
284 }
Simon Kelley16972692006-10-16 20:04:18 +0100285
Simon Kelley208b65c2006-08-05 21:41:37 +0100286 lease.s_addr = inet_addr(argv[2]);
Simon Kelley16972692006-10-16 20:04:18 +0100287 server = find_interface(lease, nl, if_nametoindex(argv[1]));
Simon Kelley208b65c2006-08-05 21:41:37 +0100288
289 memset(&packet, 0, sizeof(packet));
290
291 packet.hlen = parse_hex(argv[3], packet.chaddr, DHCP_CHADDR_MAX, &mac_type);
292 if (mac_type == 0)
293 packet.htype = ARPHRD_ETHER;
294 else
295 packet.htype = mac_type;
296
297 packet.op = BOOTREQUEST;
298 packet.ciaddr = lease;
299 packet.cookie = htonl(DHCP_COOKIE);
300
301 *(p++) = OPTION_MESSAGE_TYPE;
302 *(p++) = 1;
303 *(p++) = DHCPRELEASE;
304
305 *(p++) = OPTION_SERVER_IDENTIFIER;
306 *(p++) = sizeof(server);
307 memcpy(p, &server, sizeof(server));
308 p += sizeof(server);
309
310 if (argc == 5 && strcmp(argv[4], "*") != 0)
311 {
312 unsigned int clid_len = parse_hex(argv[4], p+2, 255, NULL);
313 *(p++) = OPTION_CLIENT_ID;
314 *(p++) = clid_len;
315 p += clid_len;
316 }
317
318 *(p++) = OPTION_END;
319
320 dest.sin_family = AF_INET;
321 dest.sin_port = ntohs(DHCP_SERVER_PORT);
322 dest.sin_addr = server;
323
324 if (sendto(fd, &packet, sizeof(packet), 0,
Simon Kelley16972692006-10-16 20:04:18 +0100325 (struct sockaddr *)&dest, sizeof(dest)) == -1)
Simon Kelley208b65c2006-08-05 21:41:37 +0100326 {
327 perror("sendto failed");
328 exit(1);
329 }
330
331 return 0;
332}