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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user