Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ and may also receive information from ubus
| dhcpv6_na |bool | 1 | DHCPv6 stateful addressing hands out IA_NA - Internet Address - Network Address |
| dhcpv6_pd |bool | 1 | DHCPv6 stateful addressing hands out IA_PD - Internet Address - Prefix Delegation |
| dhcpv6_pd_min_len |integer| - | Minimum prefix length to delegate with IA_PD (value is adjusted if needed to be greater than the interface prefix length). Range [1,62] |
| dhcpv6_option |list | - | Custom DHCPv6 options in the form of strings formatted as `<optcode>,<encoding>:<data>`. For example: `42,hex:4575726F70652F4265726C696E` (timezone = "Europe/Berlin", RFC4833, §3) |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a new example is better now we have RFC4833 support? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha...yes...but don't waste time on reviewing this in detail just yet...I have a much more comprehensive idea in mind, it needs some more time though.

| dhcpv4_option |list | - | Custom DHCPv4 options in the same format |
| router |list |`<local address>`| Routers to announce, accepts IPv4 only |
| dns |list |`<local address>`| DNS servers to announce, accepts IPv4 and IPv6 |
| dnr |list |disabled| Encrypted DNS servers to announce, `<priority> <domain name> [<comma separated IP addresses> <SvcParams (key=value)>...]` |
Expand Down
114 changes: 114 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ enum {
IFACE_ATTR_DHCPV6_PD_MIN_LEN,
IFACE_ATTR_DHCPV6_NA,
IFACE_ATTR_DHCPV6_HOSTID_LEN,
IFACE_ATTR_DHCPV6_OPTION,
IFACE_ATTR_DHCPV4_OPTION,
IFACE_ATTR_RA_DEFAULT,
IFACE_ATTR_RA_MANAGEMENT,
IFACE_ATTR_RA_FLAGS,
Expand Down Expand Up @@ -151,6 +153,8 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
[IFACE_ATTR_DHCPV6_PD_MIN_LEN] = { .name = "dhcpv6_pd_min_len", .type = BLOBMSG_TYPE_INT32 },
[IFACE_ATTR_DHCPV6_NA] = { .name = "dhcpv6_na", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_DHCPV6_HOSTID_LEN] = { .name = "dhcpv6_hostidlength", .type = BLOBMSG_TYPE_INT32 },
[IFACE_ATTR_DHCPV6_OPTION] = { .name = "dhcpv6_option", .type = BLOBMSG_TYPE_ARRAY },
[IFACE_ATTR_DHCPV4_OPTION] = { .name = "dhcpv4_option", .type = BLOBMSG_TYPE_ARRAY },
[IFACE_ATTR_PD_MANAGER] = { .name = "pd_manager", .type = BLOBMSG_TYPE_STRING },
[IFACE_ATTR_PD_CER] = { .name = "pd_cer", .type = BLOBMSG_TYPE_STRING },
[IFACE_ATTR_RA_DEFAULT] = { .name = "ra_default", .type = BLOBMSG_TYPE_INT32 },
Expand Down Expand Up @@ -281,6 +285,10 @@ static void set_interface_defaults(struct interface *iface)
iface->dhcpv6_pd_min_len = 0;
iface->dhcpv6_na = true;
iface->dhcpv6_hostid_len = HOSTID_LEN_DEFAULT;
iface->dhcpv6_options = NULL;
iface->dhcpv6_option_cnt = 0;
iface->dhcpv4_options = NULL;
iface->dhcpv4_option_cnt = 0;
iface->dns_service = true;
iface->ra_flags = ND_RA_FLAG_OTHER;
iface->ra_slaac = true;
Expand All @@ -303,6 +311,12 @@ static void clean_interface(struct interface *iface)
free(iface->dhcpv4_ntp);
free(iface->dhcpv6_ntp);
free(iface->dhcpv6_sntp);
for (unsigned i = 0; i < iface->dhcpv6_option_cnt; i++)
free(iface->dhcpv6_options[i]);
free(iface->dhcpv6_options);
for (unsigned i = 0; i < iface->dhcpv4_option_cnt; i++)
free(iface->dhcpv4_options[i]);
free(iface->dhcpv4_options);
for (unsigned i = 0; i < iface->dnr_cnt; i++) {
free(iface->dnr[i].adn);
free(iface->dnr[i].addr4);
Expand Down Expand Up @@ -1270,6 +1284,106 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr

}

if ((c = tb[IFACE_ATTR_DHCPV6_OPTION])) {
int opt_count;

opt_count = blobmsg_check_array_len(c, BLOBMSG_TYPE_STRING, blob_raw_len(c));
if (opt_count > 0) {
struct blob_attr *cur;
size_t rem;

iface->dhcpv6_option_cnt = 0;
iface->dhcpv6_options = realloc(iface->dhcpv6_options, opt_count * sizeof(*iface->dhcpv6_options));
blobmsg_for_each_attr(cur, c, rem) {
const char *opt_data;
char *opt_code_end;
unsigned long opt_code;
size_t opt_len;
struct dhcpv6_option *opt;

/* syntax: <code>,<encoding>:<data> */
opt_data = strchr(blobmsg_get_string(cur), ',');
if (!opt_data)
goto err_option;

opt_code = strtoul(blobmsg_get_string(cur), &opt_code_end, 10);
if (opt_code < 1 || opt_code > UINT16_MAX || opt_code_end != opt_data)
goto err_option;

opt_data++;
if (strncmp(opt_data, "hex:", strlen("hex:")))
goto err_option;

opt_data += strlen("hex:");
opt_len = strlen(opt_data);
if (opt_len % 2 || opt_len / 2 > UINT16_MAX)
goto err_option;

opt_len /= 2;
opt = malloc(sizeof(*opt) + opt_len);
opt->code = opt_code;
opt->length = opt_len;
odhcpd_unhexlify(opt->data, opt_len, opt_data);
iface->dhcpv6_options[iface->dhcpv6_option_cnt++] = opt;
continue;

err_option:
syslog(LOG_ERR, "Invalid %s value configured for interface '%s'",
iface_attrs[IFACE_ATTR_DHCPV6_OPTION].name, iface->name);
}
}
}

if ((c = tb[IFACE_ATTR_DHCPV4_OPTION])) {
int opt_count;

opt_count = blobmsg_check_array_len(c, BLOBMSG_TYPE_STRING, blob_raw_len(c));
if (opt_count > 0) {
struct blob_attr *cur;
size_t rem;

iface->dhcpv4_option_cnt = 0;
iface->dhcpv4_options = realloc(iface->dhcpv4_options, opt_count * sizeof(*iface->dhcpv4_options));
blobmsg_for_each_attr(cur, c, rem) {
const char *opt_data;
char *opt_code_end;
unsigned long opt_code;
size_t opt_len;
struct dhcpv4_option *opt;

/* syntax: <code>,<encoding>:<data> */
opt_data = strchr(blobmsg_get_string(cur), ',');
if (!opt_data)
goto err_option4;

opt_code = strtoul(blobmsg_get_string(cur), &opt_code_end, 10);
if (opt_code < 1 || opt_code > UINT8_MAX || opt_code_end != opt_data)
goto err_option4;

opt_data++;
if (strncmp(opt_data, "hex:", strlen("hex:")))
goto err_option4;

opt_data += strlen("hex:");
opt_len = strlen(opt_data);
if (opt_len % 2 || opt_len / 2 > UINT8_MAX)
goto err_option4;

opt_len /= 2;
opt = malloc(sizeof(*opt) + opt_len);
opt->type = opt_code;
opt->len = opt_len;
odhcpd_unhexlify(opt->data, opt_len, opt_data);
iface->dhcpv4_options[iface->dhcpv4_option_cnt++] = opt;
continue;

err_option4:
syslog(LOG_ERR, "Invalid %s value configured for interface '%s'",
iface_attrs[IFACE_ATTR_DHCPV4_OPTION].name, iface->name);
}
}
}

if ((c = tb[IFACE_ATTR_RA_DEFAULT]))
iface->default_router = blobmsg_get_u32(c);

Expand Down
12 changes: 12 additions & 0 deletions src/dhcpv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,18 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len,
dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNR,
dnrs_len, dnrs);
break;

default:
for (size_t i = 0; i < iface->dhcpv4_option_cnt; i++) {
if (iface->dhcpv4_options[i]->type != a->reqopts[opt])
continue;
dhcpv4_put(&reply, &cookie,
iface->dhcpv4_options[i]->type,
iface->dhcpv4_options[i]->len,
iface->dhcpv4_options[i]->data);
break;
}
break;
}
}

Expand Down
7 changes: 0 additions & 7 deletions src/dhcpv4.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,6 @@ struct dhcpv4_auth_forcerenew {
uint8_t key[16];
} _packed;

struct dhcpv4_option {
uint8_t type;
uint8_t len;
uint8_t data[];
};

/* DNR */
struct dhcpv4_dnr {
uint16_t len;
Expand All @@ -105,7 +99,6 @@ struct dhcpv4_dnr {
uint8_t body[];
};


#define dhcpv4_for_each_option(start, end, opt)\
for (opt = (struct dhcpv4_option*)(start); \
&opt[1] <= (struct dhcpv4_option*)(end) && \
Expand Down
67 changes: 58 additions & 9 deletions src/dhcpv6.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ enum {
IOV_DHCPV4O6_SERVER,
IOV_DNR,
IOV_BOOTFILE_URL,
IOV_CUSTOM_OPTIONS,
IOV_TOTAL
};

Expand Down Expand Up @@ -411,6 +412,12 @@ static void handle_client_request(void *addr, void *data, size_t len,
struct dhcpv6_dnr *dnrs = NULL;
size_t dnrs_len = 0;

/* Custom options */
struct dhcpv6_option *custom_options[iface->dhcpv6_option_cnt];
size_t custom_option_cnt = 0;
uint8_t *custom_option_buf = NULL;
size_t custom_option_buf_len = 0;

uint16_t otype, olen;
uint8_t *odata;
uint16_t *reqopts = NULL;
Expand Down Expand Up @@ -502,6 +509,37 @@ static void handle_client_request(void *addr, void *data, size_t len,
memcpy(&d6dnr->len, &d6dnr_len_be, sizeof(d6dnr_len_be));
}
break;

default:
for (size_t i = 0; i < iface->dhcpv6_option_cnt; i++) {
struct dhcpv6_option *copt = iface->dhcpv6_options[i];

if (opt != copt->code)
continue;

custom_options[custom_option_cnt++] = copt;
custom_option_buf_len += 2 * sizeof(uint16_t) + copt->length;
}
break;
}
}

if (custom_option_cnt > 0) {
uint8_t *tmp;

custom_option_buf = alloca(custom_option_buf_len);
tmp = custom_option_buf;

for (size_t i = 0; i < custom_option_cnt; i++) {
struct dhcpv6_option *copt = custom_options[i];
struct {
uint16_t type;
uint16_t len;
} copt_hdr = { htons(copt->code), htons(copt->length) };

memcpy(tmp, &copt_hdr, sizeof(copt_hdr));
memcpy(tmp + sizeof(copt_hdr), copt->data, copt->length);
tmp += sizeof(copt_hdr) + copt->length;
}
}

Expand Down Expand Up @@ -560,7 +598,8 @@ static void handle_client_request(void *addr, void *data, size_t len,
[IOV_DNR] = {dnrs, dnrs_len},
[IOV_RELAY_MSG] = {NULL, 0},
[IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0},
[IOV_BOOTFILE_URL] = {NULL, 0}
[IOV_BOOTFILE_URL] = {NULL, 0},
[IOV_CUSTOM_OPTIONS] = {custom_option_buf, custom_option_buf_len},
};

if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
Expand Down Expand Up @@ -730,15 +769,25 @@ static void handle_client_request(void *addr, void *data, size_t len,
}

if (iov[IOV_NESTED].iov_len > 0) /* Update length */
update_nested_message(data, len, iov[IOV_DEST].iov_len + iov[IOV_MAXRT].iov_len +
iov[IOV_RAPID_COMMIT].iov_len + iov[IOV_DNS].iov_len +
iov[IOV_DNS_ADDR].iov_len + iov[IOV_SEARCH].iov_len +
iov[IOV_SEARCH_DOMAIN].iov_len + iov[IOV_PDBUF].iov_len +
update_nested_message(data, len,
iov[IOV_DEST].iov_len +
iov[IOV_MAXRT].iov_len +
iov[IOV_RAPID_COMMIT].iov_len +
iov[IOV_DNS].iov_len +
iov[IOV_DNS_ADDR].iov_len +
iov[IOV_SEARCH].iov_len +
iov[IOV_SEARCH_DOMAIN].iov_len +
iov[IOV_PDBUF].iov_len +
iov[IOV_DHCPV4O6_SERVER].iov_len +
iov[IOV_CERID].iov_len + iov[IOV_DHCPV6_RAW].iov_len +
iov[IOV_NTP].iov_len + iov[IOV_NTP_ADDR].iov_len +
iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len +
iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len -
iov[IOV_CERID].iov_len +
iov[IOV_DHCPV6_RAW].iov_len +
iov[IOV_NTP].iov_len +
iov[IOV_NTP_ADDR].iov_len +
iov[IOV_SNTP].iov_len +
iov[IOV_SNTP_ADDR].iov_len +
iov[IOV_DNR].iov_len +
iov[IOV_BOOTFILE_URL].iov_len +
iov[IOV_CUSTOM_OPTIONS].iov_len -
(4 + opts_end - opts));

syslog(LOG_DEBUG, "Sending a DHCPv6-%s on %s", iov[IOV_NESTED].iov_len ? "relay-reply" : "reply", iface->name);
Expand Down
18 changes: 17 additions & 1 deletion src/odhcpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,19 @@ struct dnr_options {
uint16_t svc_len;
};

// Generic DHCPv6 option
struct dhcpv6_option {
uint16_t code;
uint16_t length;
uint8_t data[];
};

// Generic DHCPv4 option
struct dhcpv4_option {
uint8_t type;
uint8_t len;
uint8_t data[];
};

// RA PIO - RFC9096
struct ra_pio {
Expand All @@ -278,7 +291,6 @@ struct ra_pio {
time_t lifetime;
};


struct interface {
struct avl_node avl;

Expand Down Expand Up @@ -375,6 +387,8 @@ struct interface {
struct in_addr *dhcpv4_dns;
size_t dhcpv4_dns_cnt;
bool dhcpv4_forcereconf;
struct dhcpv4_option **dhcpv4_options;
size_t dhcpv4_option_cnt;

// DNS
struct in6_addr *dns;
Expand All @@ -390,6 +404,8 @@ struct interface {
bool dhcpv6_na;
uint32_t dhcpv6_hostid_len;
uint32_t dhcpv6_pd_min_len; // minimum delegated prefix length
struct dhcpv6_option **dhcpv6_options;
size_t dhcpv6_option_cnt;

char *upstream;
size_t upstream_len;
Expand Down