]> git.decadent.org.uk Git - odhcp6c.git/commitdiff
Merge branch 'master' into hnet
authorMarkus Stenberg <markus.stenberg@iki.fi>
Mon, 24 Jun 2013 10:53:03 +0000 (13:53 +0300)
committerMarkus Stenberg <markus.stenberg@iki.fi>
Mon, 24 Jun 2013 10:53:38 +0000 (13:53 +0300)
Conflicts:
README
src/dhcpv6.c
src/odhcp6c.h

CMakeLists.txt
README
src/dhcpv6.c
src/md5.c [new file with mode: 0644]
src/md5.h [new file with mode: 0644]
src/odhcp6c.c
src/odhcp6c.h
src/ra.c
src/ra.h
src/script.c

index 0db7aed1a50c0bc727cd2a6284768b62b868cc26..db7ef656259c75d14586b1aaa516c6bd6bce0d63 100644 (file)
@@ -7,7 +7,7 @@ set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -std=c99")
 add_definitions(-D_GNU_SOURCE -Wall -Werror -Wextra -pedantic)
 
-add_executable(odhcp6c src/odhcp6c.c src/dhcpv6.c src/ra.c src/script.c)
+add_executable(odhcp6c src/odhcp6c.c src/dhcpv6.c src/ra.c src/script.c src/md5.c)
 target_link_libraries(odhcp6c resolv)
 
 # Installation
diff --git a/README b/README
index 2a7ab633f4634ec09344f9b4f85b12769b602178..3f2087fe65f45cdc3cceec939ebf5795cec037b3 100644 (file)
--- a/README
+++ b/README
@@ -3,22 +3,35 @@ odhcp6c - Embedded DHCPv6 Client
 
 ** Abstract **
 
-odhcp6c is a minimalistic DHCPv6 client for use in embedded Linux systems.
+odhcp6c is a minimal DHCPv6 and RA-client for use in embedded Linux systems
+especially routers. It compiles to only about 30 KB (-Os -s).
 
 
 ** Features **
 
-1. Handling of non-temporary addresses
-       a) assignment of addresses to source interface
-       b) handling of valid and preferred lifetimes
-       c) duplicate address detection
+1. IPv6 bootstrap from different environments with autodetection
+       a) RA only
+       b) RA + stateless DHCPv6
+       c) RA + stateful DHCPv6 (either IA_NA or IA_PD or both)
 
-2. Handling of IPv6-Prefixes (Prefix Delegation)
-       a) requesting of prefixes
+2. Handling of non-temporary addresses (IA_NA)
+       a) handling of valid and preferred lifetimes
+       b) duplicate address detection
+       c) automatic fallback to stateless or PD-only mode
 
-3. Stateless fallback-support
+3. Support for DHCPv6 extension
+       a) Reconfigure-Messages
+       b) Prefix Delegation (including handling of valid and preferred lifetimes)
+       c) Prefix Exclusion
+       d) DNS Configuration Options
+       e) NTP Options
+       f) SIP Options
+       g) Information-Refresh Options
+       h) SOL_MAX_RT default to 3600
+       i) DS-Lite AFTR-Name Option
 
-4. State script support
+4. Support for requesting and parsing Router Advertisements
+       a) parsing of prefixes, routes, MTU and RDNSS options
 
 
 ** Compiling **
@@ -40,7 +53,7 @@ States:
 * bound                        A suitable server was found and addresses or prefixes acquired          
 * informed             A stateless information request returned updated information
 * updated              Updated information was received from the DHCPv6 server
-* ra-updated   Updated information was received from via Router Advertisement
+* ra-updated           Updated information was received from via Router Advertisement
 * rebound              The DHCPv6 client switched to another server
 * unbound              The DHCPv6 client lost all DHCPv6 servers and will restart
 * stopped              The DHCPv6 client has been stopped
@@ -53,22 +66,15 @@ Environment:
 * SNTP_FQDN            A space-separated list of SNTP server FQDNs
 * SIP_IP               A space-separated list of SIP servers
 * SIP_DOMAIN           A space-separated list of SIP domains
-* OPTION_<num> Custom option received as base-16
+* OPTION_<num>         Custom option received as base-16
 * PREFIXES             A space-separated list of prefixes currently assigned
-                               Format: <prefix>/<length>,preferred,valid[,cls]
+                               Format: <prefix>/<length>,preferred,valid[,excluded=<excluded-prefix>/<length>][,class=<prefix class #>]
 * ADDRESSES            A space-separated list of addresses currently assigned
                                Format: <address>/<length>,preferred,valid
-* RA_ADDRESSES A space-separated list of addresses from RA-prefixes
+* RA_ADDRESSES         A space-separated list of addresses from RA-prefixes
                                Format: <address>/<length>,preferred,valid
 * RA_ROUTES            A space-separated list of routes from the RA
                                Format: <address>/<length>,gateway,valid,metric
 * RA_DNS               A space-separated list of recursive DNS servers from the RA
-
-
-
-
-** Wishlist **
-
-Features that I would like to see implemented in the near or far future:
-
-* Reconfigure Authentication 
+* AFTR                 The DS-Lite AFTR domain name
+* AFTR_IP              The DS-Lite AFTR resolved IPv6 address
index 161f6287a546302db2288520461e8927f7d6ab6a..7f2aa1a70ab91c22c2b18d0700c9e0c07ee2a639 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License v2 as published by
@@ -15,6 +15,7 @@
 #include <time.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <stdlib.h>
 #include <signal.h>
 #include <limits.h>
 #include <resolv.h>
@@ -31,6 +32,7 @@
 #include <net/ethernet.h>
 
 #include "odhcp6c.h"
+#include "md5.h"
 
 
 #define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
@@ -58,7 +60,7 @@ static int dhcpv6_commit_advert(void);
 static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
        [DHCPV6_MSG_UNKNOWN] = {false, 1, 120, "<POLL>",
                        dhcpv6_handle_reconfigure, NULL},
-       [DHCPV6_MSG_SOLICIT] = {true, 1, 120, "SOLICIT",
+       [DHCPV6_MSG_SOLICIT] = {true, 1, 3600, "SOLICIT",
                        dhcpv6_handle_advert, dhcpv6_commit_advert},
        [DHCPV6_MSG_REQUEST] = {true, 30, 10, "REQUEST",
                        dhcpv6_handle_reply, NULL},
@@ -83,6 +85,9 @@ static int request_prefix = -1;
 static enum odhcp6c_ia_mode na_mode = IA_MODE_NONE;
 static bool accept_reconfig = false;
 
+// Reconfigure key
+static uint8_t reconf_key[16];
+
 
 
 int init_dhcpv6(const char *ifname, int request_pd)
@@ -131,12 +136,17 @@ int init_dhcpv6(const char *ifname, int request_pd)
        }
 
        // Create ORO
-       uint16_t oro[] = {htons(DHCPV6_OPT_DNS_SERVERS),
+       uint16_t oro[] = {
+                       htons(DHCPV6_OPT_SIP_SERVER_D),
+                       htons(DHCPV6_OPT_SIP_SERVER_A),
+                       htons(DHCPV6_OPT_DNS_SERVERS),
                        htons(DHCPV6_OPT_DNS_DOMAIN),
                        htons(DHCPV6_OPT_NTP_SERVER),
                        htons(DHCPV6_OPT_SIP_SERVER_A),
-                          htons(DHCPV6_OPT_PREFIX_CLASS)
-                         };
+                       htons(DHCPV6_OPT_AFTR_NAME),
+                       htons(DHCPV6_OPT_PD_EXCLUDE),
+                       htons(DHCPV6_OPT_PREFIX_CLASS),
+       };
        odhcp6c_add_state(STATE_ORO, oro, sizeof(oro));
 
 
@@ -189,7 +199,6 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
 
        // Build IA_PDs
        size_t ia_pd_entries, ia_pd_len = 0;
-       void *ia_pd = NULL;
        struct odhcp6c_entry *e = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entries);
        ia_pd_entries /= sizeof(*e);
        struct dhcpv6_ia_hdr hdr_ia_pd = {
@@ -198,32 +207,51 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
                1, 0, 0
        };
 
-       struct dhcpv6_ia_prefix pref = {
-               .type = htons(DHCPV6_OPT_IA_PREFIX),
-               .len = htons(25), .prefix = request_prefix
-       };
-
 
-       struct dhcpv6_ia_prefix p[ia_pd_entries];
+       uint8_t *ia_pd = alloca(ia_pd_entries * (sizeof(struct dhcpv6_ia_prefix) + 10));
        for (size_t i = 0; i < ia_pd_entries; ++i) {
-               p[i].type = htons(DHCPV6_OPT_IA_PREFIX);
-               p[i].len = htons(sizeof(p[i]) - 4U);
-               p[i].preferred = 0;
-               p[i].valid = 0;
-               p[i].prefix = e[i].length;
-               p[i].addr = e[i].target;
+               uint8_t ex_len = 0;
+               if (e[i].priority > 0)
+                       ex_len = ((e[i].priority - e[i].length - 1) / 8) + 6;
+
+               struct dhcpv6_ia_prefix p = {
+                       .type = htons(DHCPV6_OPT_IA_PREFIX),
+                       .len = htons(sizeof(p) - 4U + ex_len),
+                       .prefix = e[i].length,
+                       .addr = e[i].target
+               };
+
+               memcpy(ia_pd + ia_pd_len, &p, sizeof(p));
+               ia_pd_len += sizeof(p);
+
+               if (ex_len) {
+                       ia_pd[ia_pd_len++] = 0;
+                       ia_pd[ia_pd_len++] = DHCPV6_OPT_PD_EXCLUDE;
+                       ia_pd[ia_pd_len++] = 0;
+                       ia_pd[ia_pd_len++] = ex_len - 4;
+                       ia_pd[ia_pd_len++] = e[i].priority;
+
+                       uint32_t excl = ntohl(e[i].router.s6_addr32[1]);
+                       excl >>= (64 - e[i].priority);
+                       excl <<= 8 - ((e[i].priority - e[i].length) % 8);
+
+                       for (size_t i = ex_len - 5; i > 0; --i, excl >>= 8)
+                               ia_pd[ia_pd_len + i] = excl & 0xff;
+                       ia_pd_len += ex_len - 5;
+               }
        }
-       ia_pd = p;
-       ia_pd_len = sizeof(p);
-       hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
 
-       if (request_prefix > 0 &&
+       struct dhcpv6_ia_prefix pref = {
+               .type = htons(DHCPV6_OPT_IA_PREFIX),
+               .len = htons(25), .prefix = request_prefix
+       };
+       if (request_prefix > 0 && ia_pd_len == 0 &&
                        (type == DHCPV6_MSG_SOLICIT ||
                        type == DHCPV6_MSG_REQUEST)) {
-               ia_pd = &pref;
+               ia_pd = (uint8_t*)&pref;
                ia_pd_len = sizeof(pref);
-               hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
        }
+       hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
 
        // Build IA_NAs
        size_t ia_na_entries, ia_na_len = 0;
@@ -283,7 +311,7 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
                {&oro_refresh, 0},
                {cl_id, cl_id_len},
                {srv_id, srv_id_len},
-               {&reconf_accept, 0},
+               {&reconf_accept, sizeof(reconf_accept)},
                {&fqdn, fqdn_len},
                {&hdr_ia_na, sizeof(hdr_ia_na)},
                {ia_na, ia_na_len},
@@ -301,9 +329,8 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        }
 
        // Disable IAs if not used
-       if (type == DHCPV6_MSG_SOLICIT) {
-               iov[5].iov_len = sizeof(reconf_accept);
-       } else if (type != DHCPV6_MSG_REQUEST) {
+       if (type != DHCPV6_MSG_REQUEST && type != DHCPV6_MSG_SOLICIT) {
+               iov[5].iov_len = 0;
                if (ia_na_len == 0)
                        iov[7].iov_len = 0;
                if (ia_pd_len == 0)
@@ -346,9 +373,9 @@ int dhcpv6_request(enum dhcpv6_msg type)
        else if (type == DHCPV6_MSG_UNKNOWN)
                timeout = t1;
        else if (type == DHCPV6_MSG_RENEW)
-               timeout = t2 - t1;
+               timeout = (t2 > t1) ? t2 - t1 : 0;
        else if (type == DHCPV6_MSG_REBIND)
-               timeout = t3 - t2;
+               timeout = (t3 > t2) ? t3 - t2 : 0;
 
        if (timeout == 0)
                return -1;
@@ -358,8 +385,9 @@ int dhcpv6_request(enum dhcpv6_msg type)
        uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
 
        // Generate transaction ID
-       uint8_t trid[3];
-       odhcp6c_random(trid, sizeof(trid));
+       uint8_t trid[3] = {0, 0, 0};
+       if (type != DHCPV6_MSG_UNKNOWN)
+               odhcp6c_random(trid, sizeof(trid));
        ssize_t len = -1;
        int64_t rto = 0;
 
@@ -451,19 +479,55 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
 
        uint8_t *end = ((uint8_t*)buf) + len, *odata;
        uint16_t otype, olen;
-       bool clientid_ok = false, serverid_ok = false;
+       bool clientid_ok = false, serverid_ok = false, rcauth_ok = false;
 
        size_t client_id_len, server_id_len;
        void *client_id = odhcp6c_get_state(STATE_CLIENT_ID, &client_id_len);
        void *server_id = odhcp6c_get_state(STATE_SERVER_ID, &server_id_len);
 
-       dhcpv6_for_each_option(&rep[1], end, otype, olen, odata)
-               if (otype == DHCPV6_OPT_CLIENTID)
+       dhcpv6_for_each_option(&rep[1], end, otype, olen, odata) {
+               if (otype == DHCPV6_OPT_CLIENTID) {
                        clientid_ok = (olen + 4U == client_id_len) && !memcmp(
                                        &odata[-4], client_id, client_id_len);
-               else if (otype == DHCPV6_OPT_SERVERID)
+               } else if (otype == DHCPV6_OPT_SERVERID) {
                        serverid_ok = (olen + 4U == server_id_len) && !memcmp(
                                        &odata[-4], server_id, server_id_len);
+               } else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
+                               sizeof(struct dhcpv6_auth_reconfigure)) {
+                       struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
+                       if (r->protocol != 3 || r->algorithm != 1 || r->reconf_type != 2)
+                               continue;
+
+                       md5_state_t md5;
+                       uint8_t serverhash[16], secretbytes[16], hash[16];
+                       memcpy(serverhash, r->key, sizeof(serverhash));
+                       memset(r->key, 0, sizeof(r->key));
+                       memcpy(secretbytes, reconf_key, sizeof(secretbytes));
+
+                       for (size_t i = 0; i < sizeof(secretbytes); ++i)
+                               secretbytes[i] ^= 0x36;
+
+                       md5_init(&md5);
+                       md5_append(&md5, secretbytes, sizeof(secretbytes));
+                       md5_append(&md5, buf, len);
+                       md5_finish(&md5, hash);
+
+                       for (size_t i = 0; i < sizeof(secretbytes); ++i) {
+                               secretbytes[i] ^= 0x36;
+                               secretbytes[i] ^= 0x5c;
+                       }
+
+                       md5_init(&md5);
+                       md5_append(&md5, secretbytes, sizeof(secretbytes));
+                       md5_append(&md5, hash, 16);
+                       md5_finish(&md5, hash);
+
+                       rcauth_ok = !memcmp(hash, serverhash, sizeof(hash));
+               }
+       }
+
+       if (rep->msg_type == DHCPV6_MSG_RECONF && !rcauth_ok)
+               return false;
 
        return clientid_ok && (serverid_ok || server_id_len == 0);
 }
@@ -525,7 +589,7 @@ static int dhcpv6_handle_advert(_unused enum dhcpv6_msg orig,
                } else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
                        cand.wants_reconfigure = true;
                } else if (otype == DHCPV6_OPT_IA_PD && request_prefix) {
-                       struct dhcpv6_ia_hdr *h = (void*)odata;
+                       struct dhcpv6_ia_hdr *h = (struct dhcpv6_ia_hdr*)&odata[-4];
                        uint8_t *oend = odata + olen, *d;
                        dhcpv6_for_each_option(&h[1], oend, otype, olen, d) {
                                if (otype == DHCPV6_OPT_IA_PREFIX)
@@ -604,13 +668,15 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig,
        uint8_t *odata;
        uint16_t otype, olen;
 
-       static time_t last_update = 0;
-       time_t now = odhcp6c_get_milli_time() / 1000;
-
-       uint32_t elapsed = now - last_update;
        odhcp6c_expire();
 
        if (orig == DHCPV6_MSG_UNKNOWN) {
+               static time_t last_update = 0;
+               time_t now = odhcp6c_get_milli_time() / 1000;
+
+               uint32_t elapsed = (last_update > 0) ? now - last_update : 0;
+               last_update = now;
+
                t1 -= elapsed;
                t2 -= elapsed;
                t3 -= elapsed;
@@ -634,6 +700,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig,
                odhcp6c_clear_state(STATE_SNTP_FQDN);
                odhcp6c_clear_state(STATE_SIP_IP);
                odhcp6c_clear_state(STATE_SIP_FQDN);
+               odhcp6c_clear_state(STATE_AFTR_NAME);
        }
 
        // Parse and find all matching IAs
@@ -661,29 +728,23 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig,
                        if (error)
                                continue;
 
-                       // Update times
-                       if (l_t1 > 0 && t1 > l_t1)
-                               t1 = l_t1;
-
-                       if (l_t2 > 0 && t2 > l_t2)
-                               t2 = l_t2;
-
                        uint32_t n = dhcpv6_parse_ia(&ia_hdr[1], odata + olen);
 
-                       if (n < t1)
-                               t1 = n;
+                       if (!l_t1)
+                               l_t1 = 300;
 
-                       if (n < t2)
-                               t2 = n;
+                       if (!l_t2)
+                               l_t2 = 600;
 
                        if (n < t3)
                                t3 = n;
 
-                       if (t2 >= t3)
-                               t2 = 8 * t3 / 10;
+                       // Update times
+                       if (l_t1 > 0 && t1 > l_t1)
+                               t1 = l_t1;
 
-                       if (t1 >= t2)
-                               t1 = 5 * t2 / 8;
+                       if (l_t2 > 0 && t2 > l_t2)
+                               t2 = l_t2;
 
                } else if (otype == DHCPV6_OPT_DNS_SERVERS) {
                        if (olen % 16 == 0)
@@ -713,6 +774,17 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig,
                        uint32_t refresh = ntohl(*((uint32_t*)odata));
                        if (refresh < (uint32_t)t1)
                                t1 = refresh;
+               } else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
+                               sizeof(struct dhcpv6_auth_reconfigure)) {
+                       struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
+                       if (r->protocol == 3 && r->algorithm == 1 &&
+                                       r->reconf_type == 1)
+                               memcpy(reconf_key, r->key, sizeof(r->key));
+               } else if (otype == DHCPV6_OPT_AFTR_NAME && olen > 3) {
+                       size_t cur_len;
+                       odhcp6c_get_state(STATE_AFTR_NAME, &cur_len);
+                       if (cur_len == 0)
+                               odhcp6c_add_state(STATE_AFTR_NAME, odata, olen);
                } else if (otype != DHCPV6_OPT_CLIENTID &&
                                otype != DHCPV6_OPT_SERVERID) {
                        odhcp6c_add_state(STATE_CUSTOM_OPTS,
@@ -757,7 +829,48 @@ static uint32_t dhcpv6_parse_ia(void *opt, void *end)
                           if (stype == DHCPV6_OPT_PREFIX_CLASS && slen == 2) 
                             entry.prefix_class = ntohs(*((uint16_t*)sdata));
 
-                       odhcp6c_update_entry(STATE_IA_PD, &entry);
+                       // Parse PD-exclude
+                       bool ok = true;
+                       dhcpv6_for_each_option(odata + sizeof(*prefix) - 4U,
+                                       odata + olen, stype, slen, sdata) {
+                               if (stype != DHCPV6_OPT_PD_EXCLUDE || slen < 2)
+                                       continue;
+
+                               uint8_t elen = sdata[0];
+                               if (elen > 64)
+                                       elen = 64;
+
+                               if (elen <= 32 || elen <= entry.length) {
+                                       ok = false;
+                                       continue;
+                               }
+
+
+                               uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
+                               if (slen <= bytes) {
+                                       ok = false;
+                                       continue;
+                               }
+
+                               uint32_t exclude = 0;
+                               do {
+                                       exclude = exclude << 8 | sdata[bytes];
+                               } while (--bytes);
+
+                               exclude >>= 8 - ((elen - entry.length) % 8);
+                               exclude <<= 64 - elen;
+
+                               // Abusing router & priority fields for exclusion
+                               entry.router = entry.target;
+                               entry.router.s6_addr32[1] |= htonl(exclude);
+                               entry.priority = elen;
+                       }
+
+                       if (ok)
+                               odhcp6c_update_entry(STATE_IA_PD, &entry);
+
+                       entry.priority = 0;
+                       memset(&entry.router, 0, sizeof(entry.router));
                } else if (otype == DHCPV6_OPT_IA_ADDR) {
                        struct dhcpv6_ia_addr *addr = (void*)&odata[-4];
                        if (olen + 4U < sizeof(*addr))
diff --git a/src/md5.c b/src/md5.c
new file mode 100644 (file)
index 0000000..4ee4329
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,388 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+       http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+       either statically or dynamically; added missing #include <string.h>
+       in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+       type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+       unsigned in ANSI C, signed in traditional"; made test program
+       self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <endian.h>
+#include <string.h>
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define ARCH_IS_BIG_ENDIAN 0
+#elif __BYTE_ORDER == __BIG_ENDIAN
+# define ARCH_IS_BIG_ENDIAN 1
+#endif
+
+#undef BYTE_ORDER      /* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+       a = pms->abcd[0], b = pms->abcd[1],
+       c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+       /*
+        * Determine dynamically whether this is a big-endian or
+        * little-endian machine, since we can use a more efficient
+        * algorithm on the latter.
+        */
+       static const int w = 1;
+
+       if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0            /* little-endian */
+       {
+           /*
+            * On little-endian machines, we can process properly aligned
+            * data without copying it.
+            */
+           if (!((data - (const md5_byte_t *)0) & 3)) {
+               /* data are properly aligned */
+               X = (const md5_word_t *)data;
+           } else {
+               /* not aligned */
+               memcpy(xbuf, data, 64);
+               X = xbuf;
+           }
+       }
+#endif
+#if BYTE_ORDER == 0
+       else                    /* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0            /* big-endian */
+       {
+           /*
+            * On big-endian machines, we must arrange the bytes in the
+            * right order.
+            */
+           const md5_byte_t *xp = data;
+           int i;
+
+#  if BYTE_ORDER == 0
+           X = xbuf;           /* (dynamic only) */
+#  else
+#    define xbuf X             /* (static only) */
+#  endif
+           for (i = 0; i < 16; ++i, xp += 4)
+               xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+       }
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+       return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+       pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+       int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+       memcpy(pms->buf + offset, p, copy);
+       if (offset + copy < 64)
+           return;
+       p += copy;
+       left -= copy;
+       md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+       md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+       memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+       0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+       data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+       digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
diff --git a/src/md5.h b/src/md5.h
new file mode 100644 (file)
index 0000000..698c995
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+       http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+       references to Ghostscript; clarified derivation from RFC 1321;
+       now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+       added conditionalization for C++ compilation from Martin
+       Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];       /* message length in bits, lsw first */
+    md5_word_t abcd[4];                /* digest buffer */
+    md5_byte_t buf[64];                /* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
index 3e989a2d39c40031c59585d5b384fe146b37f3dc..6da604728c4f330d446e10c5c0e941c6ce529d65 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License v2 as published by
@@ -27,6 +27,7 @@
 #include <net/if.h>
 #include <sys/wait.h>
 #include <sys/syscall.h>
+#include <arpa/inet.h>
 
 #include "odhcp6c.h"
 #include "ra.h"
@@ -40,8 +41,9 @@ static uint8_t *state_data[_STATE_MAX] = {NULL};
 static size_t state_len[_STATE_MAX] = {0};
 
 static volatile int do_signal = 0;
-static int urandom_fd = -1;
-static bool bound = false, allow_slaac_only = true, release = true;
+static int urandom_fd = -1, allow_slaac_only = 0;
+static bool bound = false, release = true;
+static time_t last_update = 0;
 
 
 int main(_unused int argc, char* const argv[])
@@ -54,14 +56,15 @@ int main(_unused int argc, char* const argv[])
        char *optpos;
        uint16_t opttype;
        enum odhcp6c_ia_mode ia_na_mode = IA_MODE_TRY;
+       static struct in6_addr ifid = IN6ADDR_ANY_INIT;
 
        bool help = false, daemonize = false;
        int logopt = LOG_PID;
        int c, request_pd = 0;
-       while ((c = getopt(argc, argv, "SN:P:c:r:s:khedp:")) != -1) {
+       while ((c = getopt(argc, argv, "S::N:P:c:i:r:s:khedp:")) != -1) {
                switch (c) {
                case 'S':
-                       allow_slaac_only = false;
+                       allow_slaac_only = (optarg) ? atoi(optarg) : -1;
                        break;
 
                case 'N':
@@ -76,7 +79,9 @@ int main(_unused int argc, char* const argv[])
                        break;
 
                case 'P':
-                       allow_slaac_only = false;
+                       if (allow_slaac_only >= 0 && allow_slaac_only < 10)
+                               allow_slaac_only = 10;
+
                        request_pd = strtoul(optarg, NULL, 10);
                        if (request_pd == 0)
                                request_pd = -1;
@@ -95,6 +100,11 @@ int main(_unused int argc, char* const argv[])
                        }
                        break;
 
+               case 'i':
+                       if (inet_pton(AF_INET6, optarg, &ifid) != 1)
+                               help = true;
+                       break;
+
                case 'r':
                        optpos = optarg;
                        while (optpos[0]) {
@@ -148,7 +158,7 @@ int main(_unused int argc, char* const argv[])
        signal(SIGUSR2, sighandler);
 
        if ((urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY)) < 0 ||
-                       init_dhcpv6(ifname, request_pd) || ra_init(ifname) ||
+                       init_dhcpv6(ifname, request_pd) || ra_init(ifname, &ifid) ||
                        script_init(script, ifname)) {
                syslog(LOG_ERR, "failed to initialize: %s", strerror(errno));
                return 3;
@@ -307,10 +317,11 @@ static int usage(void)
        const char buf[] =
        "Usage: odhcp6c [options] <interface>\n"
        "\nFeature options:\n"
-       "       -S              Don't allow SLAAC-only (implied by -P)\n"
+       "       -S <time>       Wait at least <time> sec for a DHCP-server (0)\n"
        "       -N <mode>       Mode for requesting addresses [try|force|none]\n"
        "       -P <length>     Request IPv6-Prefix (0 = auto)\n"
        "       -c <clientid>   Override client-ID (base-16 encoded)\n"
+       "       -i <iface-id>   Use a custom interface identifier for RA handling\n"
        "       -r <options>    Options to be requested (comma-separated)\n"
        "       -s <script>     Status update script (/usr/sbin/odhcp6c-update)\n"
        "       -k              Don't send a RELEASE when stopping\n"
@@ -355,12 +366,13 @@ bool odhcp6c_signal_process(void)
 {
        if (do_signal == SIGIO) {
                do_signal = 0;
-               bool updated = ra_process();
-               updated |= ra_rtnl_process();
-               if (updated && (bound || allow_slaac_only)) {
-                       odhcp6c_expire();
-                       script_call("ra-updated");
-               }
+               bool ra_rtnled = ra_rtnl_process();
+               bool ra_updated = ra_process();
+
+               if (ra_rtnled || (ra_updated && (bound || allow_slaac_only == 0)))
+                       script_call("ra-updated"); // Immediate process urgent events
+               else if (ra_updated && !bound && allow_slaac_only > 0)
+                       script_delay_call("ra-updated", allow_slaac_only);
        }
 
        return do_signal != 0;
@@ -466,10 +478,8 @@ static void odhcp6c_expire_list(enum odhcp6c_state state, uint32_t elapsed)
 
 void odhcp6c_expire(void)
 {
-       static time_t last_update = 0;
        time_t now = odhcp6c_get_milli_time() / 1000;
-
-       uint32_t elapsed = now - last_update;
+       uint32_t elapsed = (last_update > 0) ? now - last_update : 0;
        last_update = now;
 
        odhcp6c_expire_list(STATE_RA_PREFIX, elapsed);
@@ -480,6 +490,12 @@ void odhcp6c_expire(void)
 }
 
 
+uint32_t odhcp6c_elapsed(void)
+{
+       return odhcp6c_get_milli_time() / 1000 - last_update;
+}
+
+
 void odhcp6c_random(void *buf, size_t len)
 {
        read(urandom_fd, buf, len);
index 1b46af01df98325b62913b06131bdcaff8cb4e6a..d8a9bffdf80e0040285182cc5de6ab3f0b3d6618 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License v2 as published by
@@ -51,7 +51,8 @@ enum dhcvp6_opt {
        DHCPV6_OPT_NTP_SERVER = 56,
        DHCPV6_OPT_SIP_SERVER_D = 21,
        DHCPV6_OPT_SIP_SERVER_A = 22,
-
+       DHCPV6_OPT_AFTR_NAME = 64,
+       DHCPV6_OPT_PD_EXCLUDE = 67,
         /* draft-bhandari-dhc-class-based-prefix */
        DHCPV6_OPT_PREFIX_CLASS = 200, /* NOT STANDARDIZED! */
 };
@@ -134,6 +135,18 @@ struct dhcpv6_duid {
        uint8_t data[128];
 } _packed;
 
+struct dhcpv6_auth_reconfigure {
+       uint16_t type;
+       uint16_t len;
+       uint8_t protocol;
+       uint8_t algorithm;
+       uint8_t rdm;
+       uint64_t replay;
+       uint8_t reconf_type;
+       uint8_t key[16];
+} _packed;
+
+
 #define dhcpv6_for_each_option(start, end, otype, olen, odata)\
        for (uint8_t *_o = (uint8_t*)(start); _o + 4 <= (uint8_t*)(end) &&\
                ((otype) = _o[0] << 8 | _o[1]) && ((odata) = (void*)&_o[4]) &&\
@@ -167,6 +180,7 @@ enum odhcp6c_state {
        STATE_RA_ROUTE,
        STATE_RA_PREFIX,
        STATE_RA_DNS,
+       STATE_AFTR_NAME,
        _STATE_MAX
 };
 
@@ -215,6 +229,7 @@ int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr,
 int script_init(const char *path, const char *ifname);
 ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src);
 void script_call(const char *status);
+void script_delay_call(const char *status, int timeout);
 
 bool odhcp6c_signal_process(void);
 uint64_t odhcp6c_get_milli_time(void);
@@ -232,3 +247,4 @@ void odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new);
 void odhcp6c_update_entry_safe(enum odhcp6c_state state, struct odhcp6c_entry *new, uint32_t safe);
 
 void odhcp6c_expire(void);
+uint32_t odhcp6c_elapsed(void);
index 580d4cdc3e9b185b3f685495379663c15647e74b..9a579851524786704875d675100d8fded72ef6b7 100644 (file)
--- a/src/ra.c
+++ b/src/ra.c
@@ -1,12 +1,28 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
 #include <fcntl.h>
 #include <stdio.h>
 #include <signal.h>
 #include <string.h>
 #include <stddef.h>
 #include <stdbool.h>
+#include <syslog.h>
 #include <unistd.h>
 
 #include <net/if.h>
+#include <arpa/inet.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netinet/icmp6.h>
@@ -26,11 +42,12 @@ static struct in6_addr lladdr = IN6ADDR_ANY_INIT;
 
 static void ra_send_rs(int signal __attribute__((unused)));
 
-int ra_init(const char *ifname)
+int ra_init(const char *ifname, const struct in6_addr *ifid)
 {
        sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
        if_index = if_nametoindex(ifname);
        strncpy(if_name, ifname, sizeof(if_name) - 1);
+       lladdr = *ifid;
 
        // Filter ICMPv6 package types
        struct icmp6_filter filt;
@@ -58,17 +75,19 @@ int ra_init(const char *ifname)
        fcntl(sock, F_SETOWN, ourpid);
        fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_ASYNC);
 
-       // Get LL-addr
-       FILE *fp = fopen("/proc/net/if_inet6", "r");
-       if (fp) {
-               char addrbuf[33], ifbuf[16];
-               while (fscanf(fp, "%32s %*x %*x %*x %*x %15s", addrbuf, ifbuf) == 2) {
-                       if (!strcmp(ifbuf, if_name)) {
-                               script_unhexlify((uint8_t*)&lladdr, sizeof(lladdr), addrbuf);
-                               break;
+       if (IN6_IS_ADDR_UNSPECIFIED(&lladdr)) {
+               // Autodetect interface-id if not specified
+               FILE *fp = fopen("/proc/net/if_inet6", "r");
+               if (fp) {
+                       char addrbuf[33], ifbuf[16];
+                       while (fscanf(fp, "%32s %*x %*x %*x %*x %15s", addrbuf, ifbuf) == 2) {
+                               if (!strcmp(ifbuf, if_name)) {
+                                       script_unhexlify((uint8_t*)&lladdr, sizeof(lladdr), addrbuf);
+                                       break;
+                               }
                        }
+                       fclose(fp);
                }
-               fclose(fp);
        }
 
        // Open rtnetlink socket
@@ -139,19 +158,24 @@ static bool ra_deduplicate(const struct in6_addr *any, uint8_t length)
 bool ra_rtnl_process(void)
 {
        bool found = false;
+       uint32_t elapsed = odhcp6c_elapsed();
        uint8_t buf[8192];
        while (true) {
                ssize_t len = recv(rtnl_sock, buf, sizeof(buf), MSG_DONTWAIT);
                if (len < 0)
                        break;
 
+               if (elapsed > 10)
+                       continue;
+
                for (struct nlmsghdr *nh = (struct nlmsghdr*)buf; NLMSG_OK(nh, (size_t)len);
                                        nh = NLMSG_NEXT(nh, len)) {
                        struct ifaddrmsg *ifa = NLMSG_DATA(nh);
                        struct in6_addr *addr = NULL;
                        if (NLMSG_PAYLOAD(nh, 0) < sizeof(*ifa) || ifa->ifa_index != if_index ||
                                        (nh->nlmsg_type == RTM_NEWADDR && !(ifa->ifa_flags & IFA_F_DADFAILED)) ||
-                                       (nh->nlmsg_type == RTM_DELADDR && !(ifa->ifa_flags & IFA_F_TENTATIVE)))
+                                       (nh->nlmsg_type == RTM_DELADDR && !(ifa->ifa_flags & IFA_F_TENTATIVE)) ||
+                                       (nh->nlmsg_type != RTM_NEWADDR && nh->nlmsg_type != RTM_DELADDR))
                                continue;
 
                        ssize_t alen = NLMSG_PAYLOAD(nh, sizeof(*ifa));
@@ -160,8 +184,13 @@ bool ra_rtnl_process(void)
                                if (rta->rta_type == IFA_ADDRESS && RTA_PAYLOAD(rta) >= sizeof(*addr))
                                        addr = RTA_DATA(rta);
 
-                       if (addr)
+                       if (addr) {
+                               char ipbuf[INET6_ADDRSTRLEN];
+                               inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
+                               syslog(LOG_WARNING, "duplicate address detected: %s (code: %u:%x)",
+                                               ipbuf, (unsigned)nh->nlmsg_type, (unsigned)ifa->ifa_flags);
                                found |= ra_deduplicate(addr, ifa->ifa_prefixlen);
+                       }
                }
        }
        return found;
@@ -175,7 +204,6 @@ bool ra_process(void)
        struct nd_router_advert *adv = (struct nd_router_advert*)buf;
        struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0, 0};
        const struct in6_addr any = IN6ADDR_ANY_INIT;
-       odhcp6c_expire();
 
        while (true) {
                struct sockaddr_in6 from;
@@ -192,10 +220,15 @@ bool ra_process(void)
                        rs_attempt = 0;
                }
 
-               found = true;
+               if (!found) {
+                       odhcp6c_expire();
+                       found = true;
+               }
                uint32_t router_valid = ntohs(adv->nd_ra_router_lifetime);
 
                // Parse default route
+               entry.target = any;
+               entry.length = 0;
                entry.router = from.sin6_addr;
                entry.priority = pref_to_priority(adv->nd_ra_flags_reserved);
                if (entry.priority < 0)
@@ -205,10 +238,10 @@ bool ra_process(void)
                odhcp6c_update_entry(STATE_RA_ROUTE, &entry);
 
                // Parse ND parameters
-               if (adv->nd_ra_reachable)
+               if (ntohl(adv->nd_ra_reachable) <= 3600000)
                        update_proc("neigh", "base_reachable_time_ms", ntohl(adv->nd_ra_reachable));
 
-               if (adv->nd_ra_retransmit)
+               if (ntohl(adv->nd_ra_retransmit) <= 60000)
                        update_proc("neigh", "retrans_time_ms", ntohl(adv->nd_ra_retransmit));
 
 
@@ -216,13 +249,16 @@ bool ra_process(void)
                struct icmpv6_opt *opt;
                icmpv6_for_each_option(opt, &adv[1], &buf[len]) {
                        if (opt->type == ND_OPT_MTU) {
-                               update_proc("conf", "mtu", ntohl(*((uint32_t*)&opt->data[2])));
+                               uint32_t *mtu = (uint32_t*)&opt->data[2];
+                               if (ntohl(*mtu) >= 1280 && ntohl(*mtu) <= 65535)
+                                       update_proc("conf", "mtu", ntohl(*mtu));
                        } else if (opt->type == ND_OPT_ROUTE_INFORMATION && opt->len <= 3) {
                                entry.router = from.sin6_addr;
                                entry.target = any;
                                entry.priority = pref_to_priority(opt->data[1]);
                                entry.length = opt->data[0];
-                               entry.valid = ntohl(*((uint32_t*)&opt->data[2]));
+                               uint32_t *valid = (uint32_t*)&opt->data[2];
+                               entry.valid = ntohl(*valid);
                                memcpy(&entry.target, &opt->data[6], (opt->len - 1) * 8);
 
                                if (entry.length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry.target)
@@ -262,7 +298,8 @@ bool ra_process(void)
                                entry.router = from.sin6_addr;
                                entry.priority = 0;
                                entry.length = 128;
-                               entry.valid = ntohl(*((uint32_t*)&opt->data[2]));
+                               uint32_t *valid = (uint32_t*)&opt->data[2];
+                               entry.valid = ntohl(*valid);
                                entry.preferred = 0;
 
                                for (ssize_t i = 0; i < (opt->len - 1) / 2; ++i) {
@@ -281,6 +318,8 @@ bool ra_process(void)
                                entry[i].valid = router_valid;
        }
 
-       odhcp6c_expire();
+       if (found)
+               odhcp6c_expire();
+
        return found;
 }
index 37d9573f707a6f48223ec24290e17b970a72bcc6..f66ff2672cd0353a6bea90e4313d0973ddcedaa6 100644 (file)
--- a/src/ra.h
+++ b/src/ra.h
@@ -1,3 +1,16 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
 #pragma once
 
 #define ALL_IPV6_NODES {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
@@ -21,6 +34,6 @@ struct icmpv6_opt {
        (void*)(opt + opt->len) <= (void*)(end); opt += opt->len)
 
 
-int ra_init(const char *ifname);
+int ra_init(const char *ifname, const struct in6_addr *ifid);
 bool ra_process(void);
 bool ra_rtnl_process(void);
index 7368080bc84c870a3db53647603d15f3fababa65..3cc7e2b47c663afacb4575acf3baa8147af69c32 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License v2 as published by
  */
 
 #include <stdio.h>
+#include <netdb.h>
 #include <resolv.h>
 #include <stdlib.h>
 #include <string.h>
 #include <syslog.h>
+#include <signal.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
@@ -38,6 +40,8 @@ static const int8_t hexvals[] = {
 
 
 static char *argv[4] = {NULL, NULL, NULL, NULL};
+static volatile char *delayed_call = NULL;
+static bool dont_delay = false;
 
 
 int script_init(const char *path, const char *ifname)
@@ -96,13 +100,14 @@ static void ipv6_to_env(const char *name,
 static void fqdn_to_env(const char *name, const uint8_t *fqdn, size_t len)
 {
        size_t buf_len = strlen(name);
+       size_t buf_size = len + buf_len + 2;
        const uint8_t *fqdn_end = fqdn + len;
        char *buf = realloc(NULL, len + buf_len + 2);
        memcpy(buf, name, buf_len);
        buf[buf_len++] = '=';
        int l = 1;
        while (l > 0 && fqdn < fqdn_end) {
-               l = dn_expand(fqdn, &fqdn[len], fqdn, &buf[buf_len], len);
+               l = dn_expand(fqdn, fqdn_end, fqdn, &buf[buf_len], buf_size - buf_len);
                fqdn += l;
                buf_len += strlen(&buf[buf_len]);
                buf[buf_len++] = ' ';
@@ -112,6 +117,29 @@ static void fqdn_to_env(const char *name, const uint8_t *fqdn, size_t len)
 }
 
 
+static void fqdn_to_ip_env(const char *name, const uint8_t *fqdn, size_t len)
+{
+       size_t buf_len = strlen(name);
+       char *buf = realloc(NULL, INET6_ADDRSTRLEN + buf_len + 3);
+       memcpy(buf, name, buf_len);
+       buf[buf_len++] = '=';
+
+       char namebuf[256];
+       if (dn_expand(fqdn, fqdn + len, fqdn, namebuf, sizeof(namebuf)) <= 0)
+               return;
+
+       struct addrinfo hints = {.ai_family = AF_INET6}, *r;
+       if (getaddrinfo(namebuf, NULL, &hints, &r))
+               return;
+
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)r->ai_addr;
+       inet_ntop(AF_INET6, &sin6->sin6_addr, &buf[buf_len], INET6_ADDRSTRLEN);
+
+       freeaddrinfo(r);
+       putenv(buf);
+}
+
+
 static void bin_to_env(uint8_t *opts, size_t len)
 {
        uint8_t *oend = opts + len, *odata;
@@ -128,8 +156,14 @@ static void bin_to_env(uint8_t *opts, size_t len)
        }
 }
 
+enum entry_type {
+       ENTRY_ADDRESS,
+       ENTRY_HOST,
+       ENTRY_ROUTE,
+       ENTRY_PREFIX
+};
 
-static void entry_to_env(const char *name, const void *data, size_t len, bool host, bool route)
+static void entry_to_env(const char *name, const void *data, size_t len, enum entry_type type)
 {
        size_t buf_len = strlen(name);
        const struct odhcp6c_entry *e = data;
@@ -140,9 +174,9 @@ static void entry_to_env(const char *name, const void *data, size_t len, bool ho
        for (size_t i = 0; i < len / sizeof(*e); ++i) {
                inet_ntop(AF_INET6, &e[i].target, &buf[buf_len], INET6_ADDRSTRLEN);
                buf_len += strlen(&buf[buf_len]);
-               if (!host) {
+               if (type != ENTRY_HOST) {
                        buf_len += snprintf(&buf[buf_len], 6, "/%hhu", e[i].length);
-                       if (route) {
+                       if (type == ENTRY_ROUTE) {
                                buf[buf_len++] = ',';
                                if (!IN6_IS_ADDR_UNSPECIFIED(&e[i].router)) {
                                        inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
@@ -152,8 +186,17 @@ static void entry_to_env(const char *name, const void *data, size_t len, bool ho
                                buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].priority);
                        } else {
                                buf_len += snprintf(&buf[buf_len], 24, ",%u,%u", e[i].preferred, e[i].valid);
-                                if (e[i].prefix_class)
-                                  buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].prefix_class);
+                       }
+                        if (type == ENTRY_PREFIX && e[i].prefix_class) {
+                          buf_len += snprintf(&buf[buf_len], 12, ",class=%u", e[i].prefix_class);
+                        }
+
+                       if (type == ENTRY_PREFIX && e[i].priority) {
+                               // priority and router are abused for prefix exclusion
+                               buf_len += snprintf(&buf[buf_len], 12, ",excluded=");
+                               inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
+                               buf_len += strlen(&buf[buf_len]);
+                               buf_len += snprintf(&buf[buf_len], 24, "/%u", e[i].priority);
                        }
                }
                buf[buf_len++] = ' ';
@@ -164,10 +207,35 @@ static void entry_to_env(const char *name, const void *data, size_t len, bool ho
 }
 
 
+static void script_call_delayed(int signal __attribute__((unused)))
+{
+       if (delayed_call)
+               script_call((char*)delayed_call);
+}
+
+
+void script_delay_call(const char *status, int timeout)
+{
+       if (dont_delay) {
+               script_call(status);
+       } else if (!delayed_call) {
+               delayed_call = strdup(status);
+               signal(SIGALRM, script_call_delayed);
+               alarm(timeout);
+       }
+}
+
+
 void script_call(const char *status)
 {
        size_t dns_len, search_len, custom_len, sntp_ip_len, sntp_dns_len;
-       size_t sip_ip_len, sip_fqdn_len;
+       size_t sip_ip_len, sip_fqdn_len, aftr_name_len;
+
+       odhcp6c_expire();
+       if (delayed_call) {
+               alarm(0);
+               dont_delay = true;
+       }
 
        struct in6_addr *dns = odhcp6c_get_state(STATE_DNS, &dns_len);
        uint8_t *search = odhcp6c_get_state(STATE_SEARCH, &search_len);
@@ -176,6 +244,7 @@ void script_call(const char *status)
        uint8_t *sntp_dns = odhcp6c_get_state(STATE_SNTP_FQDN, &sntp_dns_len);
        struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len);
        uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len);
+       uint8_t *aftr_name = odhcp6c_get_state(STATE_AFTR_NAME, &aftr_name_len);
 
        size_t prefix_len, address_len, ra_pref_len, ra_route_len, ra_dns_len;
        uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len);
@@ -192,12 +261,14 @@ void script_call(const char *status)
                fqdn_to_env("DOMAINS", search, search_len);
                fqdn_to_env("SNTP_FQDN", sntp_dns, sntp_dns_len);
                fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len);
+               fqdn_to_env("AFTR", aftr_name, aftr_name_len);
+               fqdn_to_ip_env("AFTR_IP", aftr_name, aftr_name_len);
                bin_to_env(custom, custom_len);
-               entry_to_env("PREFIXES", prefix, prefix_len, false, false);
-               entry_to_env("ADDRESSES", address, address_len, false, false);
-               entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, false, false);
-               entry_to_env("RA_ROUTES", ra_route, ra_route_len, false, true);
-               entry_to_env("RA_DNS", ra_dns, ra_dns_len, true, false);
+               entry_to_env("PREFIXES", prefix, prefix_len, ENTRY_PREFIX);
+               entry_to_env("ADDRESSES", address, address_len, ENTRY_ADDRESS);
+               entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS);
+               entry_to_env("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE);
+               entry_to_env("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST);
 
                argv[2] = (char*)status;
                execv(argv[0], argv);