Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / contrib / lease-tools / dhcp_release6.c
1 /*
2  dhcp_release6 --iface <interface> --client-id <client-id> --server-id
3  server-id --iaid <iaid>  --ip <IP>  [--dry-run] [--help]
4  MUST be run as root - will fail otherwise
5  */
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 address to release
24  
25  If --dry-run is specified, dhcp_release6 just prints hexadecimal representation of
26  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
45 typedef unsigned char u8;
46 typedef unsigned short u16;
47 typedef unsigned int u32;
48
49 enum DHCP6_TYPES
50   {
51     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     
65   };
66
67 enum DHCP6_OPTIONS
68   {
69     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,
88   };
89
90 enum DHCP6_STATUSES
91   {
92     SUCCESS = 0,
93     UNSPEC_FAIL = 1,
94     NOADDR_AVAIL=2,
95     NO_BINDING  = 3,
96     NOT_ON_LINK = 4,
97     USE_MULTICAST =5
98   };
99
100 static struct option longopts[] = {
101   {"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 }
109 };
110
111 const short DHCP6_CLIENT_PORT = 546;
112 const short DHCP6_SERVER_PORT = 547;
113
114 const char*  DHCP6_MULTICAST_ADDRESS = "ff02::1:2";
115
116 struct dhcp6_option {
117   uint16_t type;
118   uint16_t len;
119   char  value[1024];
120 };
121
122 struct 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;
128 };
129
130 struct 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];
137 };
138
139
140 struct dhcp6_packet {
141   size_t len;
142   char buf[2048];  
143 };
144
145 size_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;
156     }
157   
158   free(tmp_to_free);
159   return write_pos;
160 }
161
162 struct 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;
169 }
170
171 struct 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;
178 }
179
180 struct 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;
198 }
199
200 struct 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;
210 }
211
212 struct 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
236 uint16_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);
258             }
259           return status;
260         }
261     }
262
263   return -2;
264 }
265
266 int16_t parse_packet(char* buf, size_t len)
267 {
268   int16_t ret = -1;
269   uint8_t type = buf[0];
270   /*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             }
297
298           /* Got success status, return that if there's no specific error in an IA_NA. */
299           ret = SUCCESS;   
300         }
301
302       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;
310     }
311
312   return ret;
313 }
314
315 void 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);   
319 }
320
321 static void fail_fatal(const char *errstr, int exitcode)
322 {
323   perror(errstr);
324   exit(exitcode);
325 }
326
327 int send_release_packet(const char* iface, struct dhcp6_packet* packet)
328 {
329   struct sockaddr_in6 server_addr, client_addr;
330   char response[1400];
331   int sock = socket(PF_INET6, SOCK_DGRAM, 0);
332   int i = 0;
333   if (sock < 0)
334     {
335       perror("creating socket");
336       return -1;
337     }
338   
339     if (setsockopt(sock, SOL_SOCKET, 25, iface, strlen(iface)) == -1)
340       {
341         perror("SO_BINDTODEVICE");
342         close(sock);
343         return -1;
344       }
345     
346     memset(&server_addr, 0, sizeof(server_addr));
347     server_addr.sin6_family = AF_INET6;
348     client_addr.sin6_family = AF_INET6;
349     client_addr.sin6_port = htons(DHCP6_CLIENT_PORT);
350     client_addr.sin6_flowinfo = 0;
351     client_addr.sin6_scope_id =0;
352     if (inet_pton(AF_INET6, "::", &client_addr.sin6_addr) <= 0)
353       fail_fatal("inet_pton", 5);
354     if (bind(sock, (struct sockaddr*)&client_addr, sizeof(struct sockaddr_in6)) != 0)
355       perror("bind"); /* continue on bind error */
356     if (inet_pton(AF_INET6, DHCP6_MULTICAST_ADDRESS, &server_addr.sin6_addr) <= 0)
357       fail_fatal("inet_pton", 5);
358     server_addr.sin6_port = htons(DHCP6_SERVER_PORT);
359     ssize_t recv_size = 0;
360     int result;
361     for (i = 0; i < 5; i++)
362       {
363         if (sendto(sock, packet->buf, packet->len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
364           fail_fatal("sendto failed", 4);
365         
366         recv_size = recvfrom(sock, response, sizeof(response), MSG_DONTWAIT, NULL, 0);
367         if (recv_size == -1)
368           {
369             if (errno == EAGAIN)
370               {
371                 sleep(1);
372                 continue;
373               }
374             else
375               {
376                 perror("recvfrom");
377                 result = UNSPEC_FAIL;
378               }
379           }
380         else
381           {
382             result = parse_packet(response, recv_size);
383             if (result == NOT_REPLY_CODE)
384               {
385                 sleep(1);
386                 continue;
387               }
388           }
389         close(sock);
390         return result;
391       }
392
393     close(sock);
394     fprintf(stderr, "Response timed out\n");
395     return -1;   
396 }
397
398
399 int main(int argc, char *  const argv[])
400 {
401   const char* UNINITIALIZED = "";
402   const char* iface = UNINITIALIZED;
403   const char* ip = UNINITIALIZED;
404   const char* client_id = UNINITIALIZED;
405   const char* server_id = UNINITIALIZED;
406   const char* iaid = UNINITIALIZED;
407   int dry_run = 0;
408   while (1)
409     {
410       int option_index = 0;
411       int c = getopt_long(argc, argv, "a:s:c:n:i:hd", longopts, &option_index);
412       if (c == -1)
413         break;
414         
415       switch(c)
416         {
417         case 0:
418           if (longopts[option_index].flag !=0)
419             break;
420           
421           printf ("option %s", longopts[option_index].name);
422           if (optarg)
423             printf (" with arg %s", optarg);
424           printf ("\n");
425           break;
426
427         case 'i':
428           iaid = optarg;
429           break;
430         case 'n':
431           iface = optarg;
432           break;
433         case 'a':
434           ip = optarg;
435           break;
436         case 'c':
437           client_id = optarg;
438           break;
439         case 'd':
440           dry_run = 1;
441           break;
442         case 's':
443           server_id = optarg;
444           break;
445         case 'h':
446           usage(argv[0], stdout);
447           return 0;
448         case '?':
449           usage(argv[0], stderr);
450           return -1;
451         default:
452           abort();
453           
454         }
455     }
456   
457   if (iaid == UNINITIALIZED)
458     {
459       fprintf(stderr, "Missing required iaid parameter\n");
460       usage(argv[0], stderr);
461       return -1;
462     }
463   
464     if (server_id == UNINITIALIZED)
465       {
466         fprintf(stderr, "Missing required server-id parameter\n");
467         usage(argv[0], stderr);
468         return -1;
469       }
470     
471     if (client_id == UNINITIALIZED)
472       {
473         fprintf(stderr, "Missing required client-id parameter\n");
474         usage(argv[0], stderr);
475         return -1;
476       }
477     
478     if (ip == UNINITIALIZED)
479       {
480         fprintf(stderr, "Missing required ip parameter\n");
481         usage(argv[0], stderr);
482         return -1;
483       }
484     
485     if (iface == UNINITIALIZED)
486       {
487         fprintf(stderr, "Missing required iface parameter\n");
488         usage(argv[0], stderr);
489         return -1;
490       }
491
492     
493     
494     struct dhcp6_packet packet = create_release_packet(iaid, ip, client_id, server_id);
495
496     if (dry_run)
497       {
498         uint16_t i;
499
500         for(i=0; i<packet.len; i++)
501           printf("%hhx", packet.buf[i]);
502         
503         printf("\n");
504         return 0;
505       }
506
507     return send_release_packet(iface, &packet);
508 }