dhcpv6: use PRIu64 print macro
[oweals/odhcp6c.git] / src / dhcpv6.c
index cbf6991f31a0b73f841301183104d70575e7104b..30bfa50339b026167d3b26f84b0ff63d6cc6940c 100644 (file)
@@ -1,5 +1,6 @@
 /**
  * Copyright (C) 2012-2014 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2017 Hans Dedecker <dedeckeh@gmail.com>
  *
  * 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 +16,7 @@
 #include <time.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <stdlib.h>
 #include <signal.h>
 #include <limits.h>
 #include <sys/time.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <arpa/inet.h>
 #include <netinet/in.h>
 
 #include <net/if.h>
 #include <net/ethernet.h>
 
 #include "odhcp6c.h"
+#ifdef LIBUBOX
+#include <libubox/md5.h>
+#else
 #include "md5.h"
+#endif
 
 
 #define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
@@ -102,6 +109,8 @@ static int64_t t1 = 0, t2 = 0, t3 = 0;
 static int request_prefix = -1;
 static enum odhcp6c_ia_mode na_mode = IA_MODE_NONE, pd_mode = IA_MODE_NONE;
 static bool accept_reconfig = false;
+// Server unicast address
+static struct in6_addr server_addr = IN6ADDR_ANY_INIT;
 
 // Reconfigure key
 static uint8_t reconf_key[16];
@@ -176,6 +185,7 @@ int init_dhcpv6(const char *ifname, unsigned int options, int sol_timeout)
                        htons(DHCPV6_OPT_SIP_SERVER_A),
                        htons(DHCPV6_OPT_DNS_SERVERS),
                        htons(DHCPV6_OPT_DNS_DOMAIN),
+                       htons(DHCPV6_OPT_UNICAST),
                        htons(DHCPV6_OPT_SNTP_SERVERS),
                        htons(DHCPV6_OPT_NTP_SERVER),
                        htons(DHCPV6_OPT_AFTR_NAME),
@@ -497,7 +507,29 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        struct msghdr msg = {.msg_name = &srv, .msg_namelen = sizeof(srv),
                        .msg_iov = iov, .msg_iovlen = cnt};
 
-       sendmsg(sock, &msg, 0);
+       switch (type) {
+       case DHCPV6_MSG_REQUEST:
+       case DHCPV6_MSG_RENEW:
+       case DHCPV6_MSG_RELEASE:
+       case DHCPV6_MSG_DECLINE:
+               if (!IN6_IS_ADDR_UNSPECIFIED(&server_addr) &&
+                       odhcp6c_addr_in_scope(&server_addr)) {
+                       srv.sin6_addr = server_addr;
+                       if (!IN6_IS_ADDR_LINKLOCAL(&server_addr))
+                               srv.sin6_scope_id = 0;
+               }
+               break;
+       default:
+               break;
+       }
+
+       if (sendmsg(sock, &msg, 0) < 0) {
+               char in6_str[INET6_ADDRSTRLEN];
+
+               syslog(LOG_ERR, "Failed to send DHCPV6 message to %s (%s)",
+                       inet_ntop(AF_INET6, (const void *)&srv.sin6_addr,
+                               in6_str, sizeof(in6_str)), strerror(errno));
+       }
 }
 
 
@@ -531,8 +563,8 @@ int dhcpv6_request(enum dhcpv6_msg type)
        if (timeout == 0)
                return -1;
 
-       syslog(LOG_NOTICE, "Starting %s transaction (timeout %llus, max rc %d)",
-                       retx->name, (unsigned long long)timeout, retx->max_rc);
+       syslog(LOG_NOTICE, "Starting %s transaction (timeout %"PRIu64"s, max rc %d)",
+                       retx->name, timeout, retx->max_rc);
 
        uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
 
@@ -573,8 +605,8 @@ int dhcpv6_request(enum dhcpv6_msg type)
                case DHCPV6_MSG_UNKNOWN:
                        break;
                default:
-                       syslog(LOG_NOTICE, "Send %s message (elapsed %llums, rc %d)",
-                                       retx->name, (unsigned long long)elapsed, rc);
+                       syslog(LOG_NOTICE, "Send %s message (elapsed %"PRIu64"ms, rc %d)",
+                                       retx->name, elapsed, rc);
                        // Fall through
                case DHCPV6_MSG_SOLICIT:
                case DHCPV6_MSG_INFO_REQ:
@@ -586,12 +618,14 @@ int dhcpv6_request(enum dhcpv6_msg type)
                for (; len < 0 && (round_start < round_end);
                                round_start = odhcp6c_get_milli_time()) {
                        uint8_t buf[1536];
-                       uint8_t cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]
-                               __aligned(__alignof__(struct cmsghdr));
+                       union {
+                               struct cmsghdr hdr;
+                               uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+                       } cmsg_buf;
                        struct iovec iov = {buf, sizeof(buf)};
                        struct sockaddr_in6 addr;
                        struct msghdr msg = {.msg_name = &addr, .msg_namelen = sizeof(addr),
-                                       .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf,
+                                       .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf.buf,
                                        .msg_controllen = sizeof(cmsg_buf)};
                        struct in6_pktinfo *pktinfo = NULL;
 
@@ -636,8 +670,8 @@ int dhcpv6_request(enum dhcpv6_msg type)
 
                        round_start = odhcp6c_get_milli_time();
                        elapsed = round_start - start;
-                       syslog(LOG_NOTICE, "Got a valid reply after "
-                                       "%llums", (unsigned long long)elapsed);
+                       syslog(LOG_NOTICE, "Got a valid reply after %"PRIu64"ms",
+                                       elapsed);
 
                        if (retx->handler_reply)
                                len = retx->handler_reply(type, rc, opt, opt_end, &addr);
@@ -649,7 +683,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
                // Allow
                if (retx->handler_finish)
                        len = retx->handler_finish();
-       } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) && 
+       } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) &&
                        (!retx->max_rc || rc < retx->max_rc));
        return len;
 }
@@ -702,7 +736,8 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                                continue;
 
                        md5_ctx_t md5;
-                       uint8_t serverhash[16], secretbytes[64], hash[16];
+                       uint8_t serverhash[16], secretbytes[64];
+                       uint32_t hash[4];
                        memcpy(serverhash, r->key, sizeof(serverhash));
                        memset(r->key, 0, sizeof(r->key));
 
@@ -792,7 +827,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
        uint16_t olen, otype;
        uint8_t *odata, pref = 0;
        struct dhcpv6_server_cand cand = {false, false, 0, 0, {0},
-                                       DHCPV6_SOL_MAX_RT,
+                                       IN6ADDR_ANY_INIT, DHCPV6_SOL_MAX_RT,
                                        DHCPV6_INF_MAX_RT, NULL, NULL, 0, 0};
        bool have_na = false;
        int have_pd = 0;
@@ -811,6 +846,8 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
                } else if (otype == DHCPV6_OPT_PREF && olen >= 1 &&
                                cand.preference >= 0) {
                        cand.preference = pref = odata[0];
+               } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(cand.server_addr)) {
+                       cand.server_addr = *(struct in6_addr *)odata;
                } else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
                        cand.wants_reconfigure = true;
                } else if (otype == DHCPV6_OPT_SOL_MAX_RT && olen == 4) {
@@ -999,7 +1036,10 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                                if (code != DHCPV6_Success)
                                        continue;
 
-                               dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
+                               dhcpv6_parse_ia(ia_hdr, odata + olen);
+                               passthru = false;
+                       } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(server_addr)) {
+                               server_addr = *(struct in6_addr *)odata;
                                passthru = false;
                        } else if (otype == DHCPV6_OPT_STATUS && olen >= 2) {
                                uint8_t *mdata = (olen > 2) ? &odata[2] : NULL;
@@ -1008,8 +1048,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
 
                                dhcpv6_handle_status_code(orig, code, mdata, mlen, &ret);
                                passthru = false;
-                       }
-                       else if (otype == DHCPV6_OPT_DNS_SERVERS) {
+                       } else if (otype == DHCPV6_OPT_DNS_SERVERS) {
                                if (olen % 16 == 0)
                                        odhcp6c_add_state(STATE_DNS, odata, olen);
                        } else if (otype == DHCPV6_OPT_DNS_DOMAIN) {
@@ -1105,7 +1144,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
 
        if (orig != DHCPV6_MSG_INFO_REQ) {
                // Update refresh timers if no fatal status code was received
-               if ((ret > 0) && dhcpv6_calc_refresh_timers()) {
+               if ((ret > 0) && (ret = dhcpv6_calc_refresh_timers())) {
                        switch (orig) {
                        case DHCPV6_MSG_RENEW:
                                // Send further renews if T1 is not set
@@ -1294,8 +1333,6 @@ static int dhcpv6_calc_refresh_timers(void)
                t1 = l_t1;
                t2 = l_t2;
                t3 = l_t3;
-       } else {
-               t1 = 600;
        }
 
        return (int)(ia_pd_entries + ia_na_entries);
@@ -1338,7 +1375,18 @@ static void dhcpv6_handle_status_code(const enum dhcpv6_msg orig,
                break;
 
        case DHCPV6_UseMulticast:
-               // TODO handle multicast status code
+               switch(orig) {
+               case DHCPV6_MSG_REQUEST:
+               case DHCPV6_MSG_RENEW:
+               case DHCPV6_MSG_RELEASE:
+               case DHCPV6_MSG_DECLINE:
+                       // Message needs to be retransmitted according to RFC3315 chapter 18.1.8
+                       server_addr = in6addr_any;
+                       *ret = 0;
+                       break;
+               default:
+                       break;
+               }
                break;
 
        case DHCPV6_NoAddrsAvail: