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.
This commit is contained in:
Yu Watanabe
2021-09-29 15:29:36 +09:00
parent e620104956
commit 4af39cb8ec
4 changed files with 122 additions and 74 deletions
+1 -1
View File
@@ -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);
+75 -36
View File
@@ -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;
return be16toh(statusopt->status);
if (ret_status_message) {
char *msg;
/* 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:
case SD_DHCP6_OPTION_STATUS_CODE: {
_cleanup_free_ char *msg = NULL;
status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
if (status < 0)
return status;
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;
+12 -10
View File
@@ -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",
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),
dhcp6_message_status_to_string(status));
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.");
+34 -27
View File
@@ -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;