dhcpv6: check message type
[project/odhcpd.git] / src / dhcpv6.c
index a40c35361d6055bcc3fb352a8b98ae843342837a..7c6c7abe89f1de089c036d4fa030c6e72bad6253 100644 (file)
@@ -47,14 +47,16 @@ int dhcpv6_setup_interface(struct interface *iface, bool enable)
 {
        int ret = 0;
 
-       if (iface->dhcpv6_event.uloop.fd > 0) {
+       enable = enable && (iface->dhcpv6 != MODE_DISABLED);
+
+       if (iface->dhcpv6_event.uloop.fd >= 0) {
                uloop_fd_delete(&iface->dhcpv6_event.uloop);
                close(iface->dhcpv6_event.uloop.fd);
                iface->dhcpv6_event.uloop.fd = -1;
        }
 
        /* Configure multicast settings */
-       if (enable && iface->dhcpv6) {
+       if (enable) {
                struct sockaddr_in6 bind_addr = {AF_INET6, htons(DHCPV6_SERVER_PORT),
                                        0, IN6ADDR_ANY_INIT, 0};
                struct ipv6_mreq mreq;
@@ -150,7 +152,7 @@ int dhcpv6_setup_interface(struct interface *iface, bool enable)
        ret = dhcpv6_ia_setup_interface(iface, enable);
 
 out:
-       if (ret < 0 && iface->dhcpv6_event.uloop.fd > 0) {
+       if (ret < 0 && iface->dhcpv6_event.uloop.fd >= 0) {
                close(iface->dhcpv6_event.uloop.fd);
                iface->dhcpv6_event.uloop.fd = -1;
        }
@@ -163,6 +165,7 @@ enum {
        IOV_DEST,
        IOV_MAXRT,
 #define IOV_STAT IOV_MAXRT
+       IOV_RAPID_COMMIT,
        IOV_DNS,
        IOV_DNS_ADDR,
        IOV_SEARCH,
@@ -237,11 +240,13 @@ static void handle_client_request(void *addr, void *data, size_t len,
                struct interface *iface, void *dest_addr)
 {
        struct dhcpv6_client_header *hdr = data;
+       uint8_t *opts = (uint8_t *)&hdr[1], *opts_end = (uint8_t *)data + len;
+       bool o_rapid_commit = false;
 
        if (len < sizeof(*hdr))
                return;
 
-       syslog(LOG_NOTICE, "Got DHCPv6 request on %s", iface->name);
+       syslog(LOG_DEBUG, "Got a DHCPv6-request on %s", iface->name);
 
        /* Construct reply message */
        struct __attribute__((packed)) {
@@ -273,6 +278,11 @@ static void handle_client_request(void *addr, void *data, size_t len,
        } maxrt = {htons(DHCPV6_OPT_SOL_MAX_RT), htons(sizeof(maxrt) - 4),
                        htonl(60)};
 
+       struct __attribute__((packed)) {
+               uint16_t type;
+               uint16_t len;
+       } rapid_commit = {htons(DHCPV6_OPT_RAPID_COMMIT), 0};
+
        struct __attribute__((packed)) {
                uint16_t type;
                uint16_t len;
@@ -336,6 +346,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
                [IOV_NESTED] = {NULL, 0},
                [IOV_DEST] = {&dest, (uint8_t*)&dest.clientid_type - (uint8_t*)&dest},
                [IOV_MAXRT] = {&maxrt, sizeof(maxrt)},
+               [IOV_RAPID_COMMIT] = {&rapid_commit, 0},
                [IOV_DNS] = {&dns, (dns_cnt) ? sizeof(dns) : 0},
                [IOV_DNS_ADDR] = {dns_addr_ptr, dns_cnt * sizeof(*dns_addr_ptr)},
                [IOV_SEARCH] = {&search, (search_len) ? sizeof(search) : 0},
@@ -346,30 +357,34 @@ static void handle_client_request(void *addr, void *data, size_t len,
                [IOV_RELAY_MSG] = {NULL, 0}
        };
 
-       uint8_t *opts = (uint8_t*)&hdr[1], *opts_end = (uint8_t*)data + len;
        if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
                handle_nested_message(data, len, &hdr, &opts, &opts_end, iov);
 
-       memcpy(dest.tr_id, hdr->transaction_id, sizeof(dest.tr_id));
-
-       if (hdr->msg_type == DHCPV6_MSG_ADVERTISE || hdr->msg_type == DHCPV6_MSG_REPLY ||
-           hdr->msg_type == DHCPV6_MSG_RELAY_REPL)
-               return;
+       switch (hdr->msg_type) {
+       case DHCPV6_MSG_SOLICIT:
+       case DHCPV6_MSG_REQUEST:
+       case DHCPV6_MSG_CONFIRM:
+       case DHCPV6_MSG_RENEW:
+       case DHCPV6_MSG_REBIND:
+       case DHCPV6_MSG_RELEASE:
+       case DHCPV6_MSG_DECLINE:
+       case DHCPV6_MSG_INFORMATION_REQUEST:
+       case DHCPV6_MSG_RELAY_FORW:
+               break; /* Valid message types for clients */
+       case DHCPV6_MSG_ADVERTISE:
+       case DHCPV6_MSG_REPLY:
+       case DHCPV6_MSG_RECONFIGURE:
+       case DHCPV6_MSG_RELAY_REPL:
+       default:
+               return; /* Invalid message types for clients */
+       }
 
        if (!IN6_IS_ADDR_MULTICAST((struct in6_addr *)dest_addr) && iov[IOV_NESTED].iov_len == 0 &&
-               (hdr->msg_type == DHCPV6_MSG_SOLICIT || hdr->msg_type == DHCPV6_MSG_CONFIRM ||
-                hdr->msg_type == DHCPV6_MSG_REBIND || hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST))
+           (hdr->msg_type == DHCPV6_MSG_SOLICIT || hdr->msg_type == DHCPV6_MSG_CONFIRM ||
+            hdr->msg_type == DHCPV6_MSG_REBIND || hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST))
                return;
 
-       if (hdr->msg_type == DHCPV6_MSG_SOLICIT) {
-               dest.msg_type = DHCPV6_MSG_ADVERTISE;
-       } else if (hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST) {
-               iov[IOV_REFRESH].iov_base = &refresh;
-               iov[IOV_REFRESH].iov_len = sizeof(refresh);
-
-               /* Return inf max rt option in reply to information request */
-               maxrt.type = htons(DHCPV6_OPT_INF_MAX_RT);
-       }
+       memcpy(dest.tr_id, hdr->transaction_id, sizeof(dest.tr_id));
 
        /* Go through options and find what we need */
        uint16_t otype, olen;
@@ -406,6 +421,9 @@ static void handle_client_request(void *addr, void *data, size_t len,
                                free(addrs);
                        }
 #endif
+               } else if (otype == DHCPV6_OPT_RAPID_COMMIT && hdr->msg_type == DHCPV6_MSG_SOLICIT) {
+                       iov[IOV_RAPID_COMMIT].iov_len = sizeof(rapid_commit);
+                       o_rapid_commit = true;
                }
        }
 
@@ -422,6 +440,16 @@ static void handle_client_request(void *addr, void *data, size_t len,
                return;
        }
 
+       if (hdr->msg_type == DHCPV6_MSG_SOLICIT && !o_rapid_commit) {
+               dest.msg_type = DHCPV6_MSG_ADVERTISE;
+       } else if (hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST) {
+               iov[IOV_REFRESH].iov_base = &refresh;
+               iov[IOV_REFRESH].iov_len = sizeof(refresh);
+
+               /* Return inf max rt option in reply to information request */
+               maxrt.type = htons(DHCPV6_OPT_INF_MAX_RT);
+       }
+
        if (hdr->msg_type != DHCPV6_MSG_INFORMATION_REQUEST) {
                ssize_t ialen = dhcpv6_ia_handle_IAs(pdbuf, sizeof(pdbuf), iface, addr, (const void *)hdr, opts_end);
 
@@ -433,10 +461,13 @@ static void handle_client_request(void *addr, void *data, size_t len,
 
        if (iov[IOV_NESTED].iov_len > 0) /* Update length */
                update_nested_message(data, len, iov[IOV_DEST].iov_len + iov[IOV_MAXRT].iov_len +
-                               iov[IOV_DNS].iov_len + iov[IOV_DNS_ADDR].iov_len +
-                               iov[IOV_SEARCH].iov_len + iov[IOV_SEARCH_DOMAIN].iov_len +
-                               iov[IOV_PDBUF].iov_len + iov[IOV_CERID].iov_len +
-                               iov[IOV_DHCPV6_RAW].iov_len - (4 + opts_end - opts));
+                                     iov[IOV_RAPID_COMMIT].iov_len + iov[IOV_DNS].iov_len +
+                                     iov[IOV_DNS_ADDR].iov_len + iov[IOV_SEARCH].iov_len +
+                                     iov[IOV_SEARCH_DOMAIN].iov_len + iov[IOV_PDBUF].iov_len +
+                                     iov[IOV_CERID].iov_len + iov[IOV_DHCPV6_RAW].iov_len -
+                                     (4 + opts_end - opts));
+
+       syslog(LOG_DEBUG, "Sending a DHCPv6-%s on %s", iov[IOV_NESTED].iov_len ? "relay-reply" : "reply", iface->name);
 
        odhcpd_send(iface->dhcpv6_event.uloop.fd, addr, iov, ARRAY_SIZE(iov), iface);
 }
@@ -466,19 +497,17 @@ static void relay_server_response(uint8_t *data, size_t len)
        int32_t ifaceidx = 0;
        struct sockaddr_in6 target = {AF_INET6, htons(DHCPV6_CLIENT_PORT),
                0, IN6ADDR_ANY_INIT, 0};
-
-       syslog(LOG_NOTICE, "Got a DHCPv6-reply");
-
        int otype, olen;
        uint8_t *odata, *end = data + len;
-
        /* Relay DHCPv6 reply from server to client */
        struct dhcpv6_relay_header *h = (void*)data;
+
+       syslog(LOG_DEBUG, "Got a DHCPv6-relay-reply");
+
        if (len < sizeof(*h) || h->msg_type != DHCPV6_MSG_RELAY_REPL)
                return;
 
-       memcpy(&target.sin6_addr, &h->peer_address,
-                       sizeof(struct in6_addr));
+       memcpy(&target.sin6_addr, &h->peer_address, sizeof(struct in6_addr));
 
        /* Go through options and find what we need */
        dhcpv6_for_each_option(h->options, end, otype, olen, odata) {
@@ -528,7 +557,7 @@ static void relay_server_response(uint8_t *data, size_t len)
 
                if (rewrite_cnt == 0) {
                        if (odhcpd_get_interface_dns_addr(iface, &addr))
-                               return; // Unable to get interface address
+                               return; /* Unable to get interface address */
 
                        rewrite = &addr;
                        rewrite_cnt = 1;
@@ -542,6 +571,9 @@ static void relay_server_response(uint8_t *data, size_t len)
        }
 
        struct iovec iov = {payload_data, payload_len};
+
+       syslog(LOG_DEBUG, "Sending a DHCPv6-reply on %s", iface->name);
+
        odhcpd_send(iface->dhcpv6_event.uloop.fd, &target, &iov, 1, iface);
 }
 
@@ -570,19 +602,7 @@ static struct odhcpd_ipaddr *relay_link_address(struct interface *iface)
 static void relay_client_request(struct sockaddr_in6 *source,
                const void *data, size_t len, struct interface *iface)
 {
-       struct interface *master = odhcpd_get_master_interface();
        const struct dhcpv6_relay_header *h = data;
-       struct sockaddr_in6 s;
-
-       if (!master || master->dhcpv6 != MODE_RELAY ||
-                       h->msg_type == DHCPV6_MSG_RELAY_REPL ||
-                       h->msg_type == DHCPV6_MSG_RECONFIGURE ||
-                       h->msg_type == DHCPV6_MSG_REPLY ||
-                       h->msg_type == DHCPV6_MSG_ADVERTISE)
-               return; /* Invalid message types for client */
-
-       syslog(LOG_NOTICE, "Got a DHCPv6-request");
-
        /* Construct our forwarding envelope */
        struct dhcpv6_relay_forward_envelope hdr = {
                .msg_type = DHCPV6_MSG_RELAY_FORW,
@@ -592,39 +612,60 @@ static void relay_client_request(struct sockaddr_in6 *source,
                .relay_message_type = htons(DHCPV6_OPT_RELAY_MSG),
                .relay_message_len = htons(len),
        };
+       struct iovec iov[2] = {{&hdr, sizeof(hdr)}, {(void *)data, len}};
+       struct interface *c;
+       struct odhcpd_ipaddr *ip;
+       struct sockaddr_in6 s;
+
+       if (h->msg_type == DHCPV6_MSG_RELAY_REPL ||
+           h->msg_type == DHCPV6_MSG_RECONFIGURE ||
+           h->msg_type == DHCPV6_MSG_REPLY ||
+           h->msg_type == DHCPV6_MSG_ADVERTISE)
+               return; /* Invalid message types for client */
+
+       syslog(LOG_DEBUG, "Got a DHCPv6-request on %s", iface->name);
 
        if (h->msg_type == DHCPV6_MSG_RELAY_FORW) { /* handle relay-forward */
                if (h->hop_count >= DHCPV6_HOP_COUNT_LIMIT)
-                       return; // Invalid hop count
-               else
-                       hdr.hop_count = h->hop_count + 1;
+                       return; /* Invalid hop count */
+
+               hdr.hop_count = h->hop_count + 1;
        }
 
        /* use memcpy here as the destination fields are unaligned */
-       uint32_t ifindex = iface->ifindex;
        memcpy(&hdr.peer_address, &source->sin6_addr, sizeof(struct in6_addr));
-       memcpy(&hdr.interface_id_data, &ifindex, sizeof(ifindex));
+       memcpy(&hdr.interface_id_data, &iface->ifindex, sizeof(iface->ifindex));
 
        /* Detect public IP of slave interface to use as link-address */
-       struct odhcpd_ipaddr *ip = relay_link_address(iface);
-       if (!ip) {
-               /* No suitable address! Is the slave not configured yet?
-                * Detect public IP of master interface and use it instead
-                * This is WRONG and probably violates the RFC. However
-                * otherwise we have a hen and egg problem because the
-                * slave-interface cannot be auto-configured. */
-               ip = relay_link_address(master);
-               if (!ip)
-                       return; /* Could not obtain a suitable address */
-       }
-
-       memcpy(&hdr.link_address, &ip->addr.in6, sizeof(hdr.link_address));
+       ip = relay_link_address(iface);
+       if (ip)
+               memcpy(&hdr.link_address, &ip->addr.in6, sizeof(hdr.link_address));
 
        memset(&s, 0, sizeof(s));
        s.sin6_family = AF_INET6;
        s.sin6_port = htons(DHCPV6_SERVER_PORT);
        inet_pton(AF_INET6, ALL_DHCPV6_SERVERS, &s.sin6_addr);
 
-       struct iovec iov[2] = {{&hdr, sizeof(hdr)}, {(void*)data, len}};
-       odhcpd_send(master->dhcpv6_event.uloop.fd, &s, iov, 2, master);
+       avl_for_each_element(&interfaces, c, avl) {
+               if (!c->master || c->dhcpv6 != MODE_RELAY)
+                       continue;
+
+               if (!ip) {
+                       /* No suitable address! Is the slave not configured yet?
+                        * Detect public IP of master interface and use it instead
+                        * This is WRONG and probably violates the RFC. However
+                        * otherwise we have a hen and egg problem because the
+                        * slave-interface cannot be auto-configured. */
+                       ip = relay_link_address(c);
+                       if (!ip)
+                               continue; /* Could not obtain a suitable address */
+
+                       memcpy(&hdr.link_address, &ip->addr.in6, sizeof(hdr.link_address));
+                       ip = NULL;
+               }
+
+               syslog(LOG_DEBUG, "Sending a DHCPv6-relay-forward on %s", c->name);
+
+               odhcpd_send(c->dhcpv6_event.uloop.fd, &s, iov, 2, c);
+       }
 }