blob: d6802226d5385608cc266d2d0f7a9ac315852d14 [file] [log] [blame]
Simon Kelley69cbf782016-05-03 21:33:38 +01001/*
2 dhcp_release6 --iface <interface> --client-id <client-id> --server-id
3 server-id --iaid <iaid> --ip <IP> [--dry-run] [--help]
Josh Soref730c6742017-02-06 16:14:04 +00004 MUST be run as root - will fail otherwise
Simon Kelley69cbf782016-05-03 21:33:38 +01005 */
6
7/* Send a DHCPRELEASE message to IPv6 multicast address via the specified interface
8 to tell the local DHCP server to delete a particular lease.
9
10 The interface argument is the interface in which a DHCP
11 request _would_ be received if it was coming from the client,
12 rather than being faked up here.
13
14 The client-id argument is colon-separated hex string and mandatory. Normally
15 it can be found in leases file both on client and server
16
17 The server-id argument is colon-separated hex string and mandatory. Normally
18 it can be found in leases file both on client and server.
19
20 The iaid argument is numeric string and mandatory. Normally
21 it can be found in leases file both on client and server.
22
klemens43517fc2017-02-19 15:53:37 +000023 IP is an IPv6 address to release
Simon Kelley69cbf782016-05-03 21:33:38 +010024
Josh Soref730c6742017-02-06 16:14:04 +000025 If --dry-run is specified, dhcp_release6 just prints hexadecimal representation of
Simon Kelley69cbf782016-05-03 21:33:38 +010026 packet to send to stdout and exits.
27
28 If --help is specified, dhcp_release6 print usage information to stdout and exits
29
30
31
32 */
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <strings.h>
37#include <sys/types.h>
38#include <sys/socket.h>
39#include <arpa/inet.h>
40#include <getopt.h>
41#include <errno.h>
42#include <unistd.h>
43
44#define NOT_REPLY_CODE 115
45typedef unsigned char u8;
46typedef unsigned short u16;
47typedef unsigned int u32;
48
Simon Kelley00399202017-12-14 22:29:31 +000049enum DHCP6_TYPES
50 {
Simon Kelley69cbf782016-05-03 21:33:38 +010051 SOLICIT = 1,
52 ADVERTISE = 2,
53 REQUEST = 3,
54 CONFIRM = 4,
55 RENEW = 5,
56 REBIND = 6,
57 REPLY = 7,
58 RELEASE = 8,
59 DECLINE = 9,
60 RECONFIGURE = 10,
61 INFORMATION_REQUEST = 11,
62 RELAY_FORW = 12,
63 RELAY_REPL = 13
64
Simon Kelley00399202017-12-14 22:29:31 +000065 };
66
67enum DHCP6_OPTIONS
68 {
Simon Kelley69cbf782016-05-03 21:33:38 +010069 CLIENTID = 1,
70 SERVERID = 2,
71 IA_NA = 3,
72 IA_TA = 4,
73 IAADDR = 5,
74 ORO = 6,
75 PREFERENCE = 7,
76 ELAPSED_TIME = 8,
77 RELAY_MSG = 9,
78 AUTH = 11,
79 UNICAST = 12,
80 STATUS_CODE = 13,
81 RAPID_COMMIT = 14,
82 USER_CLASS = 15,
83 VENDOR_CLASS = 16,
84 VENDOR_OPTS = 17,
85 INTERFACE_ID = 18,
86 RECONF_MSG = 19,
87 RECONF_ACCEPT = 20,
Simon Kelley00399202017-12-14 22:29:31 +000088 };
Simon Kelley69cbf782016-05-03 21:33:38 +010089
Simon Kelley00399202017-12-14 22:29:31 +000090enum DHCP6_STATUSES
91 {
Simon Kelley69cbf782016-05-03 21:33:38 +010092 SUCCESS = 0,
93 UNSPEC_FAIL = 1,
94 NOADDR_AVAIL=2,
95 NO_BINDING = 3,
96 NOT_ON_LINK = 4,
97 USE_MULTICAST =5
Simon Kelley00399202017-12-14 22:29:31 +000098 };
99
Simon Kelley69cbf782016-05-03 21:33:38 +0100100static struct option longopts[] = {
Simon Kelley00399202017-12-14 22:29:31 +0000101 {"ip", required_argument, 0, 'a' },
102 {"server-id", required_argument, 0, 's' },
103 {"client-id", required_argument, 0, 'c' },
104 {"iface", required_argument, 0, 'n' },
105 {"iaid", required_argument, 0, 'i' },
106 {"dry-run", no_argument, 0, 'd' },
107 {"help", no_argument, 0, 'h' },
108 {0, 0, 0, 0 }
Simon Kelley69cbf782016-05-03 21:33:38 +0100109};
110
111const short DHCP6_CLIENT_PORT = 546;
112const short DHCP6_SERVER_PORT = 547;
113
114const char* DHCP6_MULTICAST_ADDRESS = "ff02::1:2";
115
Simon Kelley00399202017-12-14 22:29:31 +0000116struct dhcp6_option {
117 uint16_t type;
118 uint16_t len;
119 char value[1024];
Simon Kelley69cbf782016-05-03 21:33:38 +0100120};
121
Simon Kelley00399202017-12-14 22:29:31 +0000122struct dhcp6_iaaddr_option {
123 uint16_t type;
124 uint16_t len;
125 struct in6_addr ip;
126 uint32_t preferred_lifetime;
127 uint32_t valid_lifetime;
Simon Kelley69cbf782016-05-03 21:33:38 +0100128};
129
Simon Kelley00399202017-12-14 22:29:31 +0000130struct dhcp6_iana_option {
131 uint16_t type;
132 uint16_t len;
133 uint32_t iaid;
134 uint32_t t1;
135 uint32_t t2;
136 char options[1024];
Simon Kelley69cbf782016-05-03 21:33:38 +0100137};
138
139
Simon Kelley00399202017-12-14 22:29:31 +0000140struct dhcp6_packet {
141 size_t len;
142 char buf[2048];
143};
Simon Kelley69cbf782016-05-03 21:33:38 +0100144
Simon Kelley00399202017-12-14 22:29:31 +0000145size_t pack_duid(const char* str, char* dst)
146{
147 char* tmp = strdup(str);
148 char* tmp_to_free = tmp;
149 char *ptr;
150 uint8_t write_pos = 0;
151 while ((ptr = strtok (tmp, ":")))
152 {
153 dst[write_pos] = (uint8_t) strtol(ptr, NULL, 16);
154 write_pos += 1;
155 tmp = NULL;
Simon Kelley69cbf782016-05-03 21:33:38 +0100156 }
Simon Kelley00399202017-12-14 22:29:31 +0000157
158 free(tmp_to_free);
159 return write_pos;
Simon Kelley69cbf782016-05-03 21:33:38 +0100160}
161
Simon Kelley00399202017-12-14 22:29:31 +0000162struct dhcp6_option create_client_id_option(const char* duid)
163{
164 struct dhcp6_option option;
165 option.type = htons(CLIENTID);
166 bzero(option.value, sizeof(option.value));
167 option.len = htons(pack_duid(duid, option.value));
168 return option;
Simon Kelley69cbf782016-05-03 21:33:38 +0100169}
170
Simon Kelley00399202017-12-14 22:29:31 +0000171struct dhcp6_option create_server_id_option(const char* duid)
172{
173 struct dhcp6_option option;
174 option.type = htons(SERVERID);
175 bzero(option.value, sizeof(option.value));
176 option.len = htons(pack_duid(duid, option.value));
177 return option;
Simon Kelley69cbf782016-05-03 21:33:38 +0100178}
179
Simon Kelley00399202017-12-14 22:29:31 +0000180struct dhcp6_iaaddr_option create_iaadr_option(const char* ip)
181{
182 struct dhcp6_iaaddr_option result;
183 result.type =htons(IAADDR);
184 /* no suboptions needed here, so length is 24 */
185 result.len = htons(24);
186 result.preferred_lifetime = 0;
187 result.valid_lifetime = 0;
188 int s = inet_pton(AF_INET6, ip, &(result.ip));
189 if (s <= 0) {
190 if (s == 0)
191 fprintf(stderr, "Not in presentation format");
192 else
193 perror("inet_pton");
194 exit(EXIT_FAILURE);
195 }
196
197 return result;
Simon Kelley69cbf782016-05-03 21:33:38 +0100198}
199
Simon Kelley00399202017-12-14 22:29:31 +0000200struct dhcp6_iana_option create_iana_option(const char * iaid, struct dhcp6_iaaddr_option ia_addr)
201{
202 struct dhcp6_iana_option result;
203 result.type = htons(IA_NA);
204 result.iaid = htonl(atoi(iaid));
205 result.t1 = 0;
206 result.t2 = 0;
207 result.len = htons(12 + ntohs(ia_addr.len) + 2 * sizeof(uint16_t));
208 memcpy(result.options, &ia_addr, ntohs(ia_addr.len) + 2 * sizeof(uint16_t));
209 return result;
Simon Kelley69cbf782016-05-03 21:33:38 +0100210}
211
Simon Kelley00399202017-12-14 22:29:31 +0000212struct dhcp6_packet create_release_packet(const char* iaid, const char* ip, const char* client_id, const char* server_id)
213{
214 struct dhcp6_packet result;
215 bzero(result.buf, sizeof(result.buf));
216 /* message_type */
217 result.buf[0] = RELEASE;
218 /* tx_id */
219 bzero(result.buf+1, 3);
220
221 struct dhcp6_option client_option = create_client_id_option(client_id);
222 struct dhcp6_option server_option = create_server_id_option(server_id);
223 struct dhcp6_iaaddr_option iaaddr_option = create_iaadr_option(ip);
224 struct dhcp6_iana_option iana_option = create_iana_option(iaid, iaaddr_option);
225 int offset = 4;
226 memcpy(result.buf + offset, &client_option, ntohs(client_option.len) + 2*sizeof(uint16_t));
227 offset += (ntohs(client_option.len)+ 2 *sizeof(uint16_t) );
228 memcpy(result.buf + offset, &server_option, ntohs(server_option.len) + 2*sizeof(uint16_t) );
229 offset += (ntohs(server_option.len)+ 2* sizeof(uint16_t));
230 memcpy(result.buf + offset, &iana_option, ntohs(iana_option.len) + 2*sizeof(uint16_t) );
231 offset += (ntohs(iana_option.len)+ 2* sizeof(uint16_t));
232 result.len = offset;
233 return result;
234}
235
236uint16_t parse_iana_suboption(char* buf, size_t len)
237{
238 size_t current_pos = 0;
239 char option_value[1024];
240 while (current_pos < len)
241 {
242 uint16_t option_type, option_len;
243 memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
244 memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
245 option_type = ntohs(option_type);
246 option_len = ntohs(option_len);
247 current_pos += 2 * sizeof(uint16_t);
248 if (option_type == STATUS_CODE)
249 {
250 uint16_t status;
251 memcpy(&status, buf + current_pos, sizeof(uint16_t));
252 status = ntohs(status);
253 if (status != SUCCESS)
254 {
255 memcpy(option_value, buf + current_pos + sizeof(uint16_t) , option_len - sizeof(uint16_t));
256 option_value[option_len-sizeof(uint16_t)] ='\0';
257 fprintf(stderr, "Error: %s\n", option_value);
Simon Kelley69cbf782016-05-03 21:33:38 +0100258 }
Simon Kelley00399202017-12-14 22:29:31 +0000259 return status;
Simon Kelley69cbf782016-05-03 21:33:38 +0100260 }
261 }
Simon Kelley00399202017-12-14 22:29:31 +0000262
263 return -2;
Simon Kelley69cbf782016-05-03 21:33:38 +0100264}
265
Simon Kelley00399202017-12-14 22:29:31 +0000266int16_t parse_packet(char* buf, size_t len)
267{
Simon Kelleya6cee692017-12-14 22:40:48 +0000268 int16_t ret = -1;
269 uint8_t type = buf[0];
Simon Kelley00399202017-12-14 22:29:31 +0000270 /*skipping tx id. you need it, uncomment following line
271 uint16_t tx_id = ntohs((buf[1] <<16) + (buf[2] <<8) + buf[3]);
272 */
273 size_t current_pos = 4;
274 if (type != REPLY )
275 return NOT_REPLY_CODE;
276
277 char option_value[1024];
278 while (current_pos < len)
279 {
280 uint16_t option_type, option_len;
281 memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
282 memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
283 option_type = ntohs(option_type);
284 option_len = ntohs(option_len);
285 current_pos += 2 * sizeof(uint16_t);
286 if (option_type == STATUS_CODE)
287 {
288 uint16_t status;
289 memcpy(&status, buf + current_pos, sizeof(uint16_t));
290 status = ntohs(status);
291 if (status != SUCCESS)
292 {
293 memcpy(option_value, buf + current_pos +sizeof(uint16_t) , option_len -sizeof(uint16_t));
294 fprintf(stderr, "Error: %d %s\n", status, option_value);
295 return status;
296 }
Simon Kelleya6cee692017-12-14 22:40:48 +0000297
298 /* Got success status, return that if there's no specific error in an IA_NA. */
299 ret = SUCCESS;
Simon Kelley69cbf782016-05-03 21:33:38 +0100300 }
Simon Kelley69cbf782016-05-03 21:33:38 +0100301
Simon Kelley00399202017-12-14 22:29:31 +0000302 if (option_type == IA_NA )
303 {
304 uint16_t result = parse_iana_suboption(buf + current_pos +24, option_len -24);
305 if (result)
306 return result;
307 }
308
309 current_pos += option_len;
Simon Kelley69cbf782016-05-03 21:33:38 +0100310 }
Simon Kelley00399202017-12-14 22:29:31 +0000311
Simon Kelleya6cee692017-12-14 22:40:48 +0000312 return ret;
Simon Kelley69cbf782016-05-03 21:33:38 +0100313}
314
Simon Kelley00399202017-12-14 22:29:31 +0000315void usage(const char* arg, FILE* stream)
316{
317 const char* usage_string ="--ip IPv6 --iface IFACE --server-id SERVER_ID --client-id CLIENT_ID --iaid IAID [--dry-run] | --help";
318 fprintf (stream, "Usage: %s %s\n", arg, usage_string);
Simon Kelley69cbf782016-05-03 21:33:38 +0100319}
320
Simon Kelley00399202017-12-14 22:29:31 +0000321int send_release_packet(const char* iface, struct dhcp6_packet* packet)
322{
323 struct sockaddr_in6 server_addr, client_addr;
324 char response[1400];
325 int sock = socket(PF_INET6, SOCK_DGRAM, 0);
326 int i = 0;
327 if (sock < 0)
328 {
329 perror("creating socket");
330 return -1;
Simon Kelley69cbf782016-05-03 21:33:38 +0100331 }
Simon Kelley00399202017-12-14 22:29:31 +0000332
333 if (setsockopt(sock, SOL_SOCKET, 25, iface, strlen(iface)) == -1)
334 {
Simon Kelley69cbf782016-05-03 21:33:38 +0100335 perror("SO_BINDTODEVICE");
336 close(sock);
337 return -1;
Simon Kelley00399202017-12-14 22:29:31 +0000338 }
339
Simon Kelley69cbf782016-05-03 21:33:38 +0100340 memset(&server_addr, 0, sizeof(server_addr));
341 server_addr.sin6_family = AF_INET6;
342 client_addr.sin6_family = AF_INET6;
343 client_addr.sin6_port = htons(DHCP6_CLIENT_PORT);
344 client_addr.sin6_flowinfo = 0;
345 client_addr.sin6_scope_id =0;
346 inet_pton(AF_INET6, "::", &client_addr.sin6_addr);
347 bind(sock, (struct sockaddr*)&client_addr, sizeof(struct sockaddr_in6));
348 inet_pton(AF_INET6, DHCP6_MULTICAST_ADDRESS, &server_addr.sin6_addr);
349 server_addr.sin6_port = htons(DHCP6_SERVER_PORT);
350 int16_t recv_size = 0;
Simon Kelley00399202017-12-14 22:29:31 +0000351 for (i = 0; i < 5; i++)
352 {
353 if (sendto(sock, packet->buf, packet->len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
354 {
355 perror("sendto failed");
Simon Kelley69cbf782016-05-03 21:33:38 +0100356 exit(4);
Simon Kelley00399202017-12-14 22:29:31 +0000357 }
358
Simon Kelley69cbf782016-05-03 21:33:38 +0100359 recv_size = recvfrom(sock, response, sizeof(response), MSG_DONTWAIT, NULL, 0);
Simon Kelley00399202017-12-14 22:29:31 +0000360 if (recv_size == -1)
361 {
362 if (errno == EAGAIN)
363 {
364 sleep(1);
365 continue;
366 }
367 else
368 {
Simon Kelley69cbf782016-05-03 21:33:38 +0100369 perror("recvfrom");
Simon Kelley00399202017-12-14 22:29:31 +0000370 }
371 }
372
Simon Kelley69cbf782016-05-03 21:33:38 +0100373 int16_t result = parse_packet(response, recv_size);
Simon Kelley00399202017-12-14 22:29:31 +0000374 if (result == NOT_REPLY_CODE)
375 {
Simon Kelley69cbf782016-05-03 21:33:38 +0100376 sleep(1);
377 continue;
Simon Kelley00399202017-12-14 22:29:31 +0000378 }
Petr Menšík2b38e382018-08-17 10:20:05 +0200379
380 close(sock);
Simon Kelley69cbf782016-05-03 21:33:38 +0100381 return result;
Simon Kelley00399202017-12-14 22:29:31 +0000382 }
Petr Menšík2b38e382018-08-17 10:20:05 +0200383
384 close(sock);
Simon Kelley00399202017-12-14 22:29:31 +0000385 fprintf(stderr, "Response timed out\n");
386 return -1;
Simon Kelley69cbf782016-05-03 21:33:38 +0100387}
388
389
Simon Kelley00399202017-12-14 22:29:31 +0000390int main(int argc, char * const argv[])
391{
392 const char* UNINITIALIZED = "";
393 const char* iface = UNINITIALIZED;
394 const char* ip = UNINITIALIZED;
395 const char* client_id = UNINITIALIZED;
396 const char* server_id = UNINITIALIZED;
397 const char* iaid = UNINITIALIZED;
398 int dry_run = 0;
399 while (1)
400 {
401 int option_index = 0;
402 int c = getopt_long(argc, argv, "a:s:c:n:i:hd", longopts, &option_index);
403 if (c == -1)
404 break;
Simon Kelley69cbf782016-05-03 21:33:38 +0100405
Simon Kelley00399202017-12-14 22:29:31 +0000406 switch(c)
407 {
408 case 0:
409 if (longopts[option_index].flag !=0)
410 break;
411
412 printf ("option %s", longopts[option_index].name);
413 if (optarg)
414 printf (" with arg %s", optarg);
415 printf ("\n");
416 break;
417
418 case 'i':
419 iaid = optarg;
420 break;
421 case 'n':
422 iface = optarg;
423 break;
424 case 'a':
425 ip = optarg;
426 break;
427 case 'c':
428 client_id = optarg;
429 break;
430 case 'd':
431 dry_run = 1;
432 break;
433 case 's':
434 server_id = optarg;
435 break;
436 case 'h':
437 usage(argv[0], stdout);
438 return 0;
439 case '?':
440 usage(argv[0], stderr);
441 return -1;
442 default:
443 abort();
444
445 }
Simon Kelley69cbf782016-05-03 21:33:38 +0100446 }
Simon Kelley00399202017-12-14 22:29:31 +0000447
448 if (iaid == UNINITIALIZED)
449 {
450 fprintf(stderr, "Missing required iaid parameter\n");
451 usage(argv[0], stderr);
452 return -1;
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100453 }
Simon Kelley00399202017-12-14 22:29:31 +0000454
455 if (server_id == UNINITIALIZED)
456 {
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100457 fprintf(stderr, "Missing required server-id parameter\n");
458 usage(argv[0], stderr);
459 return -1;
Simon Kelley00399202017-12-14 22:29:31 +0000460 }
461
462 if (client_id == UNINITIALIZED)
463 {
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100464 fprintf(stderr, "Missing required client-id parameter\n");
465 usage(argv[0], stderr);
466 return -1;
Simon Kelley00399202017-12-14 22:29:31 +0000467 }
468
469 if (ip == UNINITIALIZED)
470 {
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100471 fprintf(stderr, "Missing required ip parameter\n");
472 usage(argv[0], stderr);
473 return -1;
Simon Kelley00399202017-12-14 22:29:31 +0000474 }
475
476 if (iface == UNINITIALIZED)
477 {
478 fprintf(stderr, "Missing required iface parameter\n");
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100479 usage(argv[0], stderr);
480 return -1;
Simon Kelley00399202017-12-14 22:29:31 +0000481 }
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100482
Simon Kelley00399202017-12-14 22:29:31 +0000483
484
Simon Kelley69cbf782016-05-03 21:33:38 +0100485 struct dhcp6_packet packet = create_release_packet(iaid, ip, client_id, server_id);
Simon Kelley00399202017-12-14 22:29:31 +0000486
487 if (dry_run)
488 {
Simon Kelley69cbf782016-05-03 21:33:38 +0100489 uint16_t i;
Simon Kelley00399202017-12-14 22:29:31 +0000490
491 for(i=0; i<packet.len; i++)
492 printf("%hhx", packet.buf[i]);
493
Simon Kelley69cbf782016-05-03 21:33:38 +0100494 printf("\n");
495 return 0;
Simon Kelley00399202017-12-14 22:29:31 +0000496 }
497
Simon Kelley69cbf782016-05-03 21:33:38 +0100498 return send_release_packet(iface, &packet);
Simon Kelley69cbf782016-05-03 21:33:38 +0100499}