summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2021-09-23 16:30:40 +0900
committerYu Watanabe <watanabe.yu+github@gmail.com>2021-09-29 15:29:36 +0900
commit4af39cb8ecdf55fc0238bf3149696946f6f28fca (patch)
tree20ef75b572cc26377fa1bf86bf41166a9232c663 /src
parente620104956dff64244c0e73e86c3138c0b13b875 (diff)
downloadsystemd-4af39cb8ecdf55fc0238bf3149696946f6f28fca.tar.gz
sd-dhcp6-client: make dhcp6_option_parse_status() also parse error message
This also introduce dhcp6_option_parse_ia_options(). Currently, it is assumed that each IA address or PD prefix may contain a status sub-option. But it is not prohibited that other sub-options or multiple status options are contained.
Diffstat (limited to 'src')
-rw-r--r--src/libsystemd-network/dhcp6-internal.h2
-rw-r--r--src/libsystemd-network/dhcp6-option.c111
-rw-r--r--src/libsystemd-network/sd-dhcp6-client.c22
-rw-r--r--src/libsystemd-network/test-dhcp6-client.c61
4 files changed, 122 insertions, 74 deletions
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 8bcc826f42..dfb0734520 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -109,7 +109,7 @@ int dhcp6_option_parse(
uint16_t *ret_option_code,
size_t *ret_option_data_len,
const uint8_t **ret_option_data);
-int dhcp6_option_parse_status(DHCP6Option *option, size_t len);
+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, DHCP6Option *iaoption, be32_t iaid, DHCP6IA *ia, uint16_t *ret_status_code);
int dhcp6_option_parse_ip6addrs(const uint8_t *optval, uint16_t optlen,
struct in6_addr **addrs, size_t count);
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
index 781d391c0c..35950386b0 100644
--- a/src/libsystemd-network/dhcp6-option.c
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -14,17 +14,12 @@
#include "dhcp6-lease-internal.h"
#include "dhcp6-protocol.h"
#include "dns-domain.h"
+#include "escape.h"
#include "memory-util.h"
#include "sparse-endian.h"
#include "strv.h"
#include "unaligned.h"
-typedef struct DHCP6StatusOption {
- struct DHCP6Option option;
- be16_t status;
- char msg[];
-} _packed_ DHCP6StatusOption;
-
typedef struct DHCP6AddressOption {
struct DHCP6Option option;
struct iaaddr iaaddr;
@@ -407,14 +402,65 @@ int dhcp6_option_parse(
return 0;
}
-int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
- DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
+int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
+ assert(data);
- if (len < sizeof(DHCP6StatusOption) ||
- be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
- return -ENOBUFS;
+ if (data_len < sizeof(uint16_t))
+ return -EBADMSG;
+
+ if (ret_status_message) {
+ char *msg;
- return be16toh(statusopt->status);
+ /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
+ * Let's escape unsafe characters for safety. */
+ msg = cescape_length((const char*) (data + sizeof(uint16_t)), data_len - sizeof(uint16_t));
+ if (!msg)
+ return -ENOMEM;
+
+ *ret_status_message = msg;
+ }
+
+ return unaligned_read_be16(data);
+}
+
+static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
+ int r;
+
+ assert(buf);
+
+ for(size_t offset = 0; offset < buflen;) {
+ const uint8_t *data;
+ size_t data_len;
+ uint16_t code;
+
+ r = dhcp6_option_parse(buf, buflen, &offset, &code, &data_len, &data);
+ if (r < 0)
+ return r;
+
+ switch(code) {
+ case SD_DHCP6_OPTION_STATUS_CODE: {
+ _cleanup_free_ char *msg = NULL;
+
+ r = dhcp6_option_parse_status(data, data_len, &msg);
+ if (r == -ENOMEM)
+ return r;
+ if (r < 0)
+ /* Let's log but ignore the invalid status option. */
+ log_dhcp6_client_errno(client, r,
+ "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;
+ }
+ default:
+ log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code);
+ }
+ }
+
+ return 0;
}
static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) {
@@ -435,14 +481,10 @@ static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *opti
"preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
lt_pref, lt_valid);
- if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
- r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
+ if (be16toh(option->len) + offsetof(DHCP6Option, data) > offsetof(DHCP6AddressOption, options)) {
+ r = dhcp6_option_parse_ia_options(client, option->data + sizeof(struct iaaddr), be16toh(option->len) - sizeof(struct iaaddr));
if (r < 0)
return r;
- if (r > 0)
- return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
- "Non-zero status code '%s' for address is received",
- dhcp6_message_status_to_string(r));
}
addr = new0(DHCP6Address, 1);
@@ -477,14 +519,10 @@ static int dhcp6_option_parse_pdprefix(sd_dhcp6_client *client, DHCP6Option *opt
"preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
lt_pref, lt_valid);
- if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
- r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
+ if (be16toh(option->len) + offsetof(DHCP6Option, data) > offsetof(DHCP6PDPrefixOption, options)) {
+ r = dhcp6_option_parse_ia_options(client, option->data + sizeof(struct iapdprefix), be16toh(option->len) - sizeof(struct iapdprefix));
if (r < 0)
return r;
- if (r > 0)
- return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
- "Non-zero status code '%s' for PD prefix is received",
- dhcp6_message_status_to_string(r));
}
prefix = new0(DHCP6Address, 1);
@@ -511,9 +549,9 @@ int dhcp6_option_parse_ia(
uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
uint16_t iatype, optlen;
size_t iaaddr_offset;
- int r = 0, status;
size_t i, len;
uint16_t opt;
+ int r;
assert_return(ia, -EINVAL);
assert_return(!ia->addresses, -EINVAL);
@@ -636,24 +674,25 @@ int dhcp6_option_parse_ia(
break;
- case SD_DHCP6_OPTION_STATUS_CODE:
-
- status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
- if (status < 0)
- return status;
+ case SD_DHCP6_OPTION_STATUS_CODE: {
+ _cleanup_free_ char *msg = NULL;
- if (status > 0) {
+ r = dhcp6_option_parse_status(option->data, optlen, &msg);
+ if (r < 0)
+ return r;
+ if (r > 0) {
if (ret_status_code)
- *ret_status_code = status;
+ *ret_status_code = r;
- log_dhcp6_client(client, "IA status %s",
- dhcp6_message_status_to_string(status));
+ log_dhcp6_client(client,
+ "Received an IA option with non-zero status: %s%s%s",
+ strempty(msg), isempty(msg) ? "" : ": ",
+ dhcp6_message_status_to_string(r));
return 0;
}
-
break;
-
+ }
default:
log_dhcp6_client(client, "Unknown IA option %d", opt);
break;
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 298a89b086..8955aedc1f 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -1126,7 +1126,6 @@ static int client_parse_message(
while (pos < len) {
DHCP6Option *option = (DHCP6Option *) &message->options[pos];
uint16_t optcode, optlen;
- int status;
uint8_t *optval;
if (len < pos + offsetof(DHCP6Option, data))
@@ -1176,18 +1175,21 @@ static int client_parse_message(
break;
- case SD_DHCP6_OPTION_STATUS_CODE:
- status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
- if (status < 0)
- return status;
+ case SD_DHCP6_OPTION_STATUS_CODE: {
+ _cleanup_free_ char *msg = NULL;
- if (status > 0)
- return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s Status %s",
- dhcp6_message_type_to_string(message->type),
- dhcp6_message_status_to_string(status));
+ 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:
if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index 35aaa2a997..fb948ea8a9 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -249,7 +249,7 @@ static int test_option_status(sd_event *e) {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d,
- /* status option */
+ /* IA address status option */
0x00, 0x0d, 0x00, 0x02, 0x00, 0x01,
};
static const uint8_t option3[] = {
@@ -261,7 +261,7 @@ static int test_option_status(sd_event *e) {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d,
- /* status option */
+ /* IA address status option */
0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 'f', 'o',
'o', 'b', 'a', 'r',
};
@@ -275,7 +275,7 @@ static int test_option_status(sd_event *e) {
0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe,
0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
- /* status option */
+ /* PD prefix status option */
0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
};
static const uint8_t option5[] = {
@@ -288,7 +288,7 @@ static int test_option_status(sd_event *e) {
0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe,
0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
- /* status option */
+ /* PD prefix status option */
0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
/* IA PD Prefix #2 */
0x00, 0x1a, 0x00, 0x1f,
@@ -296,11 +296,13 @@ static int test_option_status(sd_event *e) {
0x80, 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x0l, 0xd0,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
+ /* PD prefix status option */
0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
};
DHCP6Option *option;
DHCP6IA ia, pd;
be32_t iaid;
+ uint16_t status;
int r = 0;
log_debug("/* %s */", __func__);
@@ -308,62 +310,67 @@ static int test_option_status(sd_event *e) {
memcpy(&iaid, option1 + 4, sizeof(iaid));
zero(ia);
- option = (DHCP6Option *)option1;
+ option = (DHCP6Option*) option1;
assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len));
r = dhcp6_option_parse_ia(NULL, option, 0, &ia, NULL);
assert_se(r == -ENOANO);
- r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status);
assert_se(r == 0);
- assert_se(ia.addresses == NULL);
+ assert_se(status == 1);
+ assert_se(!ia.addresses);
option->len = htobe16(17);
- r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status);
assert_se(r == -ENOBUFS);
- assert_se(ia.addresses == NULL);
+ assert_se(!ia.addresses);
option->len = htobe16(sizeof(DHCP6Option));
- r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status);
assert_se(r == -ENOBUFS);
- assert_se(ia.addresses == NULL);
+ assert_se(!ia.addresses);
zero(ia);
- option = (DHCP6Option *)option2;
+ option = (DHCP6Option*) option2;
assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len));
- r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL);
- assert_se(r >= 0);
- assert_se(ia.addresses == NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status);
+ assert_se(r > 0);
+ assert_se(status == 0);
+ assert_se(!ia.addresses);
zero(ia);
- option = (DHCP6Option *)option3;
+ option = (DHCP6Option*) option3;
assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len));
- r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL);
- assert_se(r >= 0);
- assert_se(ia.addresses != NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status);
+ assert_se(r > 0);
+ assert_se(status == 0);
+ assert_se(ia.addresses);
dhcp6_lease_free_ia(&ia);
zero(pd);
- option = (DHCP6Option *)option4;
+ option = (DHCP6Option*) option4;
assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len));
- r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, NULL);
- assert_se(r >= 0);
- assert_se(pd.addresses != NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, &status);
+ assert_se(r > 0);
+ assert_se(status == 0);
+ assert_se(pd.addresses);
assert_se(memcmp(&pd.ia_pd.id, &option4[4], 4) == 0);
assert_se(memcmp(&pd.ia_pd.lifetime_t1, &option4[8], 4) == 0);
assert_se(memcmp(&pd.ia_pd.lifetime_t2, &option4[12], 4) == 0);
dhcp6_lease_free_ia(&pd);
zero(pd);
- option = (DHCP6Option *)option5;
+ option = (DHCP6Option*) option5;
assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len));
- r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, NULL);
- assert_se(r >= 0);
- assert_se(pd.addresses != NULL);
+ r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, &status);
+ assert_se(r > 0);
+ assert_se(status == 0);
+ assert_se(pd.addresses);
dhcp6_lease_free_ia(&pd);
return 0;