Home | History | Annotate | Download | only in wrt
      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 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 
    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, 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 
    248 int 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