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