Fix packet validation in DHCPv6.
authorSimon Kelley <simon@thekelleys.org.uk>
Tue, 22 Jul 2025 19:43:52 +0000 (20:43 +0100)
committerSimon Kelley <simon@thekelleys.org.uk>
Tue, 22 Jul 2025 19:43:52 +0000 (20:43 +0100)
Some messages must be sent as multicast.

src/dhcp6.c
src/dnsmasq.h
src/rfc3315.c

index 06285d6..ae19d0e 100644 (file)
@@ -176,6 +176,7 @@ void dhcp6_packet(time_t now)
   else
     {
       struct dhcp_bridge *bridge, *alias;
+      int multicast_dest = 0;
       
       for (tmp = daemon->if_except; tmp; tmp = tmp->next)
        if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
@@ -231,13 +232,20 @@ void dhcp6_packet(time_t now)
            memset(&context->local6, 0, IN6ADDRSZ);
          }
       
-      /* Ignore requests sent to the ALL_SERVERS multicast address for relay when
-        we're listening there for DHCPv6 server reasons. */
-      inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
+      inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &all_servers);
+      if (IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
+       multicast_dest = 1;
       
-      if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) &&
-         relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
-       return;
+      inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
+      if (IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
+       multicast_dest = 1;
+      else
+       {
+         /* Ignore requests sent to the ALL_SERVERS multicast address for relay when
+            we're listening there for DHCPv6 server reasons. */
+         if (relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
+           return;
+       }
       
       if (!iface_enumerate(AF_INET6, &parm, (callback_t){.af_inet6=complete_context6}))
        return;
@@ -266,7 +274,7 @@ void dhcp6_packet(time_t now)
       
       lease_prune(NULL, now); /* lose any expired leases */
       
-      port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, 
+      port = dhcp6_reply(parm.current, multicast_dest, if_index, ifr.ifr_name, &parm.fallback, 
                         &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now);
       
       /* The port in the source address of the original request should
index d1abed6..d0faf06 100644 (file)
@@ -1801,7 +1801,7 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
   
 /* rfc3315.c */
 #ifdef HAVE_DHCP6
-unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,  
+unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int interface, char *iface_name,  
                           struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
                           size_t sz, struct in6_addr *client_addr, time_t now);
 int relay_upstream6(int iface_index, ssize_t sz, struct in6_addr *peer_address, 
index 701bd9a..4e7a012 100644 (file)
@@ -21,7 +21,7 @@
 
 struct state {
   unsigned char *clid;
-  int clid_len, ia_type, interface, hostname_auth, lease_allocate;
+  int multicast_dest, clid_len, ia_type, interface, hostname_auth, lease_allocate;
   char *client_hostname, *hostname, *domain, *send_domain;
   struct dhcp_context *context;
   struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
@@ -68,7 +68,7 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
 #define opt6_user_vendor_next(opt, end) (opt6_next(((uint8_t *) opt) - 2, end))
  
 
-unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
+unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int interface, char *iface_name,
                           struct in6_addr *fallback,  struct in6_addr *ll_addr, struct in6_addr *ula_addr,
                           size_t sz, struct in6_addr *client_addr, time_t now)
 {
@@ -87,6 +87,7 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
   
   reset_counter();
   state.context = context;
+  state.multicast_dest = multicast_dest;
   state.interface = interface;
   state.iface_name = iface_name;
   state.fallback = fallback;
@@ -333,20 +334,24 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
   else if (msg_type != DHCP6IREQ)
     return 0;
 
-  /* server-id must match except for SOLICIT, CONFIRM and REBIND messages, which MUST NOT
-     have a server-id.  3315 para 15.x */
   opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1);
-
-  if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND)
+  
+  if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND || msg_type == DHCP6IREQ)
     {
-      if (opt)
+      /* Above message types must be multicast 3315 Section 15. */
+      if (!state->multicast_dest)
        return 0;
-    }
-  else if (msg_type == DHCP6IREQ)
-    {
-      /* If server-id provided, it must match. */
-      if (opt && (opt6_len(opt) != daemon->duid_len ||
-                 memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
+
+      /* server-id must match except for SOLICIT, CONFIRM and REBIND messages, which MUST NOT
+        have a server-id.  3315 para 15.x */
+      if (msg_type == DHCP6IREQ)
+       {
+         /* If server-id provided in IREQ, it must match. */
+         if (opt && (opt6_len(opt) != daemon->duid_len ||
+                     memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
+           return 0;
+       }
+      else if (opt) 
        return 0;
     }
   else