]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp: use TLV object to manage extra and vendor options
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 22 Mar 2026 08:00:33 +0000 (17:00 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 12 May 2026 11:06:28 +0000 (20:06 +0900)
Note, previously we replaced the previous option with the same option code with
new one. But, DHCP message can have multiple options with same option code.
Hence, this make the conf parser not replace, but append new one.

14 files changed:
src/libsystemd-network/dhcp-client-internal.h
src/libsystemd-network/dhcp-option.c
src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/sd-dhcp-server.c
src/network/networkd-dhcp-common.c
src/network/networkd-dhcp-common.h
src/network/networkd-dhcp-server.c
src/network/networkd-dhcp4.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/systemd/sd-dhcp-client.h
src/systemd/sd-dhcp-server.h

index fab4ff24aaf99d2dd8a83cc83a78172cc9f11703..8e23f7d931439acf9679dfbe147a1edf00290ae1 100644 (file)
@@ -8,6 +8,7 @@
 #include "network-common.h"
 #include "sd-forward.h"
 #include "socket-util.h"
+#include "tlv-util.h"
 
 typedef enum DHCPState {
         DHCP_STATE_STOPPED,
@@ -65,8 +66,8 @@ struct sd_dhcp_client {
         uint64_t discover_attempt;
         uint64_t request_attempt;
         uint64_t max_discover_attempts;
-        OrderedHashmap *extra_options;
-        OrderedHashmap *vendor_options;
+        TLV *extra_options;
+        TLV *vendor_options;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
         sd_event_source *timeout_expire;
@@ -90,6 +91,9 @@ int dhcp_client_set_state_callback(
                 void *userdata);
 int dhcp_client_get_state(sd_dhcp_client *client);
 
+int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options);
+int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options);
+
 int client_receive_message_raw(
                 sd_event_source *s,
                 int fd,
index 74bc21e95ba237cd45e2fc35d35c65fa3ce5faea..75470cb3d6eae0b438fbbbe4c0791b6995e592e0 100644 (file)
@@ -11,7 +11,6 @@
 #include "dns-domain.h"
 #include "hostname-util.h"
 #include "memory-util.h"
-#include "ordered-set.h"
 #include "string-util.h"
 #include "strv.h"
 #include "utf8.h"
@@ -107,33 +106,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset,
                 *offset += 3 + optlen;
 
                 break;
-        case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: {
-                /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV.
-                 * The structured handling below expects optval to be an OrderedSet*. */
-                if (optlen > 0)
-                        return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
-
-                OrderedSet *s = (OrderedSet *) optval;
-                struct sd_dhcp_option *p;
-                size_t l = 0;
-
-                ORDERED_SET_FOREACH(p, s)
-                        l += p->length + 2;
-
-                if (*offset + l + 2 > size)
-                        return -ENOBUFS;
-
-                options[*offset] = code;
-                options[*offset + 1] = l;
-                *offset += 2;
-
-                ORDERED_SET_FOREACH(p, s) {
-                        r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data);
-                        if (r < 0)
-                                return r;
-                }
-                break;
-        }
         case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: {
                 /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV.
                  * The structured handling below expects optval to be an sd_dhcp_server*. */
index 8b7c856d218af8393b799cbaa00c6ee627fc0149..a8e2cbf5c706d82ab881533b7fc06f6e70ab37f7 100644 (file)
@@ -13,6 +13,7 @@
 #include "sd-forward.h"
 #include "network-common.h"
 #include "sparse-endian.h"
+#include "tlv-util.h"
 
 typedef enum DHCPRawOption {
         DHCP_RAW_OPTION_DATA_UINT8,
@@ -53,8 +54,8 @@ typedef struct sd_dhcp_server {
         char *boot_server_name;
         char *boot_filename;
 
-        OrderedSet *extra_options;
-        OrderedSet *vendor_options;
+        TLV *extra_options;
+        TLV *vendor_options;
 
         bool emit_router;
         struct in_addr router_address;
@@ -99,6 +100,9 @@ typedef struct DHCPRequest {
         triple_timestamp timestamp;
 } DHCPRequest;
 
+int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options);
+int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options);
+
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                                size_t length, const triple_timestamp *timestamp);
 int dhcp_server_send_packet(sd_dhcp_server *server,
index 45a0eb8674a905b3276bae8cae935384d939a6e2..0b13e925f2669459dfd4b090c7e75fcffc59eafb 100644 (file)
@@ -19,6 +19,7 @@
 #include "event-util.h"
 #include "hostname-util.h"
 #include "iovec-util.h"
+#include "iovec-wrapper.h"
 #include "memory-util.h"
 #include "network-common.h"
 #include "random-util.h"
@@ -502,39 +503,18 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt
         return 0;
 }
 
-int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) {
-        int r;
-
-        assert_return(client, -EINVAL);
-        assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
-        assert_return(v, -EINVAL);
-
-        r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp_option_hash_ops, UINT_TO_PTR(v->option), v);
-        if (r < 0)
-                return r;
+int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options) {
+        assert(client);
+        assert(!sd_dhcp_client_is_running(client));
 
-        sd_dhcp_option_ref(v);
-        return 0;
+        return unref_and_replace_new_ref(client->extra_options, options, tlv_ref, tlv_unref);
 }
 
-int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) {
-        int r;
-
-        assert_return(client, -EINVAL);
-        assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
-        assert_return(v, -EINVAL);
-
-        r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops);
-        if (r < 0)
-                return -ENOMEM;
-
-        r = ordered_hashmap_put(client->vendor_options, v, v);
-        if (r < 0)
-                return r;
-
-        sd_dhcp_option_ref(v);
+int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options) {
+        assert(client);
+        assert(!sd_dhcp_client_is_running(client));
 
-        return 1;
+        return unref_and_replace_new_ref(client->vendor_options, options, tlv_ref, tlv_unref);
 }
 
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
@@ -912,7 +892,6 @@ static int client_append_fqdn_option(
 }
 
 static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
-        sd_dhcp_option *j;
         int r;
 
         assert(client);
@@ -962,18 +941,31 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client,
                         return r;
         }
 
-        ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
-                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
-                                       j->option, j->length, j->data);
+        if (client->extra_options) {
+                void *key;
+                struct iovec_wrapper *iovw;
+                HASHMAP_FOREACH_KEY(iovw, key, client->extra_options->entries) {
+                        uint32_t tag = PTR_TO_UINT32(key);
+
+                        FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
+                                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+                                               tag, iov->iov_len, iov->iov_base);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        if (!tlv_isempty(client->vendor_options)) {
+                _cleanup_(iovec_done) struct iovec iov = {};
+                r = tlv_build(client->vendor_options, &iov);
                 if (r < 0)
                         return r;
-        }
 
-        if (!ordered_hashmap_isempty(client->vendor_options)) {
                 r = dhcp_option_append(
                                 &packet->dhcp, optlen, optoffset, 0,
                                 SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
-                                /* optlen= */ 0, client->vendor_options);
+                                iov.iov_len, iov.iov_base);
                 if (r < 0)
                         return r;
         }
@@ -2306,8 +2298,8 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) {
         free(client->vendor_class_identifier);
         free(client->mudurl);
         client->user_class = strv_free(client->user_class);
-        ordered_hashmap_free(client->extra_options);
-        ordered_hashmap_free(client->vendor_options);
+        tlv_unref(client->extra_options);
+        tlv_unref(client->vendor_options);
         free(client->ifname);
         return mfree(client);
 }
index 7519189229ca080b5b995cc67d7fae8289274857..48c481809f4e54e997a48b963b2d02dda9df54d1 100644 (file)
 #include "dns-domain.h"
 #include "errno-util.h"
 #include "fd-util.h"
+#include "hashmap.h"
 #include "in-addr-util.h"
 #include "iovec-util.h"
+#include "iovec-wrapper.h"
 #include "memory-util.h"
 #include "network-common.h"
-#include "ordered-set.h"
 #include "path-util.h"
 #include "siphash24.h"
 #include "socket-util.h"
@@ -138,8 +139,8 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
         server->static_leases_by_address = hashmap_free(server->static_leases_by_address);
         server->static_leases_by_client_id = hashmap_free(server->static_leases_by_client_id);
 
-        ordered_set_free(server->extra_options);
-        ordered_set_free(server->vendor_options);
+        tlv_unref(server->extra_options);
+        tlv_unref(server->vendor_options);
 
         free(server->agent_circuit_id);
         free(server->agent_remote_id);
@@ -618,7 +619,6 @@ static int server_send_offer_or_ack(
         };
 
         _cleanup_free_ DHCPPacket *packet = NULL;
-        sd_dhcp_option *j;
         be32_t lease_time;
         size_t offset;
         int r;
@@ -718,18 +718,31 @@ static int server_send_offer_or_ack(
                         return r;
         }
 
-        ORDERED_SET_FOREACH(j, server->extra_options) {
-                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                                       j->option, j->length, j->data);
+        if (server->extra_options) {
+                void *key;
+                struct iovec_wrapper *iovw;
+                HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) {
+                        uint32_t tag = PTR_TO_UINT32(key);
+
+                        FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
+                                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                                                       tag, iov->iov_len, iov->iov_base);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        if (!tlv_isempty(server->vendor_options)) {
+                _cleanup_(iovec_done) struct iovec iov = {};
+                r = tlv_build(server->vendor_options, &iov);
                 if (r < 0)
                         return r;
-        }
 
-        if (!ordered_set_isempty(server->vendor_options)) {
                 r = dhcp_option_append(
                                 &packet->dhcp, req->max_optlen, &offset, 0,
                                 SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
-                                /* optlen= */ 0, server->vendor_options);
+                                iov.iov_len, iov.iov_base);
                 if (r < 0)
                         return r;
         }
@@ -1611,33 +1624,14 @@ int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *rout
         return 0;
 }
 
-int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v) {
-        int r;
-
-        assert_return(server, -EINVAL);
-        assert_return(v, -EINVAL);
-
-        r = ordered_set_ensure_put(&server->extra_options, &dhcp_option_hash_ops, v);
-        if (r < 0)
-                return r;
-
-        sd_dhcp_option_ref(v);
-        return 0;
+int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options) {
+        assert(server);
+        return unref_and_replace_new_ref(server->extra_options, options, tlv_ref, tlv_unref);
 }
 
-int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v) {
-        int r;
-
-        assert_return(server, -EINVAL);
-        assert_return(v, -EINVAL);
-
-        r = ordered_set_ensure_put(&server->vendor_options, &dhcp_option_hash_ops, v);
-        if (r < 0)
-                return r;
-
-        sd_dhcp_option_ref(v);
-
-        return 1;
+int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options) {
+        assert(server);
+        return unref_and_replace_new_ref(server->vendor_options, options, tlv_ref, tlv_unref);
 }
 
 int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_t cb, void *userdata) {
index 3e7cca991b392d334040bdd0fd0b7c6427f56b3e..d7353d07ed41c9191a792f4f5b2ba18212059fc4 100644 (file)
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-locator.h"
-#include "dhcp-option.h"
 #include "dhcp6-option.h"
 #include "escape.h"
 #include "extract-word.h"
 #include "hexdecoct.h"
 #include "in-addr-prefix-util.h"
+#include "iovec-util.h"
 #include "networkd-dhcp-common.h"
 #include "networkd-dhcp-prefix-delegation.h"
 #include "networkd-link.h"
@@ -696,7 +696,7 @@ int config_parse_dhcp_user_or_vendor_class(
         }
 }
 
-int config_parse_dhcp_send_option(
+int config_parse_dhcp6_send_option(
                 const char *unit,
                 const char *filename,
                 unsigned line,
@@ -708,17 +708,15 @@ int config_parse_dhcp_send_option(
                 void *data,
                 void *userdata) {
 
-        _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL;
         _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL;
-        _unused_ _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *old4 = NULL;
         _unused_ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *old6 = NULL;
         uint32_t uint32_data, enterprise_identifier = 0;
         _cleanup_free_ char *word = NULL, *q = NULL;
-        OrderedHashmap **options = ASSERT_PTR(data);
+        OrderedHashmap **dhcp6_options = ASSERT_PTR(data);
         uint16_t u16, uint16_data;
         union in_addr_union addr;
         DHCPOptionDataType type;
-        uint8_t u8, uint8_data;
+        uint8_t uint8_data;
         const void *udata;
         const char *p;
         ssize_t sz;
@@ -729,12 +727,12 @@ int config_parse_dhcp_send_option(
         assert(rvalue);
 
         if (isempty(rvalue)) {
-                *options = ordered_hashmap_free(*options);
+                *dhcp6_options = ordered_hashmap_free(*dhcp6_options);
                 return 0;
         }
 
         p = rvalue;
-        if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) {
+        if (streq(lvalue, "SendVendorOption")) {
                 r = extract_first_word(&p, &word, ":", 0);
                 if (r == -ENOMEM)
                         return log_oom();
@@ -762,30 +760,16 @@ int config_parse_dhcp_send_option(
                 return 0;
         }
 
-        if (ltype == AF_INET6) {
-                r = safe_atou16(word, &u16);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Invalid DHCP option, ignoring assignment: %s", rvalue);
-                         return 0;
-                }
-                if (u16 < 1 || u16 >= UINT16_MAX) {
-                        log_syntax(unit, LOG_WARNING, filename, line, 0,
-                                   "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue);
-                        return 0;
-                }
-        } else {
-                r = safe_atou8(word, &u8);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Invalid DHCP option, ignoring assignment: %s", rvalue);
-                         return 0;
-                }
-                if (u8 < 1 || u8 >= UINT8_MAX) {
-                        log_syntax(unit, LOG_WARNING, filename, line, 0,
-                                   "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue);
-                        return 0;
-                }
+        r = safe_atou16(word, &u16);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Invalid DHCP option, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+        if (u16 < 1 || u16 >= UINT16_MAX) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue);
+                return 0;
         }
 
         word = mfree(word);
@@ -887,49 +871,193 @@ int config_parse_dhcp_send_option(
                 return -EINVAL;
         }
 
-        if (ltype == AF_INET6) {
-                r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
-                        return 0;
-                }
+        r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
+                return 0;
+        }
+
+        r = ordered_hashmap_ensure_allocated(dhcp6_options, &dhcp6_option_hash_ops);
+        if (r < 0)
+                return log_oom();
+
+        /* Overwrite existing option */
+        old6 = ordered_hashmap_get(*dhcp6_options, UINT_TO_PTR(u16));
+        r = ordered_hashmap_replace(*dhcp6_options, UINT_TO_PTR(u16), opt6);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
+                return 0;
+        }
+        TAKE_PTR(opt6);
 
-                r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops);
+        return 0;
+}
+
+int config_parse_dhcp_option(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        struct iovec *iov = ASSERT_PTR(data);
+        bool check_length = ltype;
+        int r;
+
+        if (isempty(rvalue)) {
+                iovec_done(iov);
+                return 0;
+        }
+
+        _cleanup_free_ char *word = NULL;
+        const char *p = rvalue;
+        r = extract_first_word(&p, &word, ":", 0);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r <= 0 || isempty(p))
+                return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+        DHCPOptionDataType type = dhcp_option_data_type_from_string(word);
+        if (type < 0)
+                return log_syntax_parse_error(unit, filename, line, type, lvalue, rvalue);
+
+        switch (type) {
+        case DHCP_OPTION_DATA_UINT8:{
+                uint8_t u;
+
+                r = safe_atou8(p, &u);
+                if (r < 0)
+                        return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+                r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u)));
                 if (r < 0)
                         return log_oom();
 
-                /* Overwrite existing option */
-                old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16));
-                r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
-                        return 0;
-                }
-                TAKE_PTR(opt6);
-        } else {
-                r = sd_dhcp_option_new(u8, udata, sz, &opt4);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
-                        return 0;
-                }
+                return 1;
+        }
+        case DHCP_OPTION_DATA_UINT16:{
+                uint16_t u;
 
-                r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops);
+                r = safe_atou16(p, &u);
+                if (r < 0)
+                        return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+                u = htobe16(u);
+                r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u)));
                 if (r < 0)
                         return log_oom();
 
-                /* Overwrite existing option */
-                old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8));
-                r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
-                        return 0;
-                }
-                TAKE_PTR(opt4);
+                return 1;
+        }
+        case DHCP_OPTION_DATA_UINT32: {
+                uint32_t u;
+
+                r = safe_atou32(p, &u);
+                if (r < 0)
+                        return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+                u = htobe32(u);
+                r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u)));
+                if (r < 0)
+                        return log_oom();
+
+                return 1;
+        }
+        case DHCP_OPTION_DATA_IPV4ADDRESS: {
+                union in_addr_union a;
+
+                r = in_addr_from_string(AF_INET, p, &a);
+                if (r < 0)
+                        return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+                r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&a.in, sizeof(a.in)));
+                if (r < 0)
+                        return log_oom();
+
+                return 1;
+        }
+        case DHCP_OPTION_DATA_IPV6ADDRESS: {
+                union in_addr_union a;
+
+                r = in_addr_from_string(AF_INET6, p, &a);
+                if (r < 0)
+                        return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+                r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&a.in6, sizeof(a.in6)));
+                if (r < 0)
+                        return log_oom();
+
+                return 1;
+        }
+        case DHCP_OPTION_DATA_STRING: {
+                _cleanup_free_ char *s = NULL;
+                ssize_t sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &s);
+                if (sz < 0)
+                        return log_syntax_parse_error(unit, filename, line, sz, lvalue, rvalue);
+                if (check_length && sz > UINT8_MAX)
+                        return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue);
+
+                iovec_done(iov);
+                *iov = IOVEC_MAKE(TAKE_PTR(s), sz);
+                return 1;
+        }
+        default:
+                return -EINVAL;
         }
+}
+
+int config_parse_dhcp_option_tlv(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        TLV *options = ASSERT_PTR(data);
+        int r;
+
+        if (isempty(rvalue)) {
+                tlv_done(options);
+                return 0;
+        }
+
+        _cleanup_free_ char *word = NULL;
+        const char *p = rvalue;
+        r = extract_first_word(&p, &word, ":", 0);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r <= 0 || isempty(p))
+                return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+        uint8_t code;
+        r = safe_atou8(word, &code);
+        if (r < 0)
+                return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+        if (code < 1 || code >= UINT8_MAX)
+                return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue);
+
+        _cleanup_(iovec_done) struct iovec iov = {};
+        r = config_parse_dhcp_option(unit, filename, line, section, section_line, lvalue, ltype, p, &iov, userdata);
+        if (r <= 0)
+                return r;
+
+        r = tlv_append_iov(options, code, &iov);
+        if (r < 0)
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to store '%s=%s', ignoring assignment: %m", lvalue, rvalue);
+
         return 0;
 }
 
index 9eb9331923b9f2a86b2e18984755e8fecce99d54..0b88d7790c17ae81e024e69152106074217f794d 100644 (file)
@@ -82,7 +82,9 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname);
 CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class);
-CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_option);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_send_option);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option_tlv);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options);
 CONFIG_PARSER_PROTOTYPE(config_parse_duid_type);
 CONFIG_PARSER_PROTOTYPE(config_parse_manager_duid_type);
index c88488258f00285e136d3c1dae6d3ecc802b314b..9903c6b9d323309bac89fe19751250a034ffe9f9 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "conf-parser.h"
 #include "dhcp-protocol.h"
+#include "dhcp-server-internal.h"
 #include "dhcp-server-lease-internal.h"
 #include "dns-domain.h"
 #include "errno-util.h"
@@ -567,7 +568,6 @@ static int dhcp_server_set_domain(Link *link) {
 
 static int dhcp4_server_configure(Link *link) {
         bool acquired_uplink = false;
-        sd_dhcp_option *p;
         DHCPStaticLease *static_lease;
         Link *uplink = NULL;
         Address *address;
@@ -721,21 +721,13 @@ static int dhcp4_server_configure(Link *link) {
         else if (r < 0)
                 return log_link_error_errno(link, r, "Failed to set domain name for DHCP server: %m");
 
-        ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) {
-                r = sd_dhcp_server_add_option(link->dhcp_server, p);
-                if (r == -EEXIST)
-                        continue;
-                if (r < 0)
-                        return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
-        }
+        r = dhcp_server_set_extra_options(link->dhcp_server, &link->network->dhcp_server_extra_options);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to set DHCPv4 extra options: %m");
 
-        ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) {
-                r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p);
-                if (r == -EEXIST)
-                        continue;
-                if (r < 0)
-                        return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
-        }
+        r = dhcp_server_set_vendor_options(link->dhcp_server, &link->network->dhcp_server_vendor_options);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to set DHCPv4 vendor options: %m");
 
         HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) {
                 r = sd_dhcp_server_set_static_lease(
index dc6b78e326c34a30e045194482b43618d4507965..7304a049b699d0ab67de3aa4303d2421999c0b96 100644 (file)
@@ -1477,7 +1477,6 @@ static bool link_dhcp4_ipv6_only_mode(Link *link) {
 }
 
 static int dhcp4_configure(Link *link) {
-        sd_dhcp_option *send_option;
         void *request_options;
         int r;
 
@@ -1619,21 +1618,13 @@ static int dhcp4_configure(Link *link) {
                                 return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for '%u': %m", option);
                 }
 
-                ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_options) {
-                        r = sd_dhcp_client_add_option(link->dhcp_client, send_option);
-                        if (r == -EEXIST)
-                                continue;
-                        if (r < 0)
-                                return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set send option: %m");
-                }
+                r = dhcp_client_set_extra_options(link->dhcp_client, &link->network->dhcp_extra_options);
+                if (r < 0)
+                        return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set extra options: %m");
 
-                ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_vendor_options) {
-                        r = sd_dhcp_client_add_vendor_option(link->dhcp_client, send_option);
-                        if (r == -EEXIST)
-                                continue;
-                        if (r < 0)
-                                return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set send option: %m");
-                }
+                r = dhcp_client_set_vendor_options(link->dhcp_client, &link->network->dhcp_vendor_options);
+                if (r < 0)
+                        return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set vendor options: %m");
 
                 r = dhcp4_set_hostname(link);
                 if (r < 0)
index aaf974e312d677e8cffc867cd16e63f2ab90bf68..0f1c4e57c46f48201d60b748454f6a7f5ad38a2e 100644 (file)
@@ -293,8 +293,8 @@ DHCPv4.DenyList,                                 config_parse_in_addr_prefixes,
 DHCPv4.AllowList,                                config_parse_in_addr_prefixes,                  AF_INET,                                offsetof(Network, dhcp_allow_listed_ip)
 DHCPv4.IPServiceType,                            config_parse_dhcp_ip_service_type,              0,                                      offsetof(Network, dhcp_ip_service_type)
 DHCPv4.SocketPriority,                           config_parse_dhcp_socket_priority,              0,                                      0
-DHCPv4.SendOption,                               config_parse_dhcp_send_option,                  AF_INET,                                offsetof(Network, dhcp_client_send_options)
-DHCPv4.SendVendorOption,                         config_parse_dhcp_send_option,                  0,                                      offsetof(Network, dhcp_client_send_vendor_options)
+DHCPv4.SendOption,                               config_parse_dhcp_option_tlv,                   0,                                      offsetof(Network, dhcp_extra_options)
+DHCPv4.SendVendorOption,                         config_parse_dhcp_option_tlv,                   0,                                      offsetof(Network, dhcp_vendor_options)
 DHCPv4.RouteMTUBytes,                            config_parse_mtu,                               AF_INET,                                offsetof(Network, dhcp_route_mtu)
 DHCPv4.InitialCongestionWindow,                  config_parse_tcp_window,                        0,                                      offsetof(Network, dhcp_initial_congestion_window)
 DHCPv4.InitialAdvertisedReceiveWindow,           config_parse_tcp_window,                        0,                                      offsetof(Network, dhcp_advertised_receive_window)
@@ -320,11 +320,11 @@ DHCPv6.Hostname,                                 config_parse_hostname,
 DHCPv6.RequestOptions,                           config_parse_dhcp_request_options,              AF_INET6,                               0
 DHCPv6.UserClass,                                config_parse_dhcp_user_or_vendor_class,         AF_INET6,                               offsetof(Network, dhcp6_user_class)
 DHCPv6.VendorClass,                              config_parse_dhcp_user_or_vendor_class,         AF_INET6,                               offsetof(Network, dhcp6_vendor_class)
-DHCPv6.SendVendorOption,                         config_parse_dhcp_send_option,                  AF_INET6,                               offsetof(Network, dhcp6_client_send_vendor_options)
+DHCPv6.SendVendorOption,                         config_parse_dhcp6_send_option,                 0,                                      offsetof(Network, dhcp6_client_send_vendor_options)
 DHCPv6.PrefixDelegationHint,                     config_parse_dhcp6_pd_prefix_hint,              0,                                      0
 DHCPv6.UnassignedSubnetPolicy,                   config_parse_dhcp_pd_prefix_route_type,         0,                                      offsetof(Network, dhcp6_pd_prefix_route_type)
 DHCPv6.WithoutRA,                                config_parse_dhcp6_client_start_mode,           0,                                      offsetof(Network, dhcp6_client_start_mode)
-DHCPv6.SendOption,                               config_parse_dhcp_send_option,                  AF_INET6,                               offsetof(Network, dhcp6_client_send_options)
+DHCPv6.SendOption,                               config_parse_dhcp6_send_option,                 0,                                      offsetof(Network, dhcp6_client_send_options)
 DHCPv6.IAID,                                     config_parse_iaid,                              AF_INET6,                               0
 DHCPv6.DUIDType,                                 config_parse_duid_type,                         0,                                      offsetof(Network, dhcp6_duid)
 DHCPv6.DUIDRawData,                              config_parse_duid_rawdata,                      0,                                      offsetof(Network, dhcp6_duid)
@@ -387,8 +387,8 @@ DHCPServer.EmitDomain,                           config_parse_bool,
 DHCPServer.Domain,                               config_parse_dns_name,                          0,                                      offsetof(Network, dhcp_server_domain)
 DHCPServer.PoolOffset,                           config_parse_uint32,                            0,                                      offsetof(Network, dhcp_server_pool_offset)
 DHCPServer.PoolSize,                             config_parse_uint32,                            0,                                      offsetof(Network, dhcp_server_pool_size)
-DHCPServer.SendVendorOption,                     config_parse_dhcp_send_option,                  0,                                      offsetof(Network, dhcp_server_send_vendor_options)
-DHCPServer.SendOption,                           config_parse_dhcp_send_option,                  0,                                      offsetof(Network, dhcp_server_send_options)
+DHCPServer.SendOption,                           config_parse_dhcp_option_tlv,                   0,                                      offsetof(Network, dhcp_server_extra_options)
+DHCPServer.SendVendorOption,                     config_parse_dhcp_option_tlv,                   0,                                      offsetof(Network, dhcp_server_vendor_options)
 DHCPServer.BindToInterface,                      config_parse_bool,                              0,                                      offsetof(Network, dhcp_server_bind_to_interface)
 DHCPServer.BootServerAddress,                    config_parse_in_addr_non_null,                  AF_INET,                                offsetof(Network, dhcp_server_boot_server_address)
 DHCPServer.BootServerName,                       config_parse_dns_name,                          0,                                      offsetof(Network, dhcp_server_boot_server_name)
index 3ffe1640e767bbb7e42f74191c836d3597d373b5..0397e77f810032e9a22625059a94d6c1f5781583 100644 (file)
@@ -397,6 +397,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
                 .dhcp_use_gateway = -1,
                 .dhcp_send_hostname = true,
                 .dhcp_send_release = true,
+                .dhcp_extra_options = TLV_INIT(TLV_DHCP4),
+                .dhcp_vendor_options = TLV_INIT(TLV_DHCP4_SUBOPTION),
                 .dhcp_route_metric = DHCP_ROUTE_METRIC,
                 .dhcp_use_rapid_commit = -1,
                 .dhcp_client_identifier = _DHCP_CLIENT_ID_INVALID,
@@ -436,6 +438,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
                 .dhcp_server_emit_router = true,
                 .dhcp_server_emit_timezone = true,
                 .dhcp_server_rapid_commit = true,
+                .dhcp_server_extra_options = TLV_INIT(TLV_DHCP4),
+                .dhcp_server_vendor_options = TLV_INIT(TLV_DHCP4_SUBOPTION),
                 .dhcp_server_persist_leases = _DHCP_SERVER_PERSIST_LEASES_INVALID,
 
                 .router_lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC,
@@ -771,8 +775,8 @@ static Network *network_free(Network *network) {
         free(network->dhcp_server_uplink_name);
         for (sd_dhcp_lease_server_type_t t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++)
                 free(network->dhcp_server_emit[t].addresses);
-        ordered_hashmap_free(network->dhcp_server_send_options);
-        ordered_hashmap_free(network->dhcp_server_send_vendor_options);
+        tlv_done(&network->dhcp_server_extra_options);
+        tlv_done(&network->dhcp_server_vendor_options);
         free(network->dhcp_server_local_lease_domain);
 
         /* DHCP client */
@@ -784,8 +788,8 @@ static Network *network_free(Network *network) {
         set_free(network->dhcp_allow_listed_ip);
         strv_free(network->dhcp_user_class);
         set_free(network->dhcp_request_options);
-        ordered_hashmap_free(network->dhcp_client_send_options);
-        ordered_hashmap_free(network->dhcp_client_send_vendor_options);
+        tlv_done(&network->dhcp_extra_options);
+        tlv_done(&network->dhcp_vendor_options);
         free(network->dhcp_netlabel);
         nft_set_context_clear(&network->dhcp_nft_set_context);
 
index 9a36c312f89200db14dcde29e01764b70d3e87c1..26012612a15c880700170890f423917abcc5740a 100644 (file)
@@ -23,6 +23,7 @@
 #include "networkd-sysctl.h"
 #include "networkd-wwan-bus.h"
 #include "resolve-util.h"
+#include "tlv-util.h"
 
 typedef enum KeepConfiguration {
         KEEP_CONFIGURATION_NO               = 0,
@@ -168,8 +169,8 @@ typedef struct Network {
         Set *dhcp_deny_listed_ip;
         Set *dhcp_allow_listed_ip;
         Set *dhcp_request_options;
-        OrderedHashmap *dhcp_client_send_options;
-        OrderedHashmap *dhcp_client_send_vendor_options;
+        TLV dhcp_extra_options;
+        TLV dhcp_vendor_options;
         char *dhcp_netlabel;
         NFTSetContext dhcp_nft_set_context;
 
@@ -226,8 +227,8 @@ typedef struct Network {
         usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec;
         uint32_t dhcp_server_pool_offset;
         uint32_t dhcp_server_pool_size;
-        OrderedHashmap *dhcp_server_send_options;
-        OrderedHashmap *dhcp_server_send_vendor_options;
+        TLV dhcp_server_extra_options;
+        TLV dhcp_server_vendor_options;
         struct in_addr dhcp_server_boot_server_address;
         char *dhcp_server_boot_server_name;
         char *dhcp_server_boot_filename;
index 378271aef885dfba76055b4f6a4469a96768bd83..f7c5602b6655ee90c604bd42f5950cb717bce05c 100644 (file)
@@ -150,9 +150,6 @@ int sd_dhcp_client_set_bootp(
                 int bootp);
 int sd_dhcp_client_set_send_release(sd_dhcp_client *client, int enable);
 
-int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
-int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
-
 int sd_dhcp_client_is_running(sd_dhcp_client *client);
 int sd_dhcp_client_stop(sd_dhcp_client *client);
 int sd_dhcp_client_start(sd_dhcp_client *client);
index ef13776201a70eb1b32cfefe961a787a6953aff7..c17cd6b7b68eaaf45e0e2b1c91ad4f221fb94f94 100644 (file)
@@ -76,8 +76,6 @@ int sd_dhcp_server_set_sip(sd_dhcp_server *server, const struct in_addr sip[], s
 int sd_dhcp_server_set_pop3(sd_dhcp_server *server, const struct in_addr pop3[], size_t n);
 int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[], size_t n);
 
-int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v);
-int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v);
 int sd_dhcp_server_set_static_lease(
                 sd_dhcp_server *server,
                 const struct in_addr *address,