Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / contrib / lease-tools / dhcp_release.c
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
62 typedef unsigned char u8;
63 typedef unsigned short u16;
64 typedef unsigned int u32;
65
66 struct 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
76 static struct iovec iov;
77
78 static 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
103 static 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 support 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
142 static 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
176 static 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
181 static struct in_addr find_interface(struct in_addr client, int fd, unsigned int index, int ifrfd, struct ifreq *ifr)
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           {
222             /* No match found, return first address as src/dhcp.c code does */
223             ifr->ifr_addr.sa_family = AF_INET;
224             if (ioctl(ifrfd, SIOCGIFADDR, ifr) != -1)
225               return ((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr;
226             else
227               {
228                 fprintf(stderr, "error: local IPv4 address not found\n");
229                 exit(1);
230               }
231           }
232         else if (h->nlmsg_type == RTM_NEWADDR)
233           {
234             struct ifaddrmsg *ifa = NLMSG_DATA(h);  
235             struct rtattr *rta;
236             unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa));
237             
238             if (ifa->ifa_index == index && ifa->ifa_family == AF_INET)
239               {
240                 struct in_addr netmask, addr;
241                 
242                 netmask.s_addr = htonl(0xffffffff << (32 - ifa->ifa_prefixlen));
243                 addr.s_addr = 0;
244                 
245                 for (rta = IFA_RTA(ifa); RTA_OK(rta, len1); rta = RTA_NEXT(rta, len1))
246                   if (rta->rta_type == IFA_LOCAL)
247                     addr = *((struct in_addr *)(rta+1));
248                 
249                 if (addr.s_addr && is_same_net(addr, client, netmask))
250                   return addr;
251               }
252           }
253     }
254  
255   exit(0);
256 }
257
258 int main(int argc, char **argv)
259
260   struct in_addr server, lease;
261   int mac_type;
262   struct dhcp_packet packet;
263   unsigned char *p = packet.options;
264   struct sockaddr_in dest;
265   struct ifreq ifr;
266   int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
267   int nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
268
269   if (argc < 4 || argc > 5)
270     { 
271       fprintf(stderr, "usage: dhcp_release <interface> <addr> <mac> [<client_id>]\n");
272       exit(1);
273     }
274
275   if (fd == -1 || nl == -1)
276     {
277       perror("cannot create socket");
278       exit(1);
279     }
280   
281   /* This voodoo fakes up a packet coming from the correct interface, which really matters for 
282      a DHCP server */
283   memset(&ifr, 0, sizeof(ifr));
284   strncpy(ifr.ifr_name, argv[1], sizeof(ifr.ifr_name)-1);
285   ifr.ifr_name[sizeof(ifr.ifr_name)-1] = '\0';
286   if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
287     {
288       perror("cannot setup interface");
289       exit(1);
290     }
291   
292   if (inet_pton(AF_INET, argv[2], &lease.s_addr) < 1)
293     {
294       perror("invalid ip address");
295       exit(1);
296     }
297   
298   server = find_interface(lease, nl, if_nametoindex(argv[1]), fd, &ifr);
299   
300   memset(&packet, 0, sizeof(packet));
301  
302   packet.hlen = parse_hex(argv[3], packet.chaddr, DHCP_CHADDR_MAX, &mac_type);
303   if (mac_type == 0)
304     packet.htype = ARPHRD_ETHER;
305   else
306     packet.htype = mac_type;
307
308   packet.op = BOOTREQUEST;
309   packet.ciaddr = lease;
310   packet.cookie = htonl(DHCP_COOKIE);
311
312   *(p++) = OPTION_MESSAGE_TYPE;
313   *(p++) = 1;
314   *(p++) = DHCPRELEASE;
315
316   *(p++) = OPTION_SERVER_IDENTIFIER;
317   *(p++) = sizeof(server);
318   memcpy(p, &server, sizeof(server));
319   p += sizeof(server);
320
321   if (argc == 5 && strcmp(argv[4], "*") != 0)
322     {
323       unsigned int clid_len = parse_hex(argv[4], p+2, 255, NULL);
324       *(p++) = OPTION_CLIENT_ID;
325       *(p++) = clid_len;
326       p += clid_len;
327     }
328   
329   *(p++) = OPTION_END;
330  
331   dest.sin_family = AF_INET;
332   dest.sin_port = ntohs(DHCP_SERVER_PORT);
333   dest.sin_addr = server;
334
335   if (sendto(fd, &packet, sizeof(packet), 0, 
336              (struct sockaddr *)&dest, sizeof(dest)) == -1)
337     {
338       perror("sendto failed");
339       exit(1);
340     }
341
342   return 0;
343 }