1
0
mirror of https://github.com/systemd/systemd synced 2026-04-21 06:24:49 +02:00

Compare commits

..

No commits in common. "f55fe53ffce351bb55b6190cf36511c37f99d766" and "b6fc52408afa91f2fb7650e6a7d42c65396e7815" have entirely different histories.

30 changed files with 2382 additions and 2305 deletions

4
NEWS
View File

@ -383,8 +383,8 @@ CHANGES WITH 250:
whether to use the relevant fields from the IPv6 Router Advertisement whether to use the relevant fields from the IPv6 Router Advertisement
records. records.
* The ForceDHCPv6PDOtherInformation= setting in the [DHCPv6] section * The ForceDHCPv6PDOtherInformation= setting in the [DHCPv6] section is
has been removed. Please use the WithoutRA= and UseDelegatedPrefix= now removed. Please use the WithoutRA= and UseDelegatedPrefix=
settings in the [DHCPv6] section and the DHCPv6Client= setting in the settings in the [DHCPv6] section and the DHCPv6Client= setting in the
[IPv6AcceptRA] section to control when the DHCPv6 client is started [IPv6AcceptRA] section to control when the DHCPv6 client is started
and how the delegated prefixes are handled by the DHCPv6 client. and how the delegated prefixes are handled by the DHCPv6 client.

View File

@ -407,11 +407,3 @@ available.
The `gdisk` tool (from version 1.0.5 onward) and its variants (`sgdisk`, The `gdisk` tool (from version 1.0.5 onward) and its variants (`sgdisk`,
`cgdisk`) also support creation of partitions with a matching type code. `cgdisk`) also support creation of partitions with a matching type code.
## Links
[Boot Loader Specification](https://systemd.io/BOOT_LOADER_SPECIFICATION)<br>
[Boot Loader Interface](https://systemd.io/BOOT_LOADER_INTERFACE)<br>
[systemd-boot(7)](https://www.freedesktop.org/software/systemd/man/systemd-boot.html)<br>
[bootctl(1)](https://www.freedesktop.org/software/systemd/man/bootctl.html)<br>
[systemd-gpt-auto-generator(8)](https://www.freedesktop.org/software/systemd/man/systemd-gpt-auto-generator.html)

View File

@ -84,8 +84,13 @@
<term><option>-f</option></term> <term><option>-f</option></term>
<term><option>--force</option></term> <term><option>--force</option></term>
<listitem><para>Force immediate halt, power-off, reboot. Do <listitem><para>Force immediate halt, power-off, or reboot. When
not contact the init system.</para></listitem> specified once, this results in an immediate but clean shutdown
by the system manager. When specified twice, this results in an
immediate shutdown without contacting the system manager. See the
description of <option>--force</option> in
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for more details.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>

View File

@ -1894,9 +1894,9 @@
<para><command>WantedBy=foo.service</command> in a service <para><command>WantedBy=foo.service</command> in a service
<filename>bar.service</filename> is mostly equivalent to <filename>bar.service</filename> is mostly equivalent to
<command>Alias=foo.service.wants/bar.service</command> in the <command>Alias=foo.service.wants/bar.service</command> in the
same file. In case of template units listing non template units, same file. In case of template units, <command>systemctl
<command>systemctl enable</command> must be called with an enable</command> must be called with an instance name, and
instance name, and this instance will be added to the this instance will be added to the
<filename>.wants/</filename> or <filename>.wants/</filename> or
<filename>.requires/</filename> list of the listed unit. E.g. <filename>.requires/</filename> list of the listed unit. E.g.
<command>WantedBy=getty.target</command> in a service <command>WantedBy=getty.target</command> in a service

View File

@ -18,14 +18,6 @@ int _ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops HA
int _ordered_set_ensure_put(OrderedSet **s, const struct hash_ops *ops, void *p HASHMAP_DEBUG_PARAMS); int _ordered_set_ensure_put(OrderedSet **s, const struct hash_ops *ops, void *p HASHMAP_DEBUG_PARAMS);
#define ordered_set_ensure_put(s, hash_ops, key) _ordered_set_ensure_put(s, hash_ops, key HASHMAP_DEBUG_SRC_ARGS) #define ordered_set_ensure_put(s, hash_ops, key) _ordered_set_ensure_put(s, hash_ops, key HASHMAP_DEBUG_SRC_ARGS)
static inline void ordered_set_clear(OrderedSet *s) {
return ordered_hashmap_clear((OrderedHashmap*) s);
}
static inline void ordered_set_clear_free(OrderedSet *s) {
return ordered_hashmap_clear_free((OrderedHashmap*) s);
}
static inline OrderedSet* ordered_set_free(OrderedSet *s) { static inline OrderedSet* ordered_set_free(OrderedSet *s) {
return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s); return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s);
} }

View File

@ -1859,7 +1859,7 @@ static int make_partition_table(
if (!t) if (!t)
return log_oom(); return log_oom();
r = fdisk_parttype_set_typestr(t, GPT_USER_HOME_STR); r = fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(GPT_USER_HOME));
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to initialize partition type: %m"); return log_error_errno(r, "Failed to initialize partition type: %m");

View File

@ -8,27 +8,20 @@
#include "sd-id128.h" #include "sd-id128.h"
#include "dhcp-identifier.h" #include "dhcp-identifier.h"
#include "dhcp6-protocol.h"
#include "netif-util.h" #include "netif-util.h"
#include "siphash24.h" #include "siphash24.h"
#include "sparse-endian.h" #include "sparse-endian.h"
#include "stat-util.h" #include "stat-util.h"
#include "string-table.h" #include "stdio-util.h"
#include "udev-util.h" #include "udev-util.h"
#include "virt.h"
#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03) #define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */ #define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
static const char * const duid_type_table[_DUID_TYPE_MAX] = { int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
[DUID_TYPE_LLT] = "DUID-LLT",
[DUID_TYPE_EN] = "DUID-EN/Vendor",
[DUID_TYPE_LL] = "DUID-LL",
[DUID_TYPE_UUID] = "UUID",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType);
int dhcp_validate_duid_len(DUIDType duid_type, size_t duid_len, bool strict) {
struct duid d; struct duid d;
assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); assert_cc(sizeof(d.raw) >= MAX_DUID_LEN);
@ -64,146 +57,110 @@ int dhcp_validate_duid_len(DUIDType duid_type, size_t duid_len, bool strict) {
return 0; return 0;
} }
static int dhcp_identifier_set_duid_llt(const uint8_t *addr, size_t addr_len, uint16_t arp_type, usec_t t, struct duid *ret_duid, size_t *ret_len) { int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
uint16_t time_from_2000y; uint16_t time_from_2000y;
assert(duid);
assert(len);
assert(addr); assert(addr);
assert(ret_duid);
assert(ret_len);
if (addr_len == 0)
return -EOPNOTSUPP;
if (arp_type == ARPHRD_ETHER) if (arp_type == ARPHRD_ETHER)
assert_return(addr_len == ETH_ALEN, -EINVAL); assert_return(addr_len == ETH_ALEN, -EINVAL);
else if (arp_type == ARPHRD_INFINIBAND) else if (arp_type == ARPHRD_INFINIBAND)
assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
else else
return -EOPNOTSUPP; return -EINVAL;
if (t < USEC_2000) if (t < USEC_2000)
time_from_2000y = 0; time_from_2000y = 0;
else else
time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff); time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
unaligned_write_be16(&ret_duid->type, DUID_TYPE_LLT); unaligned_write_be16(&duid->type, DUID_TYPE_LLT);
unaligned_write_be16(&ret_duid->llt.htype, arp_type); unaligned_write_be16(&duid->llt.htype, arp_type);
unaligned_write_be32(&ret_duid->llt.time, time_from_2000y); unaligned_write_be32(&duid->llt.time, time_from_2000y);
memcpy(ret_duid->llt.haddr, addr, addr_len); memcpy(duid->llt.haddr, addr, addr_len);
*ret_len = sizeof(ret_duid->type) + sizeof(ret_duid->llt.htype) + sizeof(ret_duid->llt.time) + addr_len; *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len;
return 0; return 0;
} }
static int dhcp_identifier_set_duid_ll(const uint8_t *addr, size_t addr_len, uint16_t arp_type, struct duid *ret_duid, size_t *ret_len) { int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
assert(duid);
assert(len);
assert(addr); assert(addr);
assert(ret_duid);
assert(ret_len);
if (addr_len == 0)
return -EOPNOTSUPP;
if (arp_type == ARPHRD_ETHER) if (arp_type == ARPHRD_ETHER)
assert_return(addr_len == ETH_ALEN, -EINVAL); assert_return(addr_len == ETH_ALEN, -EINVAL);
else if (arp_type == ARPHRD_INFINIBAND) else if (arp_type == ARPHRD_INFINIBAND)
assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
else else
return -EOPNOTSUPP; return -EINVAL;
unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL); unaligned_write_be16(&duid->type, DUID_TYPE_LL);
unaligned_write_be16(&ret_duid->ll.htype, arp_type); unaligned_write_be16(&duid->ll.htype, arp_type);
memcpy(ret_duid->ll.haddr, addr, addr_len); memcpy(duid->ll.haddr, addr, addr_len);
*ret_len = sizeof(ret_duid->type) + sizeof(ret_duid->ll.htype) + addr_len; *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len;
return 0; return 0;
} }
int dhcp_identifier_set_duid_en(bool test_mode, struct duid *ret_duid, size_t *ret_len) { int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
sd_id128_t machine_id; sd_id128_t machine_id;
uint64_t hash; uint64_t hash;
int r;
assert(ret_duid); assert(duid);
assert(ret_len); assert(len);
if (!test_mode) { #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
r = sd_id128_get_machine(&machine_id); int r = sd_id128_get_machine(&machine_id);
if (r < 0) if (r < 0)
return r; return r;
} else #else
/* For tests, especially for fuzzers, reproducibility is important. machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
* Hence, use a static and constant machine ID. #endif
* See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */
machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
unaligned_write_be16(&ret_duid->type, DUID_TYPE_EN); unaligned_write_be16(&duid->type, DUID_TYPE_EN);
unaligned_write_be32(&ret_duid->en.pen, SYSTEMD_PEN); unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN);
*len = sizeof(duid->type) + sizeof(duid->en);
/* a bit of snake-oil perhaps, but no need to expose the machine-id /* a bit of snake-oil perhaps, but no need to expose the machine-id
* directly; duid->en.id might not be aligned, so we need to copy */ * directly; duid->en.id might not be aligned, so we need to copy */
hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes)); hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
memcpy(ret_duid->en.id, &hash, sizeof(ret_duid->en.id)); memcpy(duid->en.id, &hash, sizeof(duid->en.id));
*ret_len = sizeof(ret_duid->type) + sizeof(ret_duid->en);
if (test_mode)
assert_se(memcmp(ret_duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, *ret_len) == 0);
return 0; return 0;
} }
static int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) { int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) {
sd_id128_t machine_id; sd_id128_t machine_id;
int r; int r;
assert(ret_duid); assert(duid);
assert(ret_len); assert(len);
r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id); r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
if (r < 0) if (r < 0)
return r; return r;
unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID); unaligned_write_be16(&duid->type, DUID_TYPE_UUID);
memcpy(ret_duid->raw.data, &machine_id, sizeof(machine_id)); memcpy(&duid->raw.data, &machine_id, sizeof(machine_id));
*ret_len = sizeof(ret_duid->type) + sizeof(machine_id); *len = sizeof(duid->type) + sizeof(machine_id);
return 0; return 0;
} }
int dhcp_identifier_set_duid(
DUIDType duid_type,
const uint8_t *addr,
size_t addr_len,
uint16_t arp_type,
usec_t llt_time,
bool test_mode,
struct duid *ret_duid,
size_t *ret_len) {
switch (duid_type) {
case DUID_TYPE_LLT:
return dhcp_identifier_set_duid_llt(addr, addr_len, arp_type, llt_time, ret_duid, ret_len);
case DUID_TYPE_EN:
return dhcp_identifier_set_duid_en(test_mode, ret_duid, ret_len);
case DUID_TYPE_LL:
return dhcp_identifier_set_duid_ll(addr, addr_len, arp_type, ret_duid, ret_len);
case DUID_TYPE_UUID:
return dhcp_identifier_set_duid_uuid(ret_duid, ret_len);
default:
return -EINVAL;
}
}
int dhcp_identifier_set_iaid( int dhcp_identifier_set_iaid(
int ifindex, int ifindex,
const uint8_t *mac, const uint8_t *mac,
size_t mac_len, size_t mac_len,
bool legacy_unstable_byteorder, bool legacy_unstable_byteorder,
bool use_mac, bool use_mac,
void *ret) { void *_id) {
/* name is a pointer to memory in the sd_device struct, so must /* name is a pointer to memory in the sd_device struct, so must
* have the same scope */ * have the same scope */
@ -255,6 +212,6 @@ int dhcp_identifier_set_iaid(
* behavior. */ * behavior. */
id32 = be32toh(id32); id32 = be32toh(id32);
unaligned_write_ne32(ret, id32); unaligned_write_ne32(_id, id32);
return 0; return 0;
} }

View File

@ -54,23 +54,9 @@ struct duid {
}; };
} _packed_; } _packed_;
int dhcp_validate_duid_len(DUIDType duid_type, size_t duid_len, bool strict); int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict);
int dhcp_identifier_set_duid_en(bool test_mode, struct duid *ret_duid, size_t *ret_len); int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
int dhcp_identifier_set_duid( int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
DUIDType duid_type, int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
const uint8_t *addr, int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
size_t addr_len, int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, bool use_mac, void *_id);
uint16_t arp_type,
usec_t llt_time,
bool test_mode,
struct duid *ret_duid,
size_t *ret_len);
int dhcp_identifier_set_iaid(
int ifindex,
const uint8_t *mac,
size_t mac_len,
bool legacy_unstable_byteorder,
bool use_mac,
void *ret);
const char *duid_type_to_string(DUIDType t) _const_;

View File

@ -11,83 +11,138 @@
#include "sd-event.h" #include "sd-event.h"
#include "sd-dhcp6-client.h" #include "sd-dhcp6-client.h"
#include "dhcp-identifier.h"
#include "dhcp6-option.h"
#include "dhcp6-protocol.h" #include "dhcp6-protocol.h"
#include "ether-addr-util.h"
#include "hashmap.h" #include "hashmap.h"
#include "list.h"
#include "macro.h" #include "macro.h"
#include "network-common.h" #include "network-common.h"
#include "ordered-set.h"
#include "sparse-endian.h" #include "sparse-endian.h"
#include "time-util.h"
/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */ typedef struct sd_dhcp6_option {
typedef enum DHCP6RequestIA {
DHCP6_REQUEST_IA_NA = 1 << 0,
DHCP6_REQUEST_IA_TA = 1 << 1, /* currently not used */
DHCP6_REQUEST_IA_PD = 1 << 2,
} DHCP6RequestIA;
struct sd_dhcp6_client {
unsigned n_ref; unsigned n_ref;
int ifindex; uint32_t enterprise_identifier;
char *ifname; uint16_t option;
void *data;
size_t length;
} sd_dhcp6_option;
struct in6_addr local_address; extern const struct hash_ops dhcp6_option_hash_ops;
struct hw_addr_data hw_addr;
uint16_t arp_type;
sd_event *event; /* Common option header */
sd_event_source *receive_message; typedef struct DHCP6Option {
sd_event_source *timeout_resend; be16_t code;
sd_event_source *timeout_expire; be16_t len;
sd_event_source *timeout_t1; uint8_t data[];
sd_event_source *timeout_t2; } _packed_ DHCP6Option;
int event_priority;
int fd;
DHCP6State state; /* Address option */
bool information_request; struct iaaddr {
usec_t information_request_time_usec; struct in6_addr address;
usec_t information_refresh_time_usec; be32_t lifetime_preferred;
be32_t transaction_id; be32_t lifetime_valid;
usec_t transaction_start; } _packed_;
usec_t retransmit_time;
uint8_t retransmit_count;
bool iaid_set; /* Prefix Delegation Prefix option */
DHCP6IA ia_na; struct iapdprefix {
DHCP6IA ia_pd; be32_t lifetime_preferred;
DHCP6RequestIA request_ia; be32_t lifetime_valid;
struct duid duid; uint8_t prefixlen;
size_t duid_len; struct in6_addr address;
be16_t *req_opts; } _packed_;
size_t req_opts_len;
char *fqdn;
char *mudurl;
char **user_class;
char **vendor_class;
OrderedHashmap *extra_options;
OrderedSet *vendor_options;
struct sd_dhcp6_lease *lease; typedef struct DHCP6Address DHCP6Address;
sd_dhcp6_client_callback_t callback; struct DHCP6Address {
void *userdata; LIST_FIELDS(DHCP6Address, addresses);
/* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */ union {
bool test_mode; struct iaaddr iaaddr;
struct iapdprefix iapdprefix;
};
}; };
/* Non-temporary Address option */
struct ia_na {
be32_t id;
be32_t lifetime_t1;
be32_t lifetime_t2;
} _packed_;
/* Prefix Delegation option */
struct ia_pd {
be32_t id;
be32_t lifetime_t1;
be32_t lifetime_t2;
} _packed_;
/* Temporary Address option */
struct ia_ta {
be32_t id;
} _packed_;
typedef struct DHCP6IA {
uint16_t type;
union {
struct ia_na ia_na;
struct ia_pd ia_pd;
struct ia_ta ia_ta;
};
LIST_HEAD(DHCP6Address, addresses);
} DHCP6IA;
typedef struct sd_dhcp6_client sd_dhcp6_client;
bool dhcp6_option_can_request(uint16_t option);
int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
size_t optlen, const void *optval);
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix);
int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class);
int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class);
int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options);
int dhcp6_option_parse(
const uint8_t *buf,
size_t buflen,
size_t *offset,
uint16_t *ret_option_code,
size_t *ret_option_data_len,
const uint8_t **ret_option_data);
int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message);
int dhcp6_option_parse_ia(
sd_dhcp6_client *client,
be32_t iaid,
uint16_t option_code,
size_t option_data_len,
const uint8_t *option_data,
DHCP6IA *ret);
int dhcp6_option_parse_addresses(
const uint8_t *optval,
size_t optlen,
struct in6_addr **addrs,
size_t *count);
int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret);
int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret);
int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address); int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
int dhcp6_network_send_udp_socket(int s, struct in6_addr *address, int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
const void *packet, size_t len); const void *packet, size_t len);
int dhcp6_client_send_message(sd_dhcp6_client *client); int client_parse_message(
sd_dhcp6_client *client,
DHCP6Message *message,
size_t len,
sd_dhcp6_lease *lease);
const char *dhcp6_message_type_to_string(int s) _const_;
int dhcp6_message_type_from_string(const char *s) _pure_;
const char *dhcp6_message_status_to_string(int s) _const_;
int dhcp6_message_status_from_string(const char *s) _pure_;
void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode); void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode);
int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id);
#define log_dhcp6_client_errno(client, error, fmt, ...) \ #define log_dhcp6_client_errno(client, error, fmt, ...) \
log_interface_prefix_full_errno( \ log_interface_prefix_full_errno( \

View File

@ -5,13 +5,11 @@
Copyright © 2014-2015 Intel Corporation. All rights reserved. Copyright © 2014-2015 Intel Corporation. All rights reserved.
***/ ***/
#include <inttypes.h> #include <stdint.h>
#include "sd-dhcp6-lease.h" #include "sd-dhcp6-lease.h"
#include "dhcp6-option.h" #include "dhcp6-internal.h"
#include "macro.h"
#include "time-util.h"
struct sd_dhcp6_lease { struct sd_dhcp6_lease {
unsigned n_ref; unsigned n_ref;
@ -23,13 +21,10 @@ struct sd_dhcp6_lease {
uint8_t preference; uint8_t preference;
bool rapid_commit; bool rapid_commit;
triple_timestamp timestamp; triple_timestamp timestamp;
usec_t lifetime_t1;
usec_t lifetime_t2;
usec_t lifetime_valid;
struct in6_addr server_address; struct in6_addr server_address;
DHCP6IA *ia_na; DHCP6IA ia;
DHCP6IA *ia_pd; DHCP6IA pd;
DHCP6Address *addr_iter; DHCP6Address *addr_iter;
DHCP6Address *prefix_iter; DHCP6Address *prefix_iter;
@ -45,15 +40,17 @@ struct sd_dhcp6_lease {
char *fqdn; char *fqdn;
}; };
int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid); int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire);
DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia);
int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len); int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len);
int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len); int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len);
int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len); int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len);
int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len); int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len);
int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference); int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference);
int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret); int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference);
int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease); int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease);
int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret); int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit);
int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
@ -62,10 +59,3 @@ int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t op
int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
int dhcp6_lease_new(sd_dhcp6_lease **ret); int dhcp6_lease_new(sd_dhcp6_lease **ret);
int dhcp6_lease_new_from_message(
sd_dhcp6_client *client,
const DHCP6Message *message,
size_t len,
const triple_timestamp *timestamp,
const struct in6_addr *server_address,
sd_dhcp6_lease **ret);

View File

@ -9,12 +9,14 @@
#include "sd-dhcp6-client.h" #include "sd-dhcp6-client.h"
#include "alloc-util.h" #include "alloc-util.h"
#include "dhcp-identifier.h"
#include "dhcp6-internal.h" #include "dhcp6-internal.h"
#include "dhcp6-option.h" #include "dhcp6-lease-internal.h"
#include "dhcp6-protocol.h" #include "dhcp6-protocol.h"
#include "dns-domain.h" #include "dns-domain.h"
#include "escape.h" #include "escape.h"
#include "memory-util.h" #include "memory-util.h"
#include "sparse-endian.h"
#include "strv.h" #include "strv.h"
#include "unaligned.h" #include "unaligned.h"
@ -210,15 +212,19 @@ bool dhcp6_option_can_request(uint16_t option) {
} }
static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) { static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) {
DHCP6Option *option;
assert_return(buf, -EINVAL); assert_return(buf, -EINVAL);
assert_return(*buf, -EINVAL); assert_return(*buf, -EINVAL);
assert_return(buflen, -EINVAL); assert_return(buflen, -EINVAL);
option = (DHCP6Option*) *buf;
if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data)) if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
return -ENOBUFS; return -ENOBUFS;
unaligned_write_be16(*buf + offsetof(DHCP6Option, code), optcode); option->code = htobe16(optcode);
unaligned_write_be16(*buf + offsetof(DHCP6Option, len), optlen); option->len = htobe16(optlen);
*buf += offsetof(DHCP6Option, data); *buf += offsetof(DHCP6Option, data);
*buflen -= offsetof(DHCP6Option, data); *buflen -= offsetof(DHCP6Option, data);
@ -244,7 +250,7 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
return 0; return 0;
} }
int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options) { int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
sd_dhcp6_option *options; sd_dhcp6_option *options;
int r; int r;
@ -253,7 +259,7 @@ int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet
assert(buflen); assert(buflen);
assert(vendor_options); assert(vendor_options);
ORDERED_SET_FOREACH(options, vendor_options) { ORDERED_HASHMAP_FOREACH(options, vendor_options) {
_cleanup_free_ uint8_t *p = NULL; _cleanup_free_ uint8_t *p = NULL;
size_t total; size_t total;
@ -276,33 +282,79 @@ int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet
return 0; return 0;
} }
static int option_append_ia_address(uint8_t **buf, size_t *buflen, const struct iaaddr *address) { int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
struct iaaddr a; size_t ia_buflen, ia_addrlen = 0;
struct ia_na ia_na;
struct ia_ta ia_ta;
DHCP6Address *addr;
uint8_t *ia_hdr;
uint16_t len;
void *p;
int r; int r;
assert(buf); assert_return(buf, -EINVAL);
assert(*buf); assert_return(*buf, -EINVAL);
assert(buflen); assert_return(buflen, -EINVAL);
assert(address); assert_return(ia, -EINVAL);
/* Do not append T1 and T2. */ /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
a = (struct iaaddr) {
.address = address->address,
};
r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr)); switch (ia->type) {
if (r < 0) case SD_DHCP6_OPTION_IA_NA:
return r; len = DHCP6_OPTION_IA_NA_LEN;
ia_na = (struct ia_na) {
.id = ia->ia_na.id,
};
p = &ia_na;
break;
memcpy(*buf, &a, sizeof(struct iaaddr)); case SD_DHCP6_OPTION_IA_TA:
len = DHCP6_OPTION_IA_TA_LEN;
ia_ta = (struct ia_ta) {
.id = ia->ia_ta.id,
};
p = &ia_ta;
break;
*buf += sizeof(struct iaaddr); default:
*buflen -= sizeof(struct iaaddr); return -EINVAL;
}
return offsetof(DHCP6Option, data) + sizeof(struct iaaddr); if (*buflen < offsetof(DHCP6Option, data) + len)
return -ENOBUFS;
ia_hdr = *buf;
ia_buflen = *buflen;
*buf += offsetof(DHCP6Option, data);
*buflen -= offsetof(DHCP6Option, data);
memcpy(*buf, p, len);
*buf += len;
*buflen -= len;
LIST_FOREACH(addresses, addr, ia->addresses) {
struct iaaddr a = {
.address = addr->iaaddr.address,
};
r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr));
if (r < 0)
return r;
memcpy(*buf, &a, sizeof(struct iaaddr));
*buf += sizeof(struct iaaddr);
*buflen -= sizeof(struct iaaddr);
ia_addrlen += offsetof(DHCP6Option, data) + sizeof(struct iaaddr);
}
return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
} }
static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const struct iapdprefix *prefix) { static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Address *prefix) {
struct iapdprefix p; struct iapdprefix p;
int r; int r;
@ -311,13 +363,14 @@ static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const struct i
assert(buflen); assert(buflen);
assert(prefix); assert(prefix);
if (prefix->prefixlen == 0) if (prefix->iapdprefix.prefixlen == 0)
return -EINVAL; return -EINVAL;
/* Do not append T1 and T2. */ /* Do not append T1 and T2. */
p = (struct iapdprefix) { p = (struct iapdprefix) {
.prefixlen = prefix->prefixlen, .prefixlen = prefix->iapdprefix.prefixlen,
.address = prefix->address, .address = prefix->iapdprefix.address,
}; };
r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix)); r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix));
@ -332,67 +385,57 @@ static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const struct i
return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix); return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix);
} }
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix) {
struct ia_header header; struct ia_pd ia_pd;
const DHCP6Address *addr; size_t len, pd_buflen;
size_t ia_buflen; uint8_t *pd_hdr;
uint8_t *ia_hdr;
uint16_t len;
int r; int r;
assert_return(buf, -EINVAL); assert_return(buf, -EINVAL);
assert_return(*buf, -EINVAL); assert_return(*buf, -EINVAL);
assert_return(buflen, -EINVAL); assert_return(buflen, -EINVAL);
assert_return(ia, -EINVAL); assert_return(pd, -EINVAL);
assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
/* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */ /* Do not set T1 and T2. */
ia_pd = (struct ia_pd) {
switch (ia->type) { .id = pd->ia_pd.id,
case SD_DHCP6_OPTION_IA_NA: };
case SD_DHCP6_OPTION_IA_PD: len = sizeof(struct ia_pd);
len = sizeof(struct ia_header);
header = (struct ia_header) {
.id = ia->header.id,
};
break;
case SD_DHCP6_OPTION_IA_TA:
len = sizeof(be32_t); /* IA_TA does not have lifetime. */
header = (struct ia_header) {
.id = ia->header.id,
};
break;
default:
assert_not_reached();
}
if (*buflen < offsetof(DHCP6Option, data) + len) if (*buflen < offsetof(DHCP6Option, data) + len)
return -ENOBUFS; return -ENOBUFS;
ia_hdr = *buf; pd_hdr = *buf;
ia_buflen = *buflen; pd_buflen = *buflen;
/* The header will be written at the end of this function. */ /* The header will be written at the end of this function. */
*buf += offsetof(DHCP6Option, data); *buf += offsetof(DHCP6Option, data);
*buflen -= offsetof(DHCP6Option, data); *buflen -= offsetof(DHCP6Option, data);
memcpy(*buf, &header, len); memcpy(*buf, &ia_pd, len);
*buf += len;
*buflen -= len;
LIST_FOREACH(addresses, addr, ia->addresses) { *buf += sizeof(struct ia_pd);
if (ia->type == SD_DHCP6_OPTION_IA_PD) *buflen -= sizeof(struct ia_pd);
r = option_append_pd_prefix(buf, buflen, &addr->iapdprefix);
else DHCP6Address *prefix;
r = option_append_ia_address(buf, buflen, &addr->iaaddr); LIST_FOREACH(addresses, prefix, pd->addresses) {
r = option_append_pd_prefix(buf, buflen, prefix);
if (r < 0) if (r < 0)
return r; return r;
len += r; len += r;
} }
return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len); if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
r = option_append_pd_prefix(buf, buflen, hint_pd_prefix);
if (r < 0)
return r;
len += r;
}
return option_append_hdr(&pd_hdr, &pd_buflen, pd->type, len);
} }
int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
@ -505,6 +548,7 @@ int dhcp6_option_parse(
size_t *ret_option_data_len, size_t *ret_option_data_len,
const uint8_t **ret_option_data) { const uint8_t **ret_option_data) {
const DHCP6Option *option;
size_t len; size_t len;
assert(buf); assert(buf);
@ -519,15 +563,16 @@ int dhcp6_option_parse(
if (*offset >= buflen - offsetof(DHCP6Option, data)) if (*offset >= buflen - offsetof(DHCP6Option, data))
return -EBADMSG; return -EBADMSG;
len = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, len)); option = (const DHCP6Option*) (buf + *offset);
len = be16toh(option->len);
if (len > buflen - offsetof(DHCP6Option, data) - *offset) if (len > buflen - offsetof(DHCP6Option, data) - *offset)
return -EBADMSG; return -EBADMSG;
*ret_option_code = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, code));
*ret_option_data_len = len;
*ret_option_data = buf + *offset + offsetof(DHCP6Option, data);
*offset += offsetof(DHCP6Option, data) + len; *offset += offsetof(DHCP6Option, data) + len;
*ret_option_code = be16toh(option->code);
*ret_option_data_len = len;
*ret_option_data = option->data;
return 0; return 0;
} }
@ -556,7 +601,7 @@ int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_s
static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) { static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
int r; int r;
assert(buf || buflen == 0); assert(buf);
for(size_t offset = 0; offset < buflen;) { for(size_t offset = 0; offset < buflen;) {
const uint8_t *data; const uint8_t *data;
@ -574,15 +619,15 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
r = dhcp6_option_parse_status(data, data_len, &msg); r = dhcp6_option_parse_status(data, data_len, &msg);
if (r == -ENOMEM) if (r == -ENOMEM)
return r; return r;
if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an IA address or PD prefix option with non-zero status: %s%s%s",
strempty(msg), isempty(msg) ? "" : ": ",
dhcp6_message_status_to_string(r));
if (r < 0) if (r < 0)
/* Let's log but ignore the invalid status option. */ /* Let's log but ignore the invalid status option. */
log_dhcp6_client_errno(client, r, log_dhcp6_client_errno(client, r,
"Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m"); "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m");
else if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an IA address or PD prefix option with non-zero status: %s%s%s",
strempty(msg), isempty(msg) ? "" : ": ",
dhcp6_message_status_to_string(r));
break; break;
} }
default: default:
@ -593,29 +638,19 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
return 0; return 0;
} }
static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) { static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) {
_cleanup_free_ DHCP6Address *a = NULL;
uint32_t lt_valid, lt_pref; uint32_t lt_valid, lt_pref;
DHCP6Address *a;
int r; int r;
assert(ia); assert(data);
assert(data || len == 0); assert(ret);
if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an IA address sub-option in an invalid option, ignoring.");
if (len < sizeof(struct iaaddr)) if (len < sizeof(struct iaaddr))
return -EBADMSG; return -EBADMSG;
a = new(DHCP6Address, 1); lt_valid = be32toh(((const struct iaaddr*) data)->lifetime_valid);
if (!a) lt_pref = be32toh(((const struct iaaddr*) data)->lifetime_preferred);
return -ENOMEM;
memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
lt_valid = be32toh(a->iaaddr.lifetime_valid);
lt_pref = be32toh(a->iaaddr.lifetime_preferred);
if (lt_valid == 0) if (lt_valid == 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
@ -632,33 +667,27 @@ static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, c
return r; return r;
} }
LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
return 0;
}
static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
_cleanup_free_ DHCP6Address *a = NULL;
uint32_t lt_valid, lt_pref;
int r;
assert(ia);
assert(data || len == 0);
if (ia->type != SD_DHCP6_OPTION_IA_PD)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an PD prefix sub-option in an invalid option, ignoring");
if (len < sizeof(struct iapdprefix))
return -EBADMSG;
a = new(DHCP6Address, 1); a = new(DHCP6Address, 1);
if (!a) if (!a)
return -ENOMEM; return -ENOMEM;
memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix)); LIST_INIT(addresses, a);
memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
lt_valid = be32toh(a->iapdprefix.lifetime_valid); *ret = a;
lt_pref = be32toh(a->iapdprefix.lifetime_preferred); return 0;
}
static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) {
uint32_t lt_valid, lt_pref;
DHCP6Address *a;
int r;
if (len < sizeof(struct iapdprefix))
return -ENOMSG;
lt_valid = be32toh(((const struct iapdprefix*) data)->lifetime_valid);
lt_pref = be32toh(((const struct iapdprefix*) data)->lifetime_preferred);
if (lt_valid == 0) if (lt_valid == 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
@ -675,7 +704,14 @@ static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia,
return r; return r;
} }
LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a)); a = new(DHCP6Address, 1);
if (!a)
return -ENOMEM;
LIST_INIT(addresses, a);
memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
*ret = a;
return 0; return 0;
} }
@ -685,15 +721,16 @@ int dhcp6_option_parse_ia(
uint16_t option_code, uint16_t option_code,
size_t option_data_len, size_t option_data_len,
const uint8_t *option_data, const uint8_t *option_data,
DHCP6IA **ret) { DHCP6IA *ret) {
_cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {};
uint32_t lt_t1, lt_t2; uint32_t lt_t1, lt_t2, lt_min = UINT32_MAX;
size_t header_len; be32_t received_iaid;
size_t offset;
int r; int r;
assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD)); assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD));
assert(option_data || option_data_len == 0); assert(option_data);
assert(ret); assert(ret);
/* This will return the following: /* This will return the following:
@ -706,51 +743,56 @@ int dhcp6_option_parse_ia(
switch (option_code) { switch (option_code) {
case SD_DHCP6_OPTION_IA_NA: case SD_DHCP6_OPTION_IA_NA:
if (option_data_len < DHCP6_OPTION_IA_NA_LEN)
return -EBADMSG;
offset = DHCP6_OPTION_IA_NA_LEN;
received_iaid = ((const struct ia_na*) option_data)->id;
lt_t1 = be32toh(((const struct ia_na*) option_data)->lifetime_t1);
lt_t2 = be32toh(((const struct ia_na*) option_data)->lifetime_t2);
break;
case SD_DHCP6_OPTION_IA_PD: case SD_DHCP6_OPTION_IA_PD:
header_len = sizeof(struct ia_header);
if (option_data_len < DHCP6_OPTION_IA_PD_LEN)
return -EBADMSG;
offset = DHCP6_OPTION_IA_PD_LEN;
received_iaid = ((const struct ia_pd*) option_data)->id;
lt_t1 = be32toh(((const struct ia_pd*) option_data)->lifetime_t1);
lt_t2 = be32toh(((const struct ia_pd*) option_data)->lifetime_t2);
break; break;
case SD_DHCP6_OPTION_IA_TA: case SD_DHCP6_OPTION_IA_TA:
header_len = sizeof(be32_t); /* IA_TA does not have lifetime. */ if (option_data_len < DHCP6_OPTION_IA_TA_LEN)
return -ENOMSG;
offset = DHCP6_OPTION_IA_TA_LEN;
received_iaid = ((const struct ia_ta*) option_data)->id;
lt_t1 = lt_t2 = 0; /* No lifetime for IA_TA. */
break; break;
default: default:
assert_not_reached(); assert_not_reached();
} }
if (option_data_len < header_len)
return -EBADMSG;
ia = new(DHCP6IA, 1);
if (!ia)
return -ENOMEM;
*ia = (DHCP6IA) {
.type = option_code,
};
memcpy(&ia->header, option_data, header_len);
/* According to RFC8415, IAs which do not match the client's IAID should be ignored, /* According to RFC8415, IAs which do not match the client's IAID should be ignored,
* but not necessary to ignore or refuse the whole message. */ * but not necessary to ignore or refuse the whole message. */
if (ia->header.id != iaid) if (received_iaid != iaid)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO), return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO),
"Received an IA option with a different IAID " "Received an IA option with a different IAID "
"from the one chosen by the client, ignoring."); "from the one chosen by the client, ignoring.");
/* It is not necessary to check if the lifetime_t2 is zero here, as in that case it will be updated later. */
lt_t1 = be32toh(ia->header.lifetime_t1);
lt_t2 = be32toh(ia->header.lifetime_t2);
if (lt_t1 > lt_t2) if (lt_t1 > lt_t2)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an IA option with T1 %"PRIu32"sec > T2 %"PRIu32"sec, ignoring.", "Received an IA option with T1 %"PRIu32"sec > T2 %"PRIu32"sec, ignoring.",
lt_t1, lt_t2); lt_t1, lt_t2);
if (lt_t1 == 0 && lt_t2 > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an IA option with zero T1 and non-zero T2 (%"PRIu32"sec), ignoring.",
lt_t2);
for (size_t offset = header_len; offset < option_data_len;) { for (; offset < option_data_len;) {
const uint8_t *subdata; const uint8_t *subdata;
size_t subdata_len; size_t subdata_len;
uint16_t subopt; uint16_t subopt;
@ -761,19 +803,41 @@ int dhcp6_option_parse_ia(
switch (subopt) { switch (subopt) {
case SD_DHCP6_OPTION_IAADDR: { case SD_DHCP6_OPTION_IAADDR: {
r = dhcp6_option_parse_ia_address(client, ia, subdata, subdata_len); DHCP6Address *a;
if (!IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
log_dhcp6_client(client, "Received an IA_PD option with an IA address, ignoring.");
continue;
}
r = dhcp6_option_parse_ia_address(client, subdata, subdata_len, &a);
if (r == -ENOMEM) if (r == -ENOMEM)
return r; return r;
if (r < 0)
/* Ignore the sub-option on non-critical errors. */
continue;
/* Ignore non-critical errors in the sub-option. */ lt_min = MIN(lt_min, be32toh(a->iaaddr.lifetime_valid));
LIST_PREPEND(addresses, ia.addresses, a);
break; break;
} }
case SD_DHCP6_OPTION_IA_PD_PREFIX: { case SD_DHCP6_OPTION_IA_PD_PREFIX: {
r = dhcp6_option_parse_ia_pdprefix(client, ia, subdata, subdata_len); DHCP6Address *a;
if (option_code != SD_DHCP6_OPTION_IA_PD) {
log_dhcp6_client(client, "Received an IA_NA or IA_TA option with an PD prefix, ignoring");
continue;
}
r = dhcp6_option_parse_ia_pdprefix(client, subdata, subdata_len, &a);
if (r == -ENOMEM) if (r == -ENOMEM)
return r; return r;
if (r < 0)
/* Ignore the sub-option on non-critical errors. */
continue;
/* Ignore non-critical errors in the sub-option. */ lt_min = MIN(lt_min, be32toh(a->iapdprefix.lifetime_valid));
LIST_PREPEND(addresses, ia.addresses, a);
break; break;
} }
case SD_DHCP6_OPTION_STATUS_CODE: { case SD_DHCP6_OPTION_STATUS_CODE: {
@ -782,14 +846,14 @@ int dhcp6_option_parse_ia(
r = dhcp6_option_parse_status(subdata, subdata_len, &msg); r = dhcp6_option_parse_status(subdata, subdata_len, &msg);
if (r == -ENOMEM) if (r == -ENOMEM)
return r; return r;
if (r > 0) if (r < 0)
log_dhcp6_client_errno(client, r,
"Received an IA option with an invalid status sub option, ignoring: %m");
else if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received an IA option with non-zero status: %s%s%s", "Received an IA option with non-zero status: %s%s%s",
strempty(msg), isempty(msg) ? "" : ": ", strempty(msg), isempty(msg) ? "" : ": ",
dhcp6_message_status_to_string(r)); dhcp6_message_status_to_string(r));
if (r < 0)
log_dhcp6_client_errno(client, r,
"Received an IA option with an invalid status sub option, ignoring: %m");
break; break;
} }
default: default:
@ -797,11 +861,50 @@ int dhcp6_option_parse_ia(
} }
} }
if (!ia->addresses) if (!ia.addresses)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA), return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA),
"Received an IA option without valid IA addresses or PD prefixes, ignoring."); "Received an IA option without valid IA addresses or PD prefixes, ignoring.");
*ret = TAKE_PTR(ia); if (IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_PD) &&
lt_t1 == 0 && lt_t2 == 0 && lt_min != UINT32_MAX) {
lt_t1 = lt_min / 2;
lt_t2 = lt_min / 10 * 8;
log_dhcp6_client(client, "Received an IA option with both T1 and T2 equal to zero. "
"Adjusting them based on the minimum valid lifetime of IA addresses or PD prefixes: "
"T1=%"PRIu32"sec, T2=%"PRIu32"sec", lt_t1, lt_t2);
}
switch(option_code) {
case SD_DHCP6_OPTION_IA_NA:
*ret = (DHCP6IA) {
.type = option_code,
.ia_na.id = iaid,
.ia_na.lifetime_t1 = htobe32(lt_t1),
.ia_na.lifetime_t2 = htobe32(lt_t2),
.addresses = TAKE_PTR(ia.addresses),
};
break;
case SD_DHCP6_OPTION_IA_TA:
*ret = (DHCP6IA) {
.type = option_code,
.ia_ta.id = iaid,
.addresses = TAKE_PTR(ia.addresses),
};
break;
case SD_DHCP6_OPTION_IA_PD:
*ret = (DHCP6IA) {
.type = option_code,
.ia_pd.id = iaid,
.ia_pd.lifetime_t1 = htobe32(lt_t1),
.ia_pd.lifetime_t2 = htobe32(lt_t2),
.addresses = TAKE_PTR(ia.addresses),
};
break;
default:
assert_not_reached();
}
return 0; return 0;
} }

View File

@ -1,104 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-dhcp6-client.h"
#include "hash-funcs.h"
#include "list.h"
#include "macro.h"
#include "ordered-set.h"
#include "sparse-endian.h"
typedef struct sd_dhcp6_option {
unsigned n_ref;
uint32_t enterprise_identifier;
uint16_t option;
void *data;
size_t length;
} sd_dhcp6_option;
extern const struct hash_ops dhcp6_option_hash_ops;
/* Common option header */
typedef struct DHCP6Option {
be16_t code;
be16_t len;
uint8_t data[];
} _packed_ DHCP6Option;
/* Address option */
struct iaaddr {
struct in6_addr address;
be32_t lifetime_preferred;
be32_t lifetime_valid;
} _packed_;
/* Prefix Delegation Prefix option */
struct iapdprefix {
be32_t lifetime_preferred;
be32_t lifetime_valid;
uint8_t prefixlen;
struct in6_addr address;
} _packed_;
typedef struct DHCP6Address DHCP6Address;
struct DHCP6Address {
LIST_FIELDS(DHCP6Address, addresses);
union {
struct iaaddr iaaddr;
struct iapdprefix iapdprefix;
};
};
struct ia_header {
be32_t id;
be32_t lifetime_t1;
be32_t lifetime_t2;
} _packed_;
typedef struct DHCP6IA {
uint16_t type;
struct ia_header header;
LIST_HEAD(DHCP6Address, addresses);
} DHCP6IA;
void dhcp6_ia_clear_addresses(DHCP6IA *ia);
DHCP6IA *dhcp6_ia_free(DHCP6IA *ia);
DEFINE_TRIVIAL_CLEANUP_FUNC(DHCP6IA*, dhcp6_ia_free);
bool dhcp6_option_can_request(uint16_t option);
int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
size_t optlen, const void *optval);
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class);
int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class);
int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options);
int dhcp6_option_parse(
const uint8_t *buf,
size_t buflen,
size_t *offset,
uint16_t *ret_option_code,
size_t *ret_option_data_len,
const uint8_t **ret_option_data);
int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message);
int dhcp6_option_parse_ia(
sd_dhcp6_client *client,
be32_t iaid,
uint16_t option_code,
size_t option_data_len,
const uint8_t *option_data,
DHCP6IA **ret);
int dhcp6_option_parse_addresses(
const uint8_t *optval,
size_t optlen,
struct in6_addr **addrs,
size_t *count);
int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret);
int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret);

View File

@ -1,84 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dhcp6-protocol.h"
#include "string-table.h"
static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = {
[DHCP6_STATE_STOPPED] = "stopped",
[DHCP6_STATE_INFORMATION_REQUEST] = "information-request",
[DHCP6_STATE_SOLICITATION] = "solicitation",
[DHCP6_STATE_REQUEST] = "request",
[DHCP6_STATE_BOUND] = "bound",
[DHCP6_STATE_RENEW] = "renew",
[DHCP6_STATE_REBIND] = "rebind",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
static const char * const dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = {
[DHCP6_MESSAGE_SOLICIT] = "Solicit",
[DHCP6_MESSAGE_ADVERTISE] = "Advertise",
[DHCP6_MESSAGE_REQUEST] = "Request",
[DHCP6_MESSAGE_CONFIRM] = "Confirm",
[DHCP6_MESSAGE_RENEW] = "Renew",
[DHCP6_MESSAGE_REBIND] = "Rebind",
[DHCP6_MESSAGE_REPLY] = "Reply",
[DHCP6_MESSAGE_RELEASE] = "Release",
[DHCP6_MESSAGE_DECLINE] = "Decline",
[DHCP6_MESSAGE_RECONFIGURE] = "Reconfigure",
[DHCP6_MESSAGE_INFORMATION_REQUEST] = "Information Request",
[DHCP6_MESSAGE_RELAY_FORWARD] = "Relay Forward",
[DHCP6_MESSAGE_RELAY_REPLY] = "Relay Reply",
[DHCP6_MESSAGE_LEASE_QUERY] = "Lease Query",
[DHCP6_MESSAGE_LEASE_QUERY_REPLY] = "Lease Query Reply",
[DHCP6_MESSAGE_LEASE_QUERY_DONE] = "Lease Query Done",
[DHCP6_MESSAGE_LEASE_QUERY_DATA] = "Lease Query Data",
[DHCP6_MESSAGE_RECONFIGURE_REQUEST] = "Reconfigure Request",
[DHCP6_MESSAGE_RECONFIGURE_REPLY] = "Reconfigure Reply",
[DHCP6_MESSAGE_DHCPV4_QUERY] = "DHCPv4 Query",
[DHCP6_MESSAGE_DHCPV4_RESPONSE] = "DHCPv4 Response",
[DHCP6_MESSAGE_ACTIVE_LEASE_QUERY] = "Active Lease Query",
[DHCP6_MESSAGE_START_TLS] = "Start TLS",
[DHCP6_MESSAGE_BINDING_UPDATE] = "Binding Update",
[DHCP6_MESSAGE_BINDING_REPLY] = "Binding Reply",
[DHCP6_MESSAGE_POOL_REQUEST] = "Pool Request",
[DHCP6_MESSAGE_POOL_RESPONSE] = "Pool Response",
[DHCP6_MESSAGE_UPDATE_REQUEST] = "Update Request",
[DHCP6_MESSAGE_UPDATE_REQUEST_ALL] = "Update Request All",
[DHCP6_MESSAGE_UPDATE_DONE] = "Update Done",
[DHCP6_MESSAGE_CONNECT] = "Connect",
[DHCP6_MESSAGE_CONNECT_REPLY] = "Connect Reply",
[DHCP6_MESSAGE_DISCONNECT] = "Disconnect",
[DHCP6_MESSAGE_STATE] = "State",
[DHCP6_MESSAGE_CONTACT] = "Contact",
};
DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, DHCP6MessageType);
static const char * const dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
[DHCP6_STATUS_SUCCESS] = "Success",
[DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
[DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
[DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
[DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
[DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
[DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available",
[DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type",
[DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query",
[DHCP6_STATUS_NOT_CONFIGURED] = "Not configured",
[DHCP6_STATUS_NOT_ALLOWED] = "Not allowed",
[DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated",
[DHCP6_STATUS_DATA_MISSING] = "Data missing",
[DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete",
[DHCP6_STATUS_NOT_SUPPORTED] = "Not supported",
[DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused",
[DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use",
[DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict",
[DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information",
[DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
[DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down",
[DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported",
[DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew",
};
DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, DHCP6Status);

View File

@ -28,8 +28,8 @@ typedef struct DHCP6Message DHCP6Message;
#define DHCP6_MIN_OPTIONS_SIZE \ #define DHCP6_MIN_OPTIONS_SIZE \
1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr) 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ #define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
enum { enum {
@ -100,15 +100,13 @@ typedef enum DHCP6MessageType {
DHCP6_MESSAGE_STATE = 34, /* RFC 8156 */ DHCP6_MESSAGE_STATE = 34, /* RFC 8156 */
DHCP6_MESSAGE_CONTACT = 35, /* RFC 8156 */ DHCP6_MESSAGE_CONTACT = 35, /* RFC 8156 */
_DHCP6_MESSAGE_TYPE_MAX, _DHCP6_MESSAGE_TYPE_MAX,
_DHCP6_MESSAGE_TYPE_INVALID = -EINVAL, _DHCP6_MESSAGE_TYPE_INVALID = -EINVAL,
} DHCP6MessageType; } DHCP6MessageType;
typedef enum DHCP6NTPSubOption { typedef enum DHCP6NTPSubOption {
DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, DHCP6_NTP_SUBOPTION_SRV_ADDR = 1,
DHCP6_NTP_SUBOPTION_MC_ADDR = 2, DHCP6_NTP_SUBOPTION_MC_ADDR = 2,
DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, DHCP6_NTP_SUBOPTION_SRV_FQDN = 3,
_DHCP6_NTP_SUBOPTION_MAX,
_DHCP6_NTP_SUBOPTION_INVALID = -EINVAL,
} DHCP6NTPSubOption; } DHCP6NTPSubOption;
/* /*
@ -140,7 +138,7 @@ typedef enum DHCP6Status {
DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21, DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21,
DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22, DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22,
_DHCP6_STATUS_MAX, _DHCP6_STATUS_MAX,
_DHCP6_STATUS_INVALID = -EINVAL, _DHCP6_STATUS_INVALID = -EINVAL,
} DHCP6Status; } DHCP6Status;
typedef enum DHCP6FQDNFlag { typedef enum DHCP6FQDNFlag {
@ -148,9 +146,3 @@ typedef enum DHCP6FQDNFlag {
DHCP6_FQDN_FLAG_O = 1 << 1, DHCP6_FQDN_FLAG_O = 1 << 1,
DHCP6_FQDN_FLAG_N = 1 << 2, DHCP6_FQDN_FLAG_N = 1 << 2,
} DHCP6FQDNFlag; } DHCP6FQDNFlag;
const char *dhcp6_state_to_string(DHCP6State s) _const_;
const char *dhcp6_message_type_to_string(DHCP6MessageType s) _const_;
DHCP6MessageType dhcp6_message_type_from_string(const char *s) _pure_;
const char *dhcp6_message_status_to_string(DHCP6Status s) _const_;
DHCP6Status dhcp6_message_status_from_string(const char *s) _pure_;

View File

@ -0,0 +1,61 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "fuzz.h"
#include "sd-dhcp6-client.c"
int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
const void *packet, size_t len) {
return len;
}
int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
int fd;
fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
assert_se(fd >= 0);
return fd;
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
triple_timestamp t = {};
usec_t time_now;
int r;
if (size < sizeof(DHCP6Message))
return 0;
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_dhcp6_client_new(&client) >= 0);
assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
assert_se(sd_dhcp6_client_set_ifindex(client, 42) == 0);
assert_se(sd_dhcp6_client_set_fqdn(client, "example.com") == 1);
assert_se(sd_dhcp6_client_set_request_mud_url(client, "https://www.example.com/mudfile.json") >= 0);
assert_se(sd_dhcp6_client_set_request_user_class(client, STRV_MAKE("u1", "u2", "u3")) >= 0);
assert_se(sd_dhcp6_client_set_request_vendor_class(client, STRV_MAKE("v1", "v2", "v3")) >= 0);
assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
assert_se(sd_dhcp6_client_set_information_request(client, false) == 0);
dhcp6_client_set_test_mode(client, true);
assert_se(sd_dhcp6_client_start(client) >= 0);
assert_se(sd_dhcp6_client_set_transaction_id(client, htobe32(0x00ffffff) & ((const DHCP6Message *) data)->transaction_id) == 0);
triple_timestamp_get(&t);
r = client_receive_advertise(client, (DHCP6Message *) data, size, &t, NULL);
if (r < 0)
goto cleanup;
r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
goto cleanup;
if (r == DHCP6_STATE_REQUEST)
client->state = DHCP6_STATE_REQUEST;
(void) client_send_message(client, time_now);
cleanup:
assert_se(sd_dhcp6_client_stop(client) >= 0);
return 0;
}

View File

@ -6,59 +6,43 @@
#include "sd-event.h" #include "sd-event.h"
#include "dhcp6-internal.h" #include "dhcp6-internal.h"
#include "event-util.h" #include "dhcp6-protocol.h"
#include "fd-util.h" #include "fd-util.h"
#include "fuzz.h" #include "fuzz.h"
static int test_dhcp_fd[2] = { -1, -1 }; static int test_dhcp_fd[2] = { -1, -1 };
int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, const void *packet, size_t len) { int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
const void *packet, size_t len) {
return len; return len;
} }
int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0); assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0);
return TAKE_FD(test_dhcp_fd[0]); return test_dhcp_fd[0];
} }
static void fuzz_client(sd_dhcp6_client *client, const uint8_t *data, size_t size, DHCP6State state) { static void fuzz_client(const uint8_t *data, size_t size, bool is_information_request_enabled) {
assert_se(sd_dhcp6_client_set_information_request(client, state == DHCP6_STATE_INFORMATION_REQUEST) >= 0); _cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_dhcp6_client_new(&client) >= 0);
assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
assert_se(sd_dhcp6_client_set_ifindex(client, 42) == 0);
assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
assert_se(sd_dhcp6_client_set_information_request(client, is_information_request_enabled) == 0);
dhcp6_client_set_test_mode(client, true);
assert_se(sd_dhcp6_client_start(client) >= 0); assert_se(sd_dhcp6_client_start(client) >= 0);
client->state = state;
if (size >= sizeof(DHCP6Message)) if (size >= sizeof(DHCP6Message))
assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message *) data)->transaction_id) == 0); assert_se(sd_dhcp6_client_set_transaction_id(client, htobe32(0x00ffffff) & ((const DHCP6Message *) data)->transaction_id) == 0);
/* These states does not require lease to send message. */
if (IN_SET(client->state, DHCP6_STATE_INFORMATION_REQUEST, DHCP6_STATE_SOLICITATION))
assert_se(dhcp6_client_send_message(client) >= 0);
assert_se(write(test_dhcp_fd[1], data, size) == (ssize_t) size); assert_se(write(test_dhcp_fd[1], data, size) == (ssize_t) size);
assert_se(sd_event_run(sd_dhcp6_client_get_event(client), UINT64_MAX) > 0); sd_event_run(e, UINT64_MAX);
/* Check the state transition. */
if (client->state != state)
switch (state) {
case DHCP6_STATE_INFORMATION_REQUEST:
assert_se(client->state == DHCP6_STATE_STOPPED);
break;
case DHCP6_STATE_SOLICITATION:
assert_se(IN_SET(client->state, DHCP6_STATE_REQUEST, DHCP6_STATE_BOUND));
break;
case DHCP6_STATE_REQUEST:
assert_se(client->state == DHCP6_STATE_BOUND);
break;
default:
assert_not_reached();
}
/* Send message if the client has a lease. */
if (state != DHCP6_STATE_INFORMATION_REQUEST && sd_dhcp6_client_get_lease(client, NULL) >= 0) {
client->state = DHCP6_STATE_REQUEST;
dhcp6_client_send_message(client);
}
assert_se(sd_dhcp6_client_stop(client) >= 0); assert_se(sd_dhcp6_client_stop(client) >= 0);
@ -66,43 +50,14 @@ static void fuzz_client(sd_dhcp6_client *client, const uint8_t *data, size_t siz
} }
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
_cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *v1 = NULL, *v2 = NULL;
struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
struct in6_addr hint = { { { 0x3f, 0xfe, 0x05, 0x01, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } };
static const char *v1_data = "hogehoge", *v2_data = "foobar";
if (size > 65536) if (size > 65536)
return 0; return 0;
assert_se(sd_event_new(&e) >= 0); /* This triggers client_receive_advertise */
assert_se(sd_dhcp6_client_new(&client) >= 0); fuzz_client(data, size, false);
assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0);
assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
dhcp6_client_set_test_mode(client, true);
/* Used when sending message. */ /* This triggers client_receive_reply */
assert_se(sd_dhcp6_client_set_fqdn(client, "example.com") == 1); fuzz_client(data, size, true);
assert_se(sd_dhcp6_client_set_request_mud_url(client, "https://www.example.com/mudfile.json") >= 0);
assert_se(sd_dhcp6_client_set_request_user_class(client, STRV_MAKE("u1", "u2", "u3")) >= 0);
assert_se(sd_dhcp6_client_set_request_vendor_class(client, STRV_MAKE("v1", "v2", "v3")) >= 0);
assert_se(sd_dhcp6_client_set_prefix_delegation_hint(client, 48, &hint) >= 0);
assert_se(sd_dhcp6_option_new(123, v1_data, strlen(v1_data), 12345, &v1) >= 0);
assert_se(sd_dhcp6_option_new(456, v2_data, strlen(v2_data), 45678, &v2) >= 0);
assert_se(sd_dhcp6_client_add_vendor_option(client, v1) >= 0);
assert_se(sd_dhcp6_client_add_vendor_option(client, v2) >= 0);
fuzz_client(client, data, size, DHCP6_STATE_INFORMATION_REQUEST);
fuzz_client(client, data, size, DHCP6_STATE_SOLICITATION);
/* If size is zero, then the resend timer will be triggered at first,
* but in the REQUEST state the client must have a lease. */
if (size == 0)
return 0;
fuzz_client(client, data, size, DHCP6_STATE_REQUEST);
return 0; return 0;
} }

View File

@ -17,8 +17,6 @@ sources = files('''
dhcp6-lease-internal.h dhcp6-lease-internal.h
dhcp6-network.c dhcp6-network.c
dhcp6-option.c dhcp6-option.c
dhcp6-option.h
dhcp6-protocol.c
dhcp6-protocol.h dhcp6-protocol.h
icmp6-util.c icmp6-util.c
icmp6-util.h icmp6-util.h
@ -115,6 +113,10 @@ fuzzers += [
[libshared, [libshared,
libsystemd_network]], libsystemd_network]],
[files('fuzz-dhcp6-client-send.c'),
[libshared,
libsystemd_network]],
[files('fuzz-dhcp-server.c'), [files('fuzz-dhcp-server.c'),
[libsystemd_network, [libsystemd_network,
libshared]], libshared]],

View File

@ -450,7 +450,7 @@ static int dhcp_client_set_iaid_duid_internal(
bool iaid_append, bool iaid_append,
bool iaid_set, bool iaid_set,
uint32_t iaid, uint32_t iaid,
DUIDType duid_type, uint16_t duid_type,
const void *duid, const void *duid,
size_t duid_len, size_t duid_len,
usec_t llt_time) { usec_t llt_time) {
@ -489,20 +489,37 @@ static int dhcp_client_set_iaid_duid_internal(
client->client_id.ns.duid.type = htobe16(duid_type); client->client_id.ns.duid.type = htobe16(duid_type);
memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
len = sizeof(client->client_id.ns.duid.type) + duid_len; len = sizeof(client->client_id.ns.duid.type) + duid_len;
} else
switch (duid_type) {
case DUID_TYPE_LLT:
if (client->mac_addr_len == 0)
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
} else { r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
r = dhcp_identifier_set_duid(duid_type, client->mac_addr, client->mac_addr_len, if (r < 0)
client->arp_type, llt_time, client->test_mode, return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m");
&client->client_id.ns.duid, &len); break;
if (r == -EOPNOTSUPP) case DUID_TYPE_EN:
return log_dhcp_client_errno(client, r, r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
"Failed to set %s. MAC address is not set or " if (r < 0)
"interface type is not supported.", return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m");
duid_type_to_string(duid_type)); break;
if (r < 0) case DUID_TYPE_LL:
return log_dhcp_client_errno(client, r, "Failed to set %s: %m", if (client->mac_addr_len == 0)
duid_type_to_string(duid_type)); return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
}
r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
if (r < 0)
return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m");
break;
case DUID_TYPE_UUID:
r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
if (r < 0)
return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m");
break;
default:
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
}
client->client_id_len = sizeof(client->client_id.type) + len + client->client_id_len = sizeof(client->client_id.type) + len +
(iaid_append ? sizeof(client->client_id.ns.iaid) : 0); (iaid_append ? sizeof(client->client_id.ns.iaid) : 0);
@ -859,7 +876,7 @@ static int client_message_init(
if (r < 0) if (r < 0)
return r; return r;
r = dhcp_identifier_set_duid_en(client->test_mode, &client->client_id.ns.duid, &duid_len); r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len);
if (r < 0) if (r < 0)
return r; return r;

File diff suppressed because it is too large Load Diff

View File

@ -6,21 +6,10 @@
#include <errno.h> #include <errno.h>
#include "alloc-util.h" #include "alloc-util.h"
#include "dhcp6-internal.h"
#include "dhcp6-lease-internal.h" #include "dhcp6-lease-internal.h"
#include "dhcp6-protocol.h"
#include "strv.h" #include "strv.h"
#include "util.h"
#define IRT_DEFAULT (1 * USEC_PER_DAY)
#define IRT_MINIMUM (600 * USEC_PER_SEC)
static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timestamp *timestamp) {
assert(lease);
if (timestamp && triple_timestamp_is_set(timestamp))
lease->timestamp = *timestamp;
else
triple_timestamp_get(&lease->timestamp);
}
int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) { int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) {
assert_return(lease, -EINVAL); assert_return(lease, -EINVAL);
@ -35,69 +24,6 @@ int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_
return 0; return 0;
} }
static usec_t sec2usec(uint32_t sec) {
return sec == UINT32_MAX ? USEC_INFINITY : sec * USEC_PER_SEC;
}
static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
uint32_t t1 = UINT32_MAX, t2 = UINT32_MAX, min_valid_lt = UINT32_MAX;
DHCP6Address *a;
assert(lease);
assert(lease->ia_na || lease->ia_pd);
if (lease->ia_na) {
t1 = MIN(t1, be32toh(lease->ia_na->header.lifetime_t1));
t2 = MIN(t2, be32toh(lease->ia_na->header.lifetime_t2));
LIST_FOREACH(addresses, a, lease->ia_na->addresses)
min_valid_lt = MIN(min_valid_lt, be32toh(a->iaaddr.lifetime_valid));
}
if (lease->ia_pd) {
t1 = MIN(t1, be32toh(lease->ia_pd->header.lifetime_t1));
t2 = MIN(t2, be32toh(lease->ia_pd->header.lifetime_t2));
LIST_FOREACH(addresses, a, lease->ia_pd->addresses)
min_valid_lt = MIN(min_valid_lt, be32toh(a->iapdprefix.lifetime_valid));
}
if (t2 == 0 || t2 > min_valid_lt) {
/* If T2 is zero or longer than the minimum valid lifetime of the addresses or prefixes,
* then adjust lifetime with it. */
t1 = min_valid_lt / 2;
t2 = min_valid_lt / 10 * 8;
}
lease->lifetime_valid = sec2usec(min_valid_lt);
lease->lifetime_t1 = sec2usec(t1);
lease->lifetime_t2 = sec2usec(t2);
}
int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid) {
assert(lease);
if (!lease->ia_na && !lease->ia_pd)
return -ENODATA;
if (ret_t1)
*ret_t1 = lease->lifetime_t1;
if (ret_t2)
*ret_t2 = lease->lifetime_t2;
if (ret_valid)
*ret_valid = lease->lifetime_valid;
return 0;
}
static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) {
assert(lease);
if (server_address)
lease->server_address = *server_address;
else
lease->server_address = (struct in6_addr) {};
}
int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) { int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
assert_return(lease, -EINVAL); assert_return(lease, -EINVAL);
assert_return(ret, -EINVAL); assert_return(ret, -EINVAL);
@ -106,37 +32,55 @@ int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *re
return 0; return 0;
} }
void dhcp6_ia_clear_addresses(DHCP6IA *ia) { int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
DHCP6Address *a, *n; DHCP6Address *addr;
uint32_t valid = 0, t;
assert(ia); assert_return(ia, -EINVAL);
assert_return(expire, -EINVAL);
LIST_FOREACH_SAFE(addresses, a, n, ia->addresses) LIST_FOREACH(addresses, addr, ia->addresses) {
free(a); t = be32toh(addr->iaaddr.lifetime_valid);
if (valid < t)
valid = t;
}
ia->addresses = NULL; t = be32toh(ia->ia_na.lifetime_t2);
if (t > valid)
return -EINVAL;
*expire = valid - t;
return 0;
} }
DHCP6IA *dhcp6_ia_free(DHCP6IA *ia) { DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
DHCP6Address *address;
if (!ia) if (!ia)
return NULL; return NULL;
dhcp6_ia_clear_addresses(ia); while (ia->addresses) {
address = ia->addresses;
return mfree(ia); LIST_REMOVE(addresses, ia->addresses, address);
free(address);
}
return NULL;
} }
int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) { int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
uint8_t *clientid = NULL; uint8_t *clientid;
assert(lease); assert_return(lease, -EINVAL);
assert(id || len == 0); assert_return(id, -EINVAL);
assert_return(len > 0, -EINVAL);
if (len > 0) { clientid = memdup(id, len);
clientid = memdup(id, len); if (!clientid)
if (!clientid) return -ENOMEM;
return -ENOMEM;
}
free_and_replace(lease->clientid, clientid); free_and_replace(lease->clientid, clientid);
lease->clientid_len = len; lease->clientid_len = len;
@ -145,7 +89,7 @@ int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t le
} }
int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) { int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
assert(lease); assert_return(lease, -EINVAL);
if (!lease->clientid) if (!lease->clientid)
return -ENODATA; return -ENODATA;
@ -159,16 +103,15 @@ int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *re
} }
int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) { int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
uint8_t *serverid = NULL; uint8_t *serverid;
assert(lease); assert_return(lease, -EINVAL);
assert(id || len == 0); assert_return(id, -EINVAL);
assert_return(len > 0, -EINVAL);
if (len > 0) { serverid = memdup(id, len);
serverid = memdup(id, len); if (!serverid)
if (!serverid) return -ENOMEM;
return -ENOMEM;
}
free_and_replace(lease->serverid, serverid); free_and_replace(lease->serverid, serverid);
lease->serverid_len = len; lease->serverid_len = len;
@ -177,7 +120,7 @@ int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t le
} }
int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) { int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
assert(lease); assert_return(lease, -EINVAL);
if (!lease->serverid) if (!lease->serverid)
return -ENODATA; return -ENODATA;
@ -186,99 +129,107 @@ int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *re
*ret_id = lease->serverid; *ret_id = lease->serverid;
if (ret_len) if (ret_len)
*ret_len = lease->serverid_len; *ret_len = lease->serverid_len;
return 0; return 0;
} }
int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) { int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
assert(lease); assert_return(lease, -EINVAL);
lease->preference = preference; lease->preference = preference;
return 0; return 0;
} }
int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret) { int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) {
assert(lease); assert_return(preference, -EINVAL);
assert(ret);
if (!lease)
return -EINVAL;
*preference = lease->preference;
*ret = lease->preference;
return 0; return 0;
} }
int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) { int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
assert(lease);
lease->rapid_commit = true;
return 0;
}
int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret) {
assert(lease);
assert(ret);
*ret = lease->rapid_commit;
return 0;
}
int sd_dhcp6_lease_get_address(
sd_dhcp6_lease *lease,
struct in6_addr *ret_addr,
uint32_t *ret_lifetime_preferred,
uint32_t *ret_lifetime_valid) {
assert_return(lease, -EINVAL); assert_return(lease, -EINVAL);
if (!lease->addr_iter) lease->rapid_commit = true;
return -ENODATA;
if (ret_addr) return 0;
*ret_addr = lease->addr_iter->iaaddr.address; }
if (ret_lifetime_preferred)
*ret_lifetime_preferred = be32toh(lease->addr_iter->iaaddr.lifetime_preferred); int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) {
if (ret_lifetime_valid) assert_return(lease, -EINVAL);
*ret_lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid); assert_return(rapid_commit, -EINVAL);
*rapid_commit = lease->rapid_commit;
return 0;
}
int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr,
uint32_t *lifetime_preferred,
uint32_t *lifetime_valid) {
assert_return(lease, -EINVAL);
assert_return(addr, -EINVAL);
assert_return(lifetime_preferred, -EINVAL);
assert_return(lifetime_valid, -EINVAL);
if (!lease->addr_iter)
return -ENOMSG;
memcpy(addr, &lease->addr_iter->iaaddr.address,
sizeof(struct in6_addr));
*lifetime_preferred =
be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
*lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
lease->addr_iter = lease->addr_iter->addresses_next; lease->addr_iter = lease->addr_iter->addresses_next;
return 0; return 0;
} }
void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
if (lease) if (lease)
lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL; lease->addr_iter = lease->ia.addresses;
} }
int sd_dhcp6_lease_get_pd( int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
sd_dhcp6_lease *lease, uint8_t *prefix_len,
struct in6_addr *ret_prefix, uint32_t *lifetime_preferred,
uint8_t *ret_prefix_len, uint32_t *lifetime_valid) {
uint32_t *ret_lifetime_preferred,
uint32_t *ret_lifetime_valid) {
assert_return(lease, -EINVAL); assert_return(lease, -EINVAL);
assert_return(prefix, -EINVAL);
assert_return(prefix_len, -EINVAL);
assert_return(lifetime_preferred, -EINVAL);
assert_return(lifetime_valid, -EINVAL);
if (!lease->prefix_iter) if (!lease->prefix_iter)
return -ENODATA; return -ENOMSG;
if (ret_prefix) memcpy(prefix, &lease->prefix_iter->iapdprefix.address,
*ret_prefix = lease->prefix_iter->iapdprefix.address; sizeof(struct in6_addr));
if (ret_prefix_len) *prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
*ret_prefix_len = lease->prefix_iter->iapdprefix.prefixlen; *lifetime_preferred =
if (ret_lifetime_preferred) be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
*ret_lifetime_preferred = be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred); *lifetime_valid =
if (ret_lifetime_valid) be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
*ret_lifetime_valid = be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
lease->prefix_iter = lease->prefix_iter->addresses_next; lease->prefix_iter = lease->prefix_iter->addresses_next;
return 0; return 0;
} }
void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) { void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
if (lease) if (lease)
lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL; lease->prefix_iter = lease->pd.addresses;
} }
int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
assert(lease); assert_return(lease, -EINVAL);
assert(optval || optlen == 0); assert_return(optval, -EINVAL);
if (optlen == 0) if (optlen == 0)
return 0; return 0;
@ -290,7 +241,7 @@ int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret) {
assert_return(lease, -EINVAL); assert_return(lease, -EINVAL);
if (!lease->dns) if (!lease->dns)
return -ENODATA; return -ENOENT;
if (ret) if (ret)
*ret = lease->dns; *ret = lease->dns;
@ -302,8 +253,8 @@ int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t
_cleanup_strv_free_ char **domains = NULL; _cleanup_strv_free_ char **domains = NULL;
int r; int r;
assert(lease); assert_return(lease, -EINVAL);
assert(optval || optlen == 0); assert_return(optval, -EINVAL);
if (optlen == 0) if (optlen == 0)
return 0; return 0;
@ -320,7 +271,7 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
assert_return(ret, -EINVAL); assert_return(ret, -EINVAL);
if (!lease->domains) if (!lease->domains)
return -ENODATA; return -ENOENT;
*ret = lease->domains; *ret = lease->domains;
return strv_length(lease->domains); return strv_length(lease->domains);
@ -329,8 +280,8 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
int r; int r;
assert(lease); assert_return(lease, -EINVAL);
assert(optval || optlen == 0); assert_return(optval, -EINVAL);
for (size_t offset = 0; offset < optlen;) { for (size_t offset = 0; offset < optlen;) {
const uint8_t *subval; const uint8_t *subval;
@ -345,7 +296,7 @@ int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt
case DHCP6_NTP_SUBOPTION_SRV_ADDR: case DHCP6_NTP_SUBOPTION_SRV_ADDR:
case DHCP6_NTP_SUBOPTION_MC_ADDR: case DHCP6_NTP_SUBOPTION_MC_ADDR:
if (sublen != 16) if (sublen != 16)
return -EINVAL; return 0;
r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count); r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count);
if (r < 0) if (r < 0)
@ -375,8 +326,8 @@ int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt
} }
int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
assert(lease); assert_return(lease, -EINVAL);
assert(optval || optlen == 0); assert_return(optval, -EINVAL);
if (optlen == 0) if (optlen == 0)
return 0; return 0;
@ -401,14 +352,14 @@ int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **
return lease->sntp_count; return lease->sntp_count;
} }
return -ENODATA; return -ENOENT;
} }
int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) { int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
assert_return(lease, -EINVAL); assert_return(lease, -EINVAL);
if (!lease->ntp_fqdn) if (!lease->ntp_fqdn)
return -ENODATA; return -ENOENT;
if (ret) if (ret)
*ret = lease->ntp_fqdn; *ret = lease->ntp_fqdn;
@ -416,14 +367,11 @@ int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
} }
int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
char *fqdn;
int r; int r;
char *fqdn;
assert(lease); assert_return(lease, -EINVAL);
assert(optval || optlen == 0); assert_return(optval, -EINVAL);
if (optlen == 0)
return 0;
if (optlen < 2) if (optlen < 2)
return -ENODATA; return -ENODATA;
@ -442,222 +390,20 @@ int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) {
assert_return(ret, -EINVAL); assert_return(ret, -EINVAL);
if (!lease->fqdn) if (!lease->fqdn)
return -ENODATA; return -ENOENT;
*ret = lease->fqdn; *ret = lease->fqdn;
return 0; return 0;
} }
static int dhcp6_lease_parse_message(
sd_dhcp6_client *client,
sd_dhcp6_lease *lease,
const DHCP6Message *message,
size_t len) {
usec_t irt = IRT_DEFAULT;
int r;
assert(client);
assert(lease);
assert(message);
assert(len >= sizeof(DHCP6Message));
len -= sizeof(DHCP6Message);
for (size_t offset = 0; offset < len;) {
uint16_t optcode;
size_t optlen;
const uint8_t *optval;
r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
if (r < 0)
return r;
switch (optcode) {
case SD_DHCP6_OPTION_CLIENTID:
if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs",
dhcp6_message_type_to_string(message->type));
r = dhcp6_lease_set_clientid(lease, optval, optlen);
if (r < 0)
return r;
break;
case SD_DHCP6_OPTION_SERVERID:
if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs",
dhcp6_message_type_to_string(message->type));
r = dhcp6_lease_set_serverid(lease, optval, optlen);
if (r < 0)
return r;
break;
case SD_DHCP6_OPTION_PREFERENCE:
if (optlen != 1)
return -EINVAL;
r = dhcp6_lease_set_preference(lease, optval[0]);
if (r < 0)
return r;
break;
case SD_DHCP6_OPTION_STATUS_CODE: {
_cleanup_free_ char *msg = NULL;
r = dhcp6_option_parse_status(optval, optlen, &msg);
if (r < 0)
return r;
if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"Received %s message with non-zero status: %s%s%s",
dhcp6_message_type_to_string(message->type),
strempty(msg), isempty(msg) ? "" : ": ",
dhcp6_message_status_to_string(r));
break;
}
case SD_DHCP6_OPTION_IA_NA: {
_cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
break;
}
r = dhcp6_option_parse_ia(client, client->ia_na.header.id, optcode, optlen, optval, &ia);
if (r == -ENOMEM)
return r;
if (r < 0)
continue;
if (lease->ia_na) {
log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
continue;
}
dhcp6_ia_free(lease->ia_na);
lease->ia_na = TAKE_PTR(ia);
break;
}
case SD_DHCP6_OPTION_IA_PD: {
_cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
break;
}
r = dhcp6_option_parse_ia(client, client->ia_pd.header.id, optcode, optlen, optval, &ia);
if (r == -ENOMEM)
return r;
if (r < 0)
continue;
if (lease->ia_pd) {
log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
continue;
}
dhcp6_ia_free(lease->ia_pd);
lease->ia_pd = TAKE_PTR(ia);
break;
}
case SD_DHCP6_OPTION_RAPID_COMMIT:
r = dhcp6_lease_set_rapid_commit(lease);
if (r < 0)
return r;
break;
case SD_DHCP6_OPTION_DNS_SERVERS:
r = dhcp6_lease_add_dns(lease, optval, optlen);
if (r < 0)
log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m");
break;
case SD_DHCP6_OPTION_DOMAIN_LIST:
r = dhcp6_lease_add_domains(lease, optval, optlen);
if (r < 0)
log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m");
break;
case SD_DHCP6_OPTION_NTP_SERVER:
r = dhcp6_lease_add_ntp(lease, optval, optlen);
if (r < 0)
log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m");
break;
case SD_DHCP6_OPTION_SNTP_SERVERS:
r = dhcp6_lease_add_sntp(lease, optval, optlen);
if (r < 0)
log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m");
break;
case SD_DHCP6_OPTION_CLIENT_FQDN:
r = dhcp6_lease_set_fqdn(lease, optval, optlen);
if (r < 0)
log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m");
break;
case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
if (optlen != 4)
return -EINVAL;
irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
break;
}
}
uint8_t *clientid;
size_t clientid_len;
if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"%s message does not contain client ID. Ignoring.",
dhcp6_message_type_to_string(message->type));
if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"The client ID in %s message does not match. Ignoring.",
dhcp6_message_type_to_string(message->type));
if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
log_dhcp6_client(client, "New information request will be refused in %s.",
FORMAT_TIMESPAN(client->information_refresh_time_usec, USEC_PER_SEC));
} else {
r = dhcp6_lease_get_serverid(lease, NULL, NULL);
if (r < 0)
return log_dhcp6_client_errno(client, r, "%s has no server id",
dhcp6_message_type_to_string(message->type));
if (!lease->ia_na && !lease->ia_pd)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"No IA_PD prefix or IA_NA address received. Ignoring.");
dhcp6_lease_set_lifetime(lease);
}
return 0;
}
static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
if (!lease) if (!lease)
return NULL; return NULL;
free(lease->clientid); free(lease->clientid);
free(lease->serverid); free(lease->serverid);
dhcp6_ia_free(lease->ia_na); dhcp6_lease_free_ia(&lease->ia);
dhcp6_ia_free(lease->ia_pd); dhcp6_lease_free_ia(&lease->pd);
free(lease->dns); free(lease->dns);
free(lease->fqdn); free(lease->fqdn);
strv_free(lease->domains); strv_free(lease->domains);
@ -673,47 +419,14 @@ DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
int dhcp6_lease_new(sd_dhcp6_lease **ret) { int dhcp6_lease_new(sd_dhcp6_lease **ret) {
sd_dhcp6_lease *lease; sd_dhcp6_lease *lease;
assert(ret); lease = new0(sd_dhcp6_lease, 1);
lease = new(sd_dhcp6_lease, 1);
if (!lease) if (!lease)
return -ENOMEM; return -ENOMEM;
*lease = (sd_dhcp6_lease) { lease->n_ref = 1;
.n_ref = 1,
}; LIST_HEAD_INIT(lease->ia.addresses);
*ret = lease; *ret = lease;
return 0; return 0;
} }
int dhcp6_lease_new_from_message(
sd_dhcp6_client *client,
const DHCP6Message *message,
size_t len,
const triple_timestamp *timestamp,
const struct in6_addr *server_address,
sd_dhcp6_lease **ret) {
_cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
int r;
assert(client);
assert(message);
assert(len >= sizeof(DHCP6Message));
assert(ret);
r = dhcp6_lease_new(&lease);
if (r < 0)
return r;
dhcp6_lease_set_timestamp(lease, timestamp);
dhcp6_lease_set_server_address(lease, server_address);
r = dhcp6_lease_parse_message(client, lease, message, len);
if (r < 0)
return r;
*ret = TAKE_PTR(lease);
return 0;
}

View File

@ -170,7 +170,7 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us
struct duid duid; struct duid duid;
size_t duid_len; size_t duid_len;
assert_se(dhcp_identifier_set_duid_en(/* test_mode = */ true, &duid, &duid_len) >= 0); assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0);
assert_se(dhcp_identifier_set_iaid(42, mac_addr, ETH_ALEN, true, /* use_mac = */ true, &iaid) >= 0); assert_se(dhcp_identifier_set_iaid(42, mac_addr, ETH_ALEN, true, /* use_mac = */ true, &iaid) >= 0);
assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len); assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len);

File diff suppressed because it is too large Load Diff

View File

@ -432,20 +432,8 @@ int dhcp6_start_on_ra(Link *link, bool information_request) {
return r; return r;
if (inf_req == information_request) if (inf_req == information_request)
/* The client is already running in the requested mode. */
return 0; return 0;
if (!inf_req) {
log_link_debug(link,
"The DHCPv6 client is already running in the managed mode, "
"refusing to start the client in the information requesting mode.");
return 0;
}
log_link_debug(link,
"The DHCPv6 client is running in the information requesting mode. "
"Restarting the client in the managed mode.");
r = sd_dhcp6_client_stop(link->dhcp6_client); r = sd_dhcp6_client_stop(link->dhcp6_client);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -122,24 +122,15 @@
#define GPT_USR_X86_64_VERITY_SIG SD_ID128_MAKE(e7,bb,33,fb,06,cf,4e,81,82,73,e5,43,b4,13,e2,e2) #define GPT_USR_X86_64_VERITY_SIG SD_ID128_MAKE(e7,bb,33,fb,06,cf,4e,81,82,73,e5,43,b4,13,e2,e2)
#define GPT_USR_X86_VERITY_SIG SD_ID128_MAKE(97,4a,71,c0,de,41,43,c3,be,5d,5c,5c,cd,1a,d2,c0) #define GPT_USR_X86_VERITY_SIG SD_ID128_MAKE(97,4a,71,c0,de,41,43,c3,be,5d,5c,5c,cd,1a,d2,c0)
#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) #define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b)
#define GPT_ESP_STR SD_ID128_MAKE_UUID_STR(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) #define GPT_XBOOTLDR SD_ID128_MAKE(bc,13,c2,ff,59,e6,42,62,a3,52,b2,75,fd,6f,71,72)
#define GPT_XBOOTLDR SD_ID128_MAKE(bc,13,c2,ff,59,e6,42,62,a3,52,b2,75,fd,6f,71,72) #define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
#define GPT_XBOOTLDR_STR SD_ID128_MAKE_UUID_STR(bc,13,c2,ff,59,e6,42,62,a3,52,b2,75,fd,6f,71,72) #define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15)
#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) #define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
#define GPT_SWAP_STR SD_ID128_MAKE_UUID_STR(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) #define GPT_VAR SD_ID128_MAKE(4d,21,b0,16,b5,34,45,c2,a9,fb,5c,16,e0,91,fd,2d)
#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) #define GPT_TMP SD_ID128_MAKE(7e,c6,f5,57,3b,c5,4a,ca,b2,93,16,ef,5d,f6,39,d1)
#define GPT_HOME_STR SD_ID128_MAKE_UUID_STR(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) #define GPT_USER_HOME SD_ID128_MAKE(77,3f,91,ef,66,d4,49,b5,bd,83,d6,83,bf,40,ad,16)
#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) #define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4)
#define GPT_SRV_STR SD_ID128_MAKE_UUID_STR(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
#define GPT_VAR SD_ID128_MAKE(4d,21,b0,16,b5,34,45,c2,a9,fb,5c,16,e0,91,fd,2d)
#define GPT_VAR_STR SD_ID128_MAKE_UUID_STR(4d,21,b0,16,b5,34,45,c2,a9,fb,5c,16,e0,91,fd,2d)
#define GPT_TMP SD_ID128_MAKE(7e,c6,f5,57,3b,c5,4a,ca,b2,93,16,ef,5d,f6,39,d1)
#define GPT_TMP_STR SD_ID128_MAKE_UUID_STR(7e,c6,f5,57,3b,c5,4a,ca,b2,93,16,ef,5d,f6,39,d1)
#define GPT_USER_HOME SD_ID128_MAKE(77,3f,91,ef,66,d4,49,b5,bd,83,d6,83,bf,40,ad,16)
#define GPT_USER_HOME_STR SD_ID128_MAKE_UUID_STR(77,3f,91,ef,66,d4,49,b5,bd,83,d6,83,bf,40,ad,16)
#define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4)
#define GPT_LINUX_GENERIC_STR SD_ID128_MAKE_UUID_STR(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4)
/* Maintain same order as above */ /* Maintain same order as above */
#if defined(__alpha__) #if defined(__alpha__)

View File

@ -2142,7 +2142,7 @@ int show(int argc, char *argv[], void *userdata) {
show_mode = systemctl_show_mode_from_string(argv[0]); show_mode = systemctl_show_mode_from_string(argv[0]);
if (show_mode < 0) if (show_mode < 0)
return log_error_errno(show_mode, "Invalid argument '%s'.", argv[0]); return log_error_errno(show_mode, "Invalid argument.");
if (show_mode == SYSTEMCTL_SHOW_HELP && argc <= 1) if (show_mode == SYSTEMCTL_SHOW_HELP && argc <= 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), return log_error_errno(SYNTHETIC_ERRNO(EINVAL),

View File

@ -251,7 +251,7 @@ int sd_dhcp6_client_set_request_vendor_class(
int sd_dhcp6_client_set_prefix_delegation_hint( int sd_dhcp6_client_set_prefix_delegation_hint(
sd_dhcp6_client *client, sd_dhcp6_client *client,
uint8_t prefixlen, uint8_t prefixlen,
const struct in6_addr *pd_prefix); const struct in6_addr *pd_address);
int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client,
int *delegation); int *delegation);
int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client,
@ -260,6 +260,8 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client,
int *request); int *request);
int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
int request); int request);
int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client,
uint32_t transaction_id);
int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
sd_dhcp6_option *v); sd_dhcp6_option *v);

View File

@ -5933,4 +5933,5 @@ if __name__ == '__main__':
env.update({ 'UBSAN_OPTIONS' : ubsan_options }) env.update({ 'UBSAN_OPTIONS' : ubsan_options })
sys.argv[1:] = unknown_args sys.argv[1:] = unknown_args
unittest.main(verbosity=3) unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout,
verbosity=3))