blob: 1eee873c6e93230b585c5f0bcafe89f66d598473 [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
23 IP is an IPv6 adress to release
24
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
49enum DHCP6_TYPES{
50 SOLICIT = 1,
51 ADVERTISE = 2,
52 REQUEST = 3,
53 CONFIRM = 4,
54 RENEW = 5,
55 REBIND = 6,
56 REPLY = 7,
57 RELEASE = 8,
58 DECLINE = 9,
59 RECONFIGURE = 10,
60 INFORMATION_REQUEST = 11,
61 RELAY_FORW = 12,
62 RELAY_REPL = 13
63
64};
65enum DHCP6_OPTIONS{
66 CLIENTID = 1,
67 SERVERID = 2,
68 IA_NA = 3,
69 IA_TA = 4,
70 IAADDR = 5,
71 ORO = 6,
72 PREFERENCE = 7,
73 ELAPSED_TIME = 8,
74 RELAY_MSG = 9,
75 AUTH = 11,
76 UNICAST = 12,
77 STATUS_CODE = 13,
78 RAPID_COMMIT = 14,
79 USER_CLASS = 15,
80 VENDOR_CLASS = 16,
81 VENDOR_OPTS = 17,
82 INTERFACE_ID = 18,
83 RECONF_MSG = 19,
84 RECONF_ACCEPT = 20,
85};
86
87enum DHCP6_STATUSES{
88 SUCCESS = 0,
89 UNSPEC_FAIL = 1,
90 NOADDR_AVAIL=2,
91 NO_BINDING = 3,
92 NOT_ON_LINK = 4,
93 USE_MULTICAST =5
94};
95static struct option longopts[] = {
96 {"ip", required_argument, 0, 'a'},
97 {"server-id", required_argument, 0, 's'},
98 {"client-id", required_argument, 0, 'c'},
99 {"iface", required_argument, 0, 'n'},
100 {"iaid", required_argument, 0, 'i'},
101 {"dry-run", no_argument, 0, 'd'},
102 {"help", no_argument, 0, 'h'},
103 {0, 0, 0, 0}
104};
105
106const short DHCP6_CLIENT_PORT = 546;
107const short DHCP6_SERVER_PORT = 547;
108
109const char* DHCP6_MULTICAST_ADDRESS = "ff02::1:2";
110
111struct dhcp6_option{
112 uint16_t type;
113 uint16_t len;
114 char value[1024];
115};
116
117struct dhcp6_iaaddr_option{
118 uint16_t type;
119 uint16_t len;
120 struct in6_addr ip;
121 uint32_t preferred_lifetime;
122 uint32_t valid_lifetime;
123
124
125};
126
127struct dhcp6_iana_option{
128 uint16_t type;
129 uint16_t len;
130 uint32_t iaid;
131 uint32_t t1;
132 uint32_t t2;
133 char options[1024];
134};
135
136
137struct dhcp6_packet{
138 size_t len;
139 char buf[2048];
140
141} ;
142
143size_t pack_duid(const char* str, char* dst){
144
145 char* tmp = strdup(str);
146 char* tmp_to_free = tmp;
147 char *ptr;
148 uint8_t write_pos = 0;
149 while ((ptr = strtok (tmp, ":"))) {
150 dst[write_pos] = (uint8_t) strtol(ptr, NULL, 16);
151 write_pos += 1;
152 tmp = NULL;
153
154 }
155 free(tmp_to_free);
156 return write_pos;
157}
158
159struct dhcp6_option create_client_id_option(const char* duid){
160 struct dhcp6_option option;
161 option.type = htons(CLIENTID);
162 bzero(option.value, sizeof(option.value));
163 option.len = htons(pack_duid(duid, option.value));
164 return option;
165}
166
167struct dhcp6_option create_server_id_option(const char* duid){
168 struct dhcp6_option option;
169 option.type = htons(SERVERID);
170 bzero(option.value, sizeof(option.value));
171 option.len = htons(pack_duid(duid, option.value));
172 return option;
173}
174
175struct dhcp6_iaaddr_option create_iaadr_option(const char* ip){
176 struct dhcp6_iaaddr_option result;
177 result.type =htons(IAADDR);
178 /* no suboptions needed here, so length is 24 */
179 result.len = htons(24);
180 result.preferred_lifetime = 0;
181 result.valid_lifetime = 0;
182 int s = inet_pton(AF_INET6, ip, &(result.ip));
183 if (s <= 0) {
184 if (s == 0)
185 fprintf(stderr, "Not in presentation format");
186 else
187 perror("inet_pton");
188 exit(EXIT_FAILURE);
189 }
190 return result;
191}
192struct dhcp6_iana_option create_iana_option(const char * iaid, struct dhcp6_iaaddr_option ia_addr){
193 struct dhcp6_iana_option result;
194 result.type = htons(IA_NA);
195 result.iaid = htonl(atoi(iaid));
196 result.t1 = 0;
197 result.t2 = 0;
198 result.len = htons(12 + ntohs(ia_addr.len) + 2 * sizeof(uint16_t));
199 memcpy(result.options, &ia_addr, ntohs(ia_addr.len) + 2 * sizeof(uint16_t));
200 return result;
201}
202
203struct dhcp6_packet create_release_packet(const char* iaid, const char* ip, const char* client_id, const char* server_id){
204 struct dhcp6_packet result;
205 bzero(result.buf, sizeof(result.buf));
206 /* message_type */
207 result.buf[0] = RELEASE;
208 /* tx_id */
209 bzero(result.buf+1, 3);
210
211 struct dhcp6_option client_option = create_client_id_option(client_id);
212 struct dhcp6_option server_option = create_server_id_option(server_id);
213 struct dhcp6_iaaddr_option iaaddr_option = create_iaadr_option(ip);
214 struct dhcp6_iana_option iana_option = create_iana_option(iaid, iaaddr_option);
215 int offset = 4;
216 memcpy(result.buf + offset, &client_option, ntohs(client_option.len) + 2*sizeof(uint16_t));
217 offset += (ntohs(client_option.len)+ 2 *sizeof(uint16_t) );
218 memcpy(result.buf + offset, &server_option, ntohs(server_option.len) + 2*sizeof(uint16_t) );
219 offset += (ntohs(server_option.len)+ 2* sizeof(uint16_t));
220 memcpy(result.buf + offset, &iana_option, ntohs(iana_option.len) + 2*sizeof(uint16_t) );
221 offset += (ntohs(iana_option.len)+ 2* sizeof(uint16_t));
222 result.len = offset;
223 return result;
224}
225
226uint16_t parse_iana_suboption(char* buf, size_t len){
227 size_t current_pos = 0;
228 char option_value[1024];
229 while (current_pos < len) {
230 uint16_t option_type, option_len;
231 memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
232 memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
233 option_type = ntohs(option_type);
234 option_len = ntohs(option_len);
235 current_pos += 2 * sizeof(uint16_t);
236 if (option_type == STATUS_CODE){
237 uint16_t status;
238 memcpy(&status, buf + current_pos, sizeof(uint16_t));
239 status = ntohs(status);
240 if (status != SUCCESS){
241 memcpy(option_value, buf + current_pos + sizeof(uint16_t) , option_len - sizeof(uint16_t));
242 option_value[option_len-sizeof(uint16_t)] ='\0';
243 fprintf(stderr, "Error: %s\n", option_value);
244 }
245 return status;
246 }
247 }
248 return -2;
249}
250
251int16_t parse_packet(char* buf, size_t len){
252 uint8_t type = buf[0];
253 /*skipping tx id. you need it, uncomment following line
254 uint16_t tx_id = ntohs((buf[1] <<16) + (buf[2] <<8) + buf[3]);
255 */
256 size_t current_pos = 4;
257 if (type != REPLY ){
258 return NOT_REPLY_CODE;
259 }
260 char option_value[1024];
261 while (current_pos < len) {
262 uint16_t option_type, option_len;
263 memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
264 memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
265 option_type = ntohs(option_type);
266 option_len = ntohs(option_len);
267 current_pos += 2 * sizeof(uint16_t);
268 if (option_type == STATUS_CODE){
269 uint16_t status;
270 memcpy(&status, buf + current_pos, sizeof(uint16_t));
271 status = ntohs(status);
272 if (status != SUCCESS){
273 memcpy(option_value, buf + current_pos +sizeof(uint16_t) , option_len -sizeof(uint16_t));
274 fprintf(stderr, "Error: %d %s\n", status, option_value);
275 return status;
276 }
277
278 }
279 if (option_type == IA_NA ){
280 uint16_t result = parse_iana_suboption(buf + current_pos +24, option_len -24);
281 if (result){
282 return result;
283 }
284 }
285 current_pos += option_len;
286
287 }
288 return -1;
289}
290
291void usage(const char* arg, FILE* stream){
292 const char* usage_string ="--ip IPv6 --iface IFACE --server-id SERVER_ID --client-id CLIENT_ID --iaid IAID [--dry-run] | --help";
293 fprintf (stream, "Usage: %s %s\n", arg, usage_string);
294
295}
296
297int send_release_packet(const char* iface, struct dhcp6_packet* packet){
298
299 struct sockaddr_in6 server_addr, client_addr;
300 char response[1400];
301 int sock = socket(PF_INET6, SOCK_DGRAM, 0);
302 int i = 0;
303 if (sock < 0) {
304 perror("creating socket");
305 return -1;
306 }
307 if (setsockopt(sock, SOL_SOCKET, 25, iface, strlen(iface)) == -1) {
308 perror("SO_BINDTODEVICE");
309 close(sock);
310 return -1;
311 }
312 memset(&server_addr, 0, sizeof(server_addr));
313 server_addr.sin6_family = AF_INET6;
314 client_addr.sin6_family = AF_INET6;
315 client_addr.sin6_port = htons(DHCP6_CLIENT_PORT);
316 client_addr.sin6_flowinfo = 0;
317 client_addr.sin6_scope_id =0;
318 inet_pton(AF_INET6, "::", &client_addr.sin6_addr);
319 bind(sock, (struct sockaddr*)&client_addr, sizeof(struct sockaddr_in6));
320 inet_pton(AF_INET6, DHCP6_MULTICAST_ADDRESS, &server_addr.sin6_addr);
321 server_addr.sin6_port = htons(DHCP6_SERVER_PORT);
322 int16_t recv_size = 0;
323 for (i = 0; i < 5; i++) {
324 if (sendto(sock, packet->buf, packet->len, 0,
325 (struct sockaddr *)&server_addr,
326 sizeof(server_addr)) < 0) {
327 perror("sendto failed");
328 exit(4);
329 }
330 recv_size = recvfrom(sock, response, sizeof(response), MSG_DONTWAIT, NULL, 0);
331 if (recv_size == -1){
332 if (errno == EAGAIN){
333 sleep(1);
334 continue;
335 }else {
336 perror("recvfrom");
337 }
338 }
339 int16_t result = parse_packet(response, recv_size);
340 if (result == NOT_REPLY_CODE){
341 sleep(1);
342 continue;
343 }
344 return result;
345 }
346 fprintf(stderr, "Response timed out\n");
347 return -1;
348
349}
350
351
352int main(int argc, char * const argv[]) {
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100353 const char* UNINITIALIZED = "";
354 const char* iface = UNINITIALIZED;
355 const char* ip = UNINITIALIZED;
356 const char* client_id = UNINITIALIZED;
357 const char* server_id = UNINITIALIZED;
358 const char* iaid = UNINITIALIZED;
Simon Kelley69cbf782016-05-03 21:33:38 +0100359 int dry_run = 0;
360 while (1) {
361 int option_index = 0;
362 int c = getopt_long(argc, argv, "a:s:c:n:i:hd", longopts, &option_index);
363 if (c == -1){
364 break;
365 }
366 switch(c){
367 case 0:
368 if (longopts[option_index].flag !=0){
369 break;
370 }
371 printf ("option %s", longopts[option_index].name);
372 if (optarg)
373 printf (" with arg %s", optarg);
374 printf ("\n");
375 break;
376 case 'i':
377 iaid = optarg;
378 break;
379 case 'n':
380 iface = optarg;
381 break;
382 case 'a':
383 ip = optarg;
384 break;
385 case 'c':
386 client_id = optarg;
387 break;
388 case 'd':
389 dry_run = 1;
390 break;
391 case 's':
392 server_id = optarg;
393 break;
394 case 'h':
395 usage(argv[0], stdout);
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100396 return 0;
Simon Kelley69cbf782016-05-03 21:33:38 +0100397 case '?':
398 usage(argv[0], stderr);
399 return -1;
400 default:
401 abort();
402
403 }
404
405 }
Sergey Nechaev45cb8dd2016-05-14 21:36:15 +0100406 if (iaid == UNINITIALIZED){
407 fprintf(stderr, "Missing required iaid parameter\n");
408 usage(argv[0], stderr);
409 return -1;
410 }
411 if (server_id == UNINITIALIZED){
412 fprintf(stderr, "Missing required server-id parameter\n");
413 usage(argv[0], stderr);
414 return -1;
415 }
416 if (client_id == UNINITIALIZED){
417 fprintf(stderr, "Missing required client-id parameter\n");
418 usage(argv[0], stderr);
419 return -1;
420 }
421 if (ip == UNINITIALIZED){
422 fprintf(stderr, "Missing required ip parameter\n");
423 usage(argv[0], stderr);
424 return -1;
425 }
426 if (iface == UNINITIALIZED){
427 fprintf(stderr, "Missing required iface parameter\n");
428 usage(argv[0], stderr);
429 return -1;
430 }
431
432
433
Simon Kelley69cbf782016-05-03 21:33:38 +0100434 struct dhcp6_packet packet = create_release_packet(iaid, ip, client_id, server_id);
435 if (dry_run){
436 uint16_t i;
437 for(i=0;i<packet.len;i++){
438 printf("%hhx", packet.buf[i]);
439 }
440 printf("\n");
441 return 0;
442 }
443 return send_release_packet(iface, &packet);
444
445}