summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2016-05-03 21:33:38 +0100
committerSimon Kelley <simon@thekelleys.org.uk>2016-05-03 21:33:38 +0100
commit69cbf78bb676e493f0a4cd6dc7ffec0fcafafed5 (patch)
tree83c8de9742b1d28119564e875642f76429c33f2f /contrib
parentc6cdf6bbee27d1127847ce7bdff2a22a785f9723 (diff)
downloaddnsmasq-69cbf78bb676e493f0a4cd6dc7ffec0fcafafed5.tar.gz
Add contrib/lease-tools/dhcp_release6
Diffstat (limited to 'contrib')
-rw-r--r--contrib/lease-tools/Makefile4
-rw-r--r--contrib/lease-tools/dhcp_release6.138
-rw-r--r--contrib/lease-tools/dhcp_release6.c416
3 files changed, 456 insertions, 2 deletions
diff --git a/contrib/lease-tools/Makefile b/contrib/lease-tools/Makefile
index 68e8d32..f38f2ed 100644
--- a/contrib/lease-tools/Makefile
+++ b/contrib/lease-tools/Makefile
@@ -1,6 +1,6 @@
CFLAGS?= -O2 -Wall -W
-all: dhcp_release dhcp_lease_time
+all: dhcp_release dhcp_release6 dhcp_lease_time
clean:
- rm -f *~ *.o core dhcp_release dhcp_lease_time
+ rm -f *~ *.o core dhcp_release dhcp_release6 dhcp_lease_time
diff --git a/contrib/lease-tools/dhcp_release6.1 b/contrib/lease-tools/dhcp_release6.1
new file mode 100644
index 0000000..763e01c
--- /dev/null
+++ b/contrib/lease-tools/dhcp_release6.1
@@ -0,0 +1,38 @@
+.TH DHCP_RELEASE 1
+.SH NAME
+dhcp_release6 \- Release a DHCPv6 lease on a the local dnsmasq DHCP server.
+.SH SYNOPSIS
+.B dhcp_release6 --iface <interface> --client-id <client-id> --server-id
+server-id --iaid <iaid> --ip <IP> [--dry-run] [--help]
+.SH "DESCRIPTION"
+A utility which forces the DHCP server running on this machine to release a
+DHCPv6 lease.
+.SS OPTIONS
+.IP "-a, --ip"
+IPv6 address to release.
+.IP "-c, --client-id"
+Colon-separated hex string representing DHCPv6 client id. Normally
+it can be found in leases file both on client and server.
+.IP "-d, --dry-run"
+Print hexadecimal representation of generated DHCPv6 release packet to standard
+output and exit.
+.IP "-h, --help"
+print usage information to standard output and exit.
+.IP "-i, --iaid"
+Decimal representation of DHCPv6 IAID. Normally it can be found in leases file
+both on client and server.
+.IP "-n, --iface"
+Network interface to send a DHCPv6 release packet from.
+.IP "-s, --server-id"
+Colon-separated hex string representing DHCPv6 server id. Normally
+it can be found in leases file both on client and server.
+.SH NOTES
+MUST be run as root - will fail otherwise.
+.SH LIMITATIONS
+Only usable on IPv6 DHCP leases.
+.SH SEE ALSO
+.BR dnsmasq (8)
+.SH AUTHOR
+This manual page was written by Simon Kelley <simon@thekelleys.org.uk>.
+
+
diff --git a/contrib/lease-tools/dhcp_release6.c b/contrib/lease-tools/dhcp_release6.c
new file mode 100644
index 0000000..74fb26a
--- /dev/null
+++ b/contrib/lease-tools/dhcp_release6.c
@@ -0,0 +1,416 @@
+/*
+ dhcp_release6 --iface <interface> --client-id <client-id> --server-id
+ server-id --iaid <iaid> --ip <IP> [--dry-run] [--help]
+ MUST be run as root - will fail othewise
+ */
+
+/* Send a DHCPRELEASE message to IPv6 multicast address via the specified interface
+ to tell the local DHCP server to delete a particular lease.
+
+ The interface argument is the interface in which a DHCP
+ request _would_ be received if it was coming from the client,
+ rather than being faked up here.
+
+ The client-id argument is colon-separated hex string and mandatory. Normally
+ it can be found in leases file both on client and server
+
+ The server-id argument is colon-separated hex string and mandatory. Normally
+ it can be found in leases file both on client and server.
+
+ The iaid argument is numeric string and mandatory. Normally
+ it can be found in leases file both on client and server.
+
+ IP is an IPv6 adress to release
+
+ If --dry-run is specified, dhcp_release6 just prints hexadecimal represantation of
+ packet to send to stdout and exits.
+
+ If --help is specified, dhcp_release6 print usage information to stdout and exits
+
+
+
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <getopt.h>
+#include <errno.h>
+#include <unistd.h>
+
+#define NOT_REPLY_CODE 115
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+
+enum DHCP6_TYPES{
+ SOLICIT = 1,
+ ADVERTISE = 2,
+ REQUEST = 3,
+ CONFIRM = 4,
+ RENEW = 5,
+ REBIND = 6,
+ REPLY = 7,
+ RELEASE = 8,
+ DECLINE = 9,
+ RECONFIGURE = 10,
+ INFORMATION_REQUEST = 11,
+ RELAY_FORW = 12,
+ RELAY_REPL = 13
+
+};
+enum DHCP6_OPTIONS{
+ CLIENTID = 1,
+ SERVERID = 2,
+ IA_NA = 3,
+ IA_TA = 4,
+ IAADDR = 5,
+ ORO = 6,
+ PREFERENCE = 7,
+ ELAPSED_TIME = 8,
+ RELAY_MSG = 9,
+ AUTH = 11,
+ UNICAST = 12,
+ STATUS_CODE = 13,
+ RAPID_COMMIT = 14,
+ USER_CLASS = 15,
+ VENDOR_CLASS = 16,
+ VENDOR_OPTS = 17,
+ INTERFACE_ID = 18,
+ RECONF_MSG = 19,
+ RECONF_ACCEPT = 20,
+};
+
+enum DHCP6_STATUSES{
+ SUCCESS = 0,
+ UNSPEC_FAIL = 1,
+ NOADDR_AVAIL=2,
+ NO_BINDING = 3,
+ NOT_ON_LINK = 4,
+ USE_MULTICAST =5
+};
+static struct option longopts[] = {
+ {"ip", required_argument, 0, 'a'},
+ {"server-id", required_argument, 0, 's'},
+ {"client-id", required_argument, 0, 'c'},
+ {"iface", required_argument, 0, 'n'},
+ {"iaid", required_argument, 0, 'i'},
+ {"dry-run", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+};
+
+const short DHCP6_CLIENT_PORT = 546;
+const short DHCP6_SERVER_PORT = 547;
+
+const char* DHCP6_MULTICAST_ADDRESS = "ff02::1:2";
+
+struct dhcp6_option{
+ uint16_t type;
+ uint16_t len;
+ char value[1024];
+};
+
+struct dhcp6_iaaddr_option{
+ uint16_t type;
+ uint16_t len;
+ struct in6_addr ip;
+ uint32_t preferred_lifetime;
+ uint32_t valid_lifetime;
+
+
+};
+
+struct dhcp6_iana_option{
+ uint16_t type;
+ uint16_t len;
+ uint32_t iaid;
+ uint32_t t1;
+ uint32_t t2;
+ char options[1024];
+};
+
+
+struct dhcp6_packet{
+ size_t len;
+ char buf[2048];
+
+} ;
+
+size_t pack_duid(const char* str, char* dst){
+
+ char* tmp = strdup(str);
+ char* tmp_to_free = tmp;
+ char *ptr;
+ uint8_t write_pos = 0;
+ while ((ptr = strtok (tmp, ":"))) {
+ dst[write_pos] = (uint8_t) strtol(ptr, NULL, 16);
+ write_pos += 1;
+ tmp = NULL;
+
+ }
+ free(tmp_to_free);
+ return write_pos;
+}
+
+struct dhcp6_option create_client_id_option(const char* duid){
+ struct dhcp6_option option;
+ option.type = htons(CLIENTID);
+ bzero(option.value, sizeof(option.value));
+ option.len = htons(pack_duid(duid, option.value));
+ return option;
+}
+
+struct dhcp6_option create_server_id_option(const char* duid){
+ struct dhcp6_option option;
+ option.type = htons(SERVERID);
+ bzero(option.value, sizeof(option.value));
+ option.len = htons(pack_duid(duid, option.value));
+ return option;
+}
+
+struct dhcp6_iaaddr_option create_iaadr_option(const char* ip){
+ struct dhcp6_iaaddr_option result;
+ result.type =htons(IAADDR);
+ /* no suboptions needed here, so length is 24 */
+ result.len = htons(24);
+ result.preferred_lifetime = 0;
+ result.valid_lifetime = 0;
+ int s = inet_pton(AF_INET6, ip, &(result.ip));
+ if (s <= 0) {
+ if (s == 0)
+ fprintf(stderr, "Not in presentation format");
+ else
+ perror("inet_pton");
+ exit(EXIT_FAILURE);
+ }
+ return result;
+}
+struct dhcp6_iana_option create_iana_option(const char * iaid, struct dhcp6_iaaddr_option ia_addr){
+ struct dhcp6_iana_option result;
+ result.type = htons(IA_NA);
+ result.iaid = htonl(atoi(iaid));
+ result.t1 = 0;
+ result.t2 = 0;
+ result.len = htons(12 + ntohs(ia_addr.len) + 2 * sizeof(uint16_t));
+ memcpy(result.options, &ia_addr, ntohs(ia_addr.len) + 2 * sizeof(uint16_t));
+ return result;
+}
+
+struct dhcp6_packet create_release_packet(const char* iaid, const char* ip, const char* client_id, const char* server_id){
+ struct dhcp6_packet result;
+ bzero(result.buf, sizeof(result.buf));
+ /* message_type */
+ result.buf[0] = RELEASE;
+ /* tx_id */
+ bzero(result.buf+1, 3);
+
+ struct dhcp6_option client_option = create_client_id_option(client_id);
+ struct dhcp6_option server_option = create_server_id_option(server_id);
+ struct dhcp6_iaaddr_option iaaddr_option = create_iaadr_option(ip);
+ struct dhcp6_iana_option iana_option = create_iana_option(iaid, iaaddr_option);
+ int offset = 4;
+ memcpy(result.buf + offset, &client_option, ntohs(client_option.len) + 2*sizeof(uint16_t));
+ offset += (ntohs(client_option.len)+ 2 *sizeof(uint16_t) );
+ memcpy(result.buf + offset, &server_option, ntohs(server_option.len) + 2*sizeof(uint16_t) );
+ offset += (ntohs(server_option.len)+ 2* sizeof(uint16_t));
+ memcpy(result.buf + offset, &iana_option, ntohs(iana_option.len) + 2*sizeof(uint16_t) );
+ offset += (ntohs(iana_option.len)+ 2* sizeof(uint16_t));
+ result.len = offset;
+ return result;
+}
+
+uint16_t parse_iana_suboption(char* buf, size_t len){
+ size_t current_pos = 0;
+ char option_value[1024];
+ while (current_pos < len) {
+ uint16_t option_type, option_len;
+ memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
+ memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
+ option_type = ntohs(option_type);
+ option_len = ntohs(option_len);
+ current_pos += 2 * sizeof(uint16_t);
+ if (option_type == STATUS_CODE){
+ uint16_t status;
+ memcpy(&status, buf + current_pos, sizeof(uint16_t));
+ status = ntohs(status);
+ if (status != SUCCESS){
+ memcpy(option_value, buf + current_pos + sizeof(uint16_t) , option_len - sizeof(uint16_t));
+ option_value[option_len-sizeof(uint16_t)] ='\0';
+ fprintf(stderr, "Error: %s\n", option_value);
+ }
+ return status;
+ }
+ }
+ return -2;
+}
+
+int16_t parse_packet(char* buf, size_t len){
+ uint8_t type = buf[0];
+ /*skipping tx id. you need it, uncomment following line
+ uint16_t tx_id = ntohs((buf[1] <<16) + (buf[2] <<8) + buf[3]);
+ */
+ size_t current_pos = 4;
+ if (type != REPLY ){
+ return NOT_REPLY_CODE;
+ }
+ char option_value[1024];
+ while (current_pos < len) {
+ uint16_t option_type, option_len;
+ memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
+ memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
+ option_type = ntohs(option_type);
+ option_len = ntohs(option_len);
+ current_pos += 2 * sizeof(uint16_t);
+ if (option_type == STATUS_CODE){
+ uint16_t status;
+ memcpy(&status, buf + current_pos, sizeof(uint16_t));
+ status = ntohs(status);
+ if (status != SUCCESS){
+ memcpy(option_value, buf + current_pos +sizeof(uint16_t) , option_len -sizeof(uint16_t));
+ fprintf(stderr, "Error: %d %s\n", status, option_value);
+ return status;
+ }
+
+ }
+ if (option_type == IA_NA ){
+ uint16_t result = parse_iana_suboption(buf + current_pos +24, option_len -24);
+ if (result){
+ return result;
+ }
+ }
+ current_pos += option_len;
+
+ }
+ return -1;
+}
+
+void usage(const char* arg, FILE* stream){
+ const char* usage_string ="--ip IPv6 --iface IFACE --server-id SERVER_ID --client-id CLIENT_ID --iaid IAID [--dry-run] | --help";
+ fprintf (stream, "Usage: %s %s\n", arg, usage_string);
+
+}
+
+int send_release_packet(const char* iface, struct dhcp6_packet* packet){
+
+ struct sockaddr_in6 server_addr, client_addr;
+ char response[1400];
+ int sock = socket(PF_INET6, SOCK_DGRAM, 0);
+ int i = 0;
+ if (sock < 0) {
+ perror("creating socket");
+ return -1;
+ }
+ if (setsockopt(sock, SOL_SOCKET, 25, iface, strlen(iface)) == -1) {
+ perror("SO_BINDTODEVICE");
+ close(sock);
+ return -1;
+ }
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin6_family = AF_INET6;
+ client_addr.sin6_family = AF_INET6;
+ client_addr.sin6_port = htons(DHCP6_CLIENT_PORT);
+ client_addr.sin6_flowinfo = 0;
+ client_addr.sin6_scope_id =0;
+ inet_pton(AF_INET6, "::", &client_addr.sin6_addr);
+ bind(sock, (struct sockaddr*)&client_addr, sizeof(struct sockaddr_in6));
+ inet_pton(AF_INET6, DHCP6_MULTICAST_ADDRESS, &server_addr.sin6_addr);
+ server_addr.sin6_port = htons(DHCP6_SERVER_PORT);
+ int16_t recv_size = 0;
+ for (i = 0; i < 5; i++) {
+ if (sendto(sock, packet->buf, packet->len, 0,
+ (struct sockaddr *)&server_addr,
+ sizeof(server_addr)) < 0) {
+ perror("sendto failed");
+ exit(4);
+ }
+ recv_size = recvfrom(sock, response, sizeof(response), MSG_DONTWAIT, NULL, 0);
+ if (recv_size == -1){
+ if (errno == EAGAIN){
+ sleep(1);
+ continue;
+ }else {
+ perror("recvfrom");
+ }
+ }
+ int16_t result = parse_packet(response, recv_size);
+ if (result == NOT_REPLY_CODE){
+ sleep(1);
+ continue;
+ }
+ return result;
+ }
+ fprintf(stderr, "Response timed out\n");
+ return -1;
+
+}
+
+
+int main(int argc, char * const argv[]) {
+ const char* iface = "";
+ const char* ip = "";
+ const char* client_id = "";
+ const char* server_id = "";
+ const char* iaid = "";
+ int dry_run = 0;
+ while (1) {
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "a:s:c:n:i:hd", longopts, &option_index);
+ if (c == -1){
+ break;
+ }
+ switch(c){
+ case 0:
+ if (longopts[option_index].flag !=0){
+ break;
+ }
+ printf ("option %s", longopts[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+ case 'i':
+ iaid = optarg;
+ break;
+ case 'n':
+ iface = optarg;
+ break;
+ case 'a':
+ ip = optarg;
+ break;
+ case 'c':
+ client_id = optarg;
+ break;
+ case 'd':
+ dry_run = 1;
+ break;
+ case 's':
+ server_id = optarg;
+ break;
+ case 'h':
+ usage(argv[0], stdout);
+ break;
+ case '?':
+ usage(argv[0], stderr);
+ return -1;
+ default:
+ abort();
+
+ }
+
+ }
+ struct dhcp6_packet packet = create_release_packet(iaid, ip, client_id, server_id);
+ if (dry_run){
+ uint16_t i;
+ for(i=0;i<packet.len;i++){
+ printf("%hhx", packet.buf[i]);
+ }
+ printf("\n");
+ return 0;
+ }
+ return send_release_packet(iface, &packet);
+
+}