First stab at IPv6-only preferred from RFC8925.
authorflorian <florian@openbsd.org>
Sat, 25 Nov 2023 12:00:39 +0000 (12:00 +0000)
committerflorian <florian@openbsd.org>
Sat, 25 Nov 2023 12:00:39 +0000 (12:00 +0000)
This lets dhcpleased(8) request "IPv6-only preferred". If the
server replies with this option dhcpleased stops and does not request
a lease and deconfigures IPv4 on the interface.

For now this is pretty much useless unless one dynamically configures
pf(4) to act as a CLAT. gelatod(8) from ports can help with this.

However, this helps me while hacking on a kernel based stateless CLAT
by moving dhcpleased out of the way while having an IPv6-mostly
network configured to compare behaviour with macOS.

Input jmc
OK phessler
Input & OK sthen

sbin/dhcpleased/dhcpleased.conf.5
sbin/dhcpleased/dhcpleased.h
sbin/dhcpleased/engine.c
sbin/dhcpleased/frontend.c
sbin/dhcpleased/parse.y
sbin/dhcpleased/printconf.c

index 4d3eb86..27798d7 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: dhcpleased.conf.5,v 1.12 2023/03/02 17:09:52 jmc Exp $
+.\"    $OpenBSD: dhcpleased.conf.5,v 1.13 2023/11/25 12:00:39 florian Exp $
 .\"
 .\" Copyright (c) 2018, 2021 Florian Obser <florian@openbsd.org>
 .\" Copyright (c) 2005 Esben Norby <norby@openbsd.org>
@@ -18,7 +18,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: March 2 2023 $
+.Dd $Mdocdate: November 25 2023 $
 .Dt DHCPLEASED.CONF 5
 .Os
 .Sh NAME
@@ -68,6 +68,9 @@ Ignore leases from
 .Ar server-ip .
 This option can be listed multiple times.
 The default is to not ignore servers.
+.It Ic prefer ipv6
+Send the IPv6-Only preferred option to the server.
+If the server responds with the option, no lease is configured.
 .It Ic send client id Ar client-id
 Send the DHCP client identifier option with a value of
 .Ar client-id .
index c5d7e20..80fe9dc 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dhcpleased.h,v 1.14 2022/03/21 04:35:41 dlg Exp $     */
+/*     $OpenBSD: dhcpleased.h,v 1.15 2023/11/25 12:00:39 florian Exp $ */
 
 /*
  * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
 #define        DHO_NDS_SERVERS                 85
 #define        DHO_NDS_TREE_NAME               86
 #define        DHO_NDS_CONTEXT                 87
+#define        DHO_IPV6_ONLY_PREFERRED         108
 #define        DHO_DOMAIN_SEARCH               119
 #define        DHO_CLASSLESS_STATIC_ROUTES     121
 #define        DHO_TFTP_CONFIG_FILE            144
@@ -258,6 +259,7 @@ struct iface_conf {
        int                              ignore;
        struct in_addr                   ignore_servers[MAX_SERVERS];
        int                              ignore_servers_len;
+       int                              prefer_ipv6;
 };
 
 struct dhcpleased_conf {
index b2e34ef..d435850 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: engine.c,v 1.39 2023/11/03 15:02:06 tb Exp $  */
+/*     $OpenBSD: engine.c,v 1.40 2023/11/25 12:00:39 florian Exp $     */
 
 /*
  * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
@@ -70,6 +70,7 @@ enum if_state {
        IF_REBINDING,
        /* IF_INIT_REBOOT, */
        IF_REBOOTING,
+       IF_IPV6_ONLY,
 };
 
 const char* if_state_name[] = {
@@ -82,6 +83,7 @@ const char* if_state_name[] = {
        "Rebinding",
        /* "Init-Reboot", */
        "Rebooting",
+       "IPv6 only",
 };
 
 struct dhcpleased_iface {
@@ -113,6 +115,7 @@ struct dhcpleased_iface {
        uint32_t                         lease_time;
        uint32_t                         renewal_time;
        uint32_t                         rebinding_time;
+       uint32_t                         ipv6_only_time;
 };
 
 LIST_HEAD(, dhcpleased_iface) dhcpleased_interfaces;
@@ -339,6 +342,7 @@ engine_dispatch_frontend(int fd, short event, void *bula)
                                case IF_REBINDING:
                                case IF_REBOOTING:
                                case IF_BOUND:
+                               case IF_IPV6_ONLY:
                                        state_transition(iface, IF_REBOOTING);
                                        break;
                                }
@@ -727,6 +731,7 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
        size_t                   rem, i;
        uint32_t                 sum, usum, lease_time = 0, renewal_time = 0;
        uint32_t                 rebinding_time = 0;
+       uint32_t                 ipv6_only_time = 0;
        uint8_t                 *p, dho = DHO_PAD, dho_len, slen;
        uint8_t                  dhcp_message_type = 0;
        int                      routes_len = 0, routers = 0, csr = 0;
@@ -1173,6 +1178,18 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
                        }
                        break;
                }
+               case DHO_IPV6_ONLY_PREFERRED:
+                       if (dho_len != sizeof(ipv6_only_time))
+                               goto wrong_length;
+                       memcpy(&ipv6_only_time, p, sizeof(ipv6_only_time));
+                       ipv6_only_time = ntohl(ipv6_only_time);
+                       if (log_getverbose() > 1) {
+                               log_debug("DHO_IPV6_ONLY_PREFERRED %us",
+                                   ipv6_only_time);
+                       }
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
                default:
                        if (log_getverbose() > 1)
                                log_debug("DHO_%u, len: %u", dho, dho_len);
@@ -1207,6 +1224,14 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
                            "offered IP address", __func__);
                        return;
                }
+#ifndef SMALL
+               if (iface_conf != NULL && iface_conf->prefer_ipv6 &&
+                   ipv6_only_time > 0) {
+                       iface->ipv6_only_time = ipv6_only_time;
+                       state_transition(iface, IF_IPV6_ONLY);
+                       break;
+               }
+#endif
                iface->server_identifier = server_identifier;
                iface->dhcp_server = server_identifier;
                iface->requested_ip = dhcp_hdr->yiaddr;
@@ -1307,6 +1332,14 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
                strlcpy(iface->domainname, domainname,
                    sizeof(iface->domainname));
                strlcpy(iface->hostname, hostname, sizeof(iface->hostname));
+#ifndef SMALL
+               if (iface_conf != NULL && iface_conf->prefer_ipv6 &&
+                   ipv6_only_time > 0) {
+                       iface->ipv6_only_time = ipv6_only_time;
+                       state_transition(iface, IF_IPV6_ONLY);
+                       break;
+               }
+#endif
                state_transition(iface, IF_BOUND);
                break;
        case DHCPNAK:
@@ -1386,6 +1419,7 @@ state_transition(struct dhcpleased_iface *iface, enum if_state new_state)
                        send_deconfigure_interface(iface);
                        /* fall through */
                case IF_DOWN:
+               case IF_IPV6_ONLY:
                        iface->timo.tv_sec = START_EXP_BACKOFF;
                        break;
                case IF_BOUND:
@@ -1434,6 +1468,25 @@ state_transition(struct dhcpleased_iface *iface, enum if_state new_state)
                        iface->timo.tv_sec /= 2;
                request_dhcp_request(iface);
                break;
+       case IF_IPV6_ONLY:
+               switch (old_state) {
+               case IF_REQUESTING:
+               case IF_RENEWING:
+               case IF_REBINDING:
+               case IF_REBOOTING:
+                       /* going IPv6 only: delete legacy IP */
+                       send_rdns_withdraw(iface);
+                       send_deconfigure_interface(iface);
+                       /* fall through */
+               case IF_INIT:
+               case IF_DOWN:
+               case IF_IPV6_ONLY:
+                       iface->timo.tv_sec = iface->ipv6_only_time;
+                       break;
+               case IF_BOUND:
+                       fatal("invalid transition Bound -> IPv6 only");
+                       break;
+               }
        }
 
        if_name = if_indextoname(iface->if_index, ifnamebuf);
@@ -1499,6 +1552,9 @@ iface_timeout(int fd, short events, void *arg)
                else
                        state_transition(iface, IF_REBINDING);
                break;
+       case IF_IPV6_ONLY:
+               state_transition(iface, IF_REQUESTING);
+               break;
        }
 }
 
@@ -1584,6 +1640,9 @@ request_dhcp_request(struct dhcpleased_iface *iface)
                imsg.requested_ip.s_addr = INADDR_ANY;          /* MUST NOT */
                imsg.ciaddr = iface->requested_ip;              /* IP address */
                break;
+       case IF_IPV6_ONLY:
+               fatalx("invalid state IF_IPV6_ONLY in %s", __func__);
+               break;
        }
 
        engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg, sizeof(imsg));
index e94a359..3640f3b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: frontend.c,v 1.30 2022/07/14 15:23:09 florian Exp $   */
+/*     $OpenBSD: frontend.c,v 1.31 2023/11/25 12:00:39 florian Exp $   */
 
 /*
  * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
@@ -924,6 +924,11 @@ build_packet(uint8_t message_type, char *if_name, uint32_t xid,
                8, DHO_SUBNET_MASK, DHO_ROUTERS, DHO_DOMAIN_NAME_SERVERS,
                DHO_HOST_NAME, DHO_DOMAIN_NAME, DHO_BROADCAST_ADDRESS,
                DHO_DOMAIN_SEARCH, DHO_CLASSLESS_STATIC_ROUTES};
+       static uint8_t   dhcp_req_list_v6[] = {DHO_DHCP_PARAMETER_REQUEST_LIST,
+               9, DHO_SUBNET_MASK, DHO_ROUTERS, DHO_DOMAIN_NAME_SERVERS,
+               DHO_HOST_NAME, DHO_DOMAIN_NAME, DHO_BROADCAST_ADDRESS,
+               DHO_DOMAIN_SEARCH, DHO_CLASSLESS_STATIC_ROUTES,
+               DHO_IPV6_ONLY_PREFERRED};
        static uint8_t   dhcp_requested_address[] = {DHO_DHCP_REQUESTED_ADDRESS,
                4, 0, 0, 0, 0};
        static uint8_t   dhcp_server_identifier[] = {DHO_DHCP_SERVER_IDENTIFIER,
@@ -997,15 +1002,23 @@ build_packet(uint8_t message_type, char *if_name, uint32_t xid,
                        memcpy(p, iface_conf->vc_id, iface_conf->vc_id_len);
                        p += iface_conf->vc_id_len;
                }
+               if (iface_conf->prefer_ipv6) {
+                       memcpy(p, dhcp_req_list_v6, sizeof(dhcp_req_list_v6));
+                       p += sizeof(dhcp_req_list_v6);
+
+               } else {
+                       memcpy(p, dhcp_req_list, sizeof(dhcp_req_list));
+                       p += sizeof(dhcp_req_list);
+               }
        } else
 #endif /* SMALL */
        {
                memcpy(dhcp_client_id + 3, hw_address, sizeof(*hw_address));
                memcpy(p, dhcp_client_id, sizeof(dhcp_client_id));
                p += sizeof(dhcp_client_id);
+               memcpy(p, dhcp_req_list, sizeof(dhcp_req_list));
+               p += sizeof(dhcp_req_list);
        }
-       memcpy(p, dhcp_req_list, sizeof(dhcp_req_list));
-       p += sizeof(dhcp_req_list);
 
        if (requested_ip->s_addr != INADDR_ANY) {
                memcpy(dhcp_requested_address + 2, requested_ip,
index e3a6895..eeae428 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.7 2022/03/21 04:35:41 dlg Exp $   */
+/*     $OpenBSD: parse.y,v 1.8 2023/11/25 12:00:39 florian Exp $       */
 
 /*
  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -109,7 +109,7 @@ typedef struct {
 %}
 
 %token DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT IGNORE DNS ROUTES HOST NAME
-%token NO
+%token NO PREFER IPV6
 
 %token <v.string>      STRING
 %token <v.number>      NUMBER
@@ -324,6 +324,9 @@ ifaceoptsl  : SEND VENDOR CLASS ID STRING {
                        }
                        free($2);
                }
+               | PREFER IPV6 {
+                       iface_conf->prefer_ipv6 = 1;
+               }
                ;
 %%
 
@@ -366,8 +369,10 @@ lookup(char *s)
                {"id",                  ID},
                {"ignore",              IGNORE},
                {"interface",           DHCP_IFACE},
+               {"ipv6",                IPV6},
                {"name",                NAME},
                {"no",                  NO},
+               {"prefer",              PREFER},
                {"routes",              ROUTES},
                {"send",                SEND},
                {"vendor",              VENDOR},
index 076fe2e..0ef3970 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: printconf.c,v 1.4 2022/01/04 06:20:37 florian Exp $   */
+/*     $OpenBSD: printconf.c,v 1.5 2023/11/25 12:00:39 florian Exp $   */
 
 /*
  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -126,6 +126,8 @@ print_config(struct dhcpleased_conf *conf)
                        printf("\tignore %s\n", hbuf);
 
                }
+               if (iface->prefer_ipv6)
+                       printf("\t prefer ipv6\n");
                printf("}\n");
        }
 }