diff --git a/CMakeLists.txt b/CMakeLists.txt index 828fa71..1588637 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ if(${DHCPV4_SUPPORT}) set(EXT_SRC ${EXT_SRC} src/dhcpv4.c) endif(${DHCPV4_SUPPORT}) -add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/dhcpv6-ia.c src/netlink.c ${EXT_SRC}) +add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/dhcpv6-ia.c src/dhcpv6-pxe.c src/netlink.c ${EXT_SRC}) target_link_libraries(odhcpd resolv ubox uci ${libnl} ${EXT_LINK}) # Installation diff --git a/README b/README index 34c4e1f..76c0b5c 100644 --- a/README +++ b/README @@ -40,6 +40,7 @@ prefix delegation and can be used to relay RA, DHCPv6 and NDP between routed and only serving NDP for DAD and for traffic to the router itself [Warning: you should provide additional firewall rules for security] +5. IPv6 PxE Support. ** Compiling ** @@ -111,6 +112,8 @@ router list Routers to announce accepts IPv4 only dns list DNS servers to announce accepts IPv4 and IPv6 +dnr list disabled Encrypted DNS servers to announce + [ ...] dns_service bool 1 Announce the address of interface as DNS service if the list of dns is empty domain list Search domains to announce @@ -170,3 +173,8 @@ hostid string IPv6 host identifier name string Hostname leasetime string DHCPv4/v6 leasetime +Sections of type boot6 +Option Type Required Description +url string yes e.g. tftp://[fd11::1]/pxe.efi +arch integer no the arch code. 07 is EFI. + If not present, this boot6 will be the default. diff --git a/src/config.c b/src/config.c index 5d174c5..300a60d 100644 --- a/src/config.c +++ b/src/config.c @@ -18,6 +18,7 @@ #include #include "odhcpd.h" +#include "dhcpv6-pxe.h" static struct blob_buf b; static int reload_pipe[2] = { -1, -1 }; @@ -43,6 +44,22 @@ struct config config = {.legacy = false, .main_dhcpv4 = false, #define OAF_DHCPV6 (OAF_DHCPV6_NA | OAF_DHCPV6_PD) +enum { + IPV6_PXE_URL, + IPV6_PXE_ARCH, + IPV6_PXE_MAX +}; + +static const struct blobmsg_policy ipv6_pxe_attrs[IPV6_PXE_MAX] = { + [IPV6_PXE_URL] = {.name = "url", .type = BLOBMSG_TYPE_STRING }, + [IPV6_PXE_ARCH] = {.name = "arch", .type = BLOBMSG_TYPE_INT32 }, +}; + +const struct uci_blob_param_list ipv6_pxe_attr_list = { + .n_params = IPV6_PXE_MAX, + .params = ipv6_pxe_attrs, +}; + enum { IFACE_ATTR_INTERFACE, IFACE_ATTR_IFNAME, @@ -59,6 +76,7 @@ enum { IFACE_ATTR_NDP, IFACE_ATTR_ROUTER, IFACE_ATTR_DNS, + IFACE_ATTR_DNR, IFACE_ATTR_DNS_SERVICE, IFACE_ATTR_DOMAIN, IFACE_ATTR_FILTER_CLASS, @@ -113,6 +131,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_NDP] = { .name = "ndp", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_ROUTER] = { .name = "router", .type = BLOBMSG_TYPE_ARRAY }, [IFACE_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY }, + [IFACE_ATTR_DNR] = { .name = "dnr", .type = BLOBMSG_TYPE_ARRAY }, [IFACE_ATTR_DNS_SERVICE] = { .name = "dns_service", .type = BLOBMSG_TYPE_BOOL }, [IFACE_ATTR_DOMAIN] = { .name = "domain", .type = BLOBMSG_TYPE_ARRAY }, [IFACE_ATTR_FILTER_CLASS] = { .name = "filter_class", .type = BLOBMSG_TYPE_STRING }, @@ -210,6 +229,32 @@ static const struct { const char *name; uint8_t flag; } ra_flags[] = { { .name = NULL, }, }; +// https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml +enum svc_param_keys { + DNR_SVC_MANDATORY, + DNR_SVC_ALPN, + DNR_SVC_NO_DEFAULT_ALPN, + DNR_SVC_PORT, + DNR_SVC_IPV4HINT, + DNR_SVC_ECH, + DNR_SVC_IPV6HINT, + DNR_SVC_DOHPATH, + DNR_SVC_OHTTP, + DNR_SVC_MAX, +}; + +static const char *svc_param_key_names[DNR_SVC_MAX] = { + [DNR_SVC_MANDATORY] = "mandatory", + [DNR_SVC_ALPN] = "alpn", + [DNR_SVC_NO_DEFAULT_ALPN] = "no-default-alpn", + [DNR_SVC_PORT] = "port", + [DNR_SVC_IPV4HINT] = "ipv4hint", + [DNR_SVC_ECH] = "ech", + [DNR_SVC_IPV6HINT] = "ipv6hint", + [DNR_SVC_DOHPATH] = "dohpath", + [DNR_SVC_OHTTP] = "ohttp", +}; + static void set_interface_defaults(struct interface *iface) { iface->ignore = true; @@ -249,6 +294,13 @@ 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->dnr_cnt; i++) { + free(iface->dnr[i].adn); + free(iface->dnr[i].addr4); + free(iface->dnr[i].addr6); + free(iface->dnr[i].svc); + } + free(iface->dnr); memset(&iface->ra, 0, sizeof(*iface) - offsetof(struct interface, ra)); set_interface_defaults(iface); } @@ -604,6 +656,287 @@ static int parse_ntp_fqdn(uint16_t *dhcpv6_ntp_len, char *fqdn, uint8_t **dhcpv6 return 0; } +/* Parse DNR Options */ +static int parse_dnr_str(char *str, struct interface *iface) +{ + struct dnr_options dnr = {0}; + size_t adn_len; + uint8_t adn_buf[256] = {0}; + char *saveptr1, *saveptr2; + + char *priority; + priority = strtok_r(str, " \f\n\r\t\v", &saveptr1); + if (!priority) { + goto err; + } else if (sscanf(priority, "%" SCNu16, &dnr.priority) != 1) { + syslog(LOG_ERR, "Unable to parse priority '%s'", priority); + goto err; + } else if (dnr.priority == 0) { + syslog(LOG_ERR, "Invalid priority '%s'", priority); + goto err; + } + + char *adn; + adn = strtok_r(NULL, " \f\n\r\t\v", &saveptr1); + if (!adn) + goto err; + + adn_len = strlen(adn); + if (adn_len > 0 && adn[adn_len - 1] == '.') + adn[adn_len - 1] = '\0'; + + if (adn_len >= sizeof(adn_buf)) { + syslog(LOG_ERR, "Hostname '%s' too long", adn); + goto err; + } + + adn_len = dn_comp(adn, adn_buf, sizeof(adn_buf), NULL, NULL); + if (adn_len <= 0) { + syslog(LOG_ERR, "Unable to parse hostname '%s'", adn); + goto err; + } + + dnr.adn = malloc(adn_len); + if (!dnr.adn) + goto err; + memcpy(dnr.adn, adn_buf, adn_len); + dnr.adn_len = adn_len; + + char *addrs; + addrs = strtok_r(NULL, " \f\n\r\t\v", &saveptr1); + if (!addrs) + // ADN-Only mode + goto done; + + for (char *addr = strtok_r(addrs, ",", &saveptr2); addr; addr = strtok_r(NULL, ",", &saveptr2)) { + struct in6_addr addr6, *tmp6; + struct in_addr addr4, *tmp4; + size_t new_sz; + + if (inet_pton(AF_INET6, addr, &addr6) == 1) { + new_sz = (dnr.addr6_cnt + 1) * sizeof(*dnr.addr6); + if (new_sz > UINT16_MAX) + continue; + tmp6 = realloc(dnr.addr6, new_sz); + if (!tmp6) + goto err; + dnr.addr6 = tmp6; + memcpy(&dnr.addr6[dnr.addr6_cnt], &addr6, sizeof(*dnr.addr6)); + dnr.addr6_cnt++; + + } else if (inet_pton(AF_INET, addr, &addr4) == 1) { + new_sz = (dnr.addr4_cnt + 1) * sizeof(*dnr.addr4); + if (new_sz > UINT8_MAX) + continue; + tmp4 = realloc(dnr.addr4, new_sz); + if (!tmp4) + goto err; + dnr.addr4 = tmp4; + memcpy(&dnr.addr4[dnr.addr4_cnt], &addr4, sizeof(*dnr.addr4)); + dnr.addr4_cnt++; + + } else { + syslog(LOG_ERR, "Unable to parse IP address '%s'", addr); + goto err; + } + } + + char *svc_vals[DNR_SVC_MAX] = { NULL, }; + for (char *svc_tok = strtok_r(NULL, " \f\n\r\t\v", &saveptr1); svc_tok; svc_tok = strtok_r(NULL, " \f\n\r\t\v", &saveptr1)) { + uint16_t svc_id; + char *svc_key, *svc_val; + + svc_key = strtok_r(svc_tok, "=", &saveptr2); + svc_val = strtok_r(NULL, "=", &saveptr2); + + if (!strcmp(svc_key, "_lifetime")) { + uint32_t lifetime; + + if (!svc_val || sscanf(svc_val, "%" SCNu32, &lifetime) != 1) { + syslog(LOG_ERR, "Invalid value '%s' for _lifetime", svc_val ? svc_val : ""); + goto err; + } + + dnr.lifetime = lifetime; + dnr.lifetime_set = true; + continue; + } + + for (svc_id = 0; svc_id < DNR_SVC_MAX; svc_id++) + if (!strcmp(svc_key, svc_param_key_names[svc_id])) + break; + + if (svc_id >= DNR_SVC_MAX) { + syslog(LOG_ERR, "Invalid SvcParam '%s'", svc_key); + goto err; + } + + svc_vals[svc_id] = svc_val ? svc_val : ""; + } + + /* SvcParamKeys must be in increasing order, RFC9460 §2.2 */ + for (uint16_t svc_key = 0; svc_key < DNR_SVC_MAX; svc_key++) { + uint16_t svc_key_be = ntohs(svc_key); + uint16_t svc_val_len, svc_val_len_be; + char *svc_val_str = svc_vals[svc_key]; + uint8_t *tmp; + + if (!svc_val_str) + continue; + + switch (svc_key) { + case DNR_SVC_MANDATORY: + uint16_t mkeys[DNR_SVC_MAX]; + + svc_val_len = 0; + for (char *mkey_str = strtok_r(svc_val_str, ",", &saveptr2); mkey_str; mkey_str = strtok_r(NULL, ",", &saveptr2)) { + uint16_t mkey; + + for (mkey = 0; mkey < DNR_SVC_MAX; mkey++) + if (!strcmp(mkey_str, svc_param_key_names[mkey])) + break; + + if (mkey >= DNR_SVC_MAX || !svc_vals[mkey]) { + syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'mandatory'", mkey_str); + goto err; + } + + mkeys[svc_val_len++] = ntohs(mkey); + } + + svc_val_len *= sizeof(uint16_t); + svc_val_len_be = ntohs(svc_val_len); + + tmp = realloc(dnr.svc, dnr.svc_len + 4 + svc_val_len); + if (!tmp) + goto err; + + dnr.svc = tmp; + memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be)); + memcpy(dnr.svc + dnr.svc_len + 2, &svc_val_len_be, sizeof(svc_val_len_be)); + memcpy(dnr.svc + dnr.svc_len + 4, mkeys, svc_val_len); + dnr.svc_len += 4 + svc_val_len; + break; + + case DNR_SVC_ALPN: + size_t len_off; + + tmp = realloc(dnr.svc, dnr.svc_len + 4); + if (!tmp) + goto err; + + dnr.svc = tmp; + memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be)); + /* the length is not known yet */ + len_off = dnr.svc_len + sizeof(svc_key_be); + dnr.svc_len += 4; + + svc_val_len = 0; + for (char *alpn_id_str = strtok_r(svc_val_str, ",", &saveptr2); alpn_id_str; alpn_id_str = strtok_r(NULL, ",", &saveptr2)) { + size_t alpn_id_len; + + alpn_id_len = strlen(alpn_id_str); + if (alpn_id_len > UINT8_MAX) { + syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'alpn'", alpn_id_str); + goto err; + } + + tmp = realloc(dnr.svc, dnr.svc_len + 1 + alpn_id_len); + if (!tmp) + goto err; + dnr.svc = tmp; + + dnr.svc[dnr.svc_len] = alpn_id_len; + memcpy(dnr.svc + dnr.svc_len + 1, alpn_id_str, alpn_id_len); + dnr.svc_len += 1 + alpn_id_len; + svc_val_len += 1 + alpn_id_len; + } + + svc_val_len_be = ntohs(svc_val_len); + memcpy(dnr.svc + len_off, &svc_val_len_be, sizeof(svc_val_len_be)); + break; + + case DNR_SVC_PORT: + uint16_t port; + + if (sscanf(svc_val_str, "%" SCNu16, &port) != 1) { + syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'port'", svc_val_str); + goto err; + } + + port = ntohs(port); + svc_val_len_be = ntohs(2); + + tmp = realloc(dnr.svc, dnr.svc_len + 6); + if (!tmp) + goto err; + + dnr.svc = tmp; + memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be)); + memcpy(dnr.svc + dnr.svc_len + 2, &svc_val_len_be, sizeof(svc_val_len_be)); + memcpy(dnr.svc + dnr.svc_len + 4, &port, sizeof(port)); + dnr.svc_len += 6; + break; + + case DNR_SVC_NO_DEFAULT_ALPN: + /* fall through */ + + case DNR_SVC_OHTTP: + if (strlen(svc_val_str) > 0) { + syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'port'", svc_val_str); + goto err; + } + /* fall through */ + + case DNR_SVC_DOHPATH: + /* plain string */ + svc_val_len = strlen(svc_val_str); + svc_val_len_be = ntohs(svc_val_len); + tmp = realloc(dnr.svc, dnr.svc_len + 4 + svc_val_len); + if (!tmp) + goto err; + + dnr.svc = tmp; + memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be)); + dnr.svc_len += sizeof(svc_key_be); + memcpy(dnr.svc + dnr.svc_len, &svc_val_len_be, sizeof(svc_val_len_be)); + dnr.svc_len += sizeof(svc_val_len_be); + memcpy(dnr.svc + dnr.svc_len, svc_val_str, svc_val_len); + dnr.svc_len += svc_val_len; + break; + + case DNR_SVC_ECH: + syslog(LOG_ERR, "SvcParam 'ech' is not implemented"); + goto err; + + case DNR_SVC_IPV4HINT: + /* fall through */ + + case DNR_SVC_IPV6HINT: + syslog(LOG_ERR, "SvcParam '%s' is not allowed", svc_param_key_names[svc_key]); + goto err; + } + } + +done: + struct dnr_options *tmp; + tmp = realloc(iface->dnr, (iface->dnr_cnt + 1) * sizeof(dnr)); + if (!tmp) + goto err; + + iface->dnr = tmp; + memcpy(iface->dnr + iface->dnr_cnt, &dnr, sizeof(dnr)); + iface->dnr_cnt++; + return 0; + +err: + free(dnr.adn); + free(dnr.addr4); + free(dnr.addr6); + free(dnr.svc); + return -1; +} + int config_parse_interface(void *data, size_t len, const char *name, bool overwrite) { struct odhcpd_ipaddr *addrs = NULL; @@ -693,13 +1026,12 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr addrs_len = netlink_get_interface_linklocal(iface->ifindex, &addrs); if (addrs_len > 0) { for (ssize_t i = 0; i < addrs_len; i++) { - struct odhcpd_ipaddr *addr = &addrs[i]; - - if (!addr->tentative) { + if (!addrs[i].tentative) { iface->have_link_local = true; break; } } + free(addrs); } iface->inuse = true; @@ -1074,22 +1406,63 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr } } + if ((c = tb[IFACE_ATTR_DNR])) { + struct blob_attr *cur; + unsigned rem; + + blobmsg_for_each_attr(cur, c, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, false)) + continue; + + if (parse_dnr_str(blobmsg_get_string(cur), iface)) + syslog(LOG_ERR, "Invalid %s value configured for interface '%s'", + iface_attrs[IFACE_ATTR_DNR].name, iface->name); + } + } + if ((c = tb[IFACE_ATTR_RA_PREF64])) { - const char *str = blobmsg_get_string(c); - char *astr = malloc(strlen(str) + 1); - char *delim; - int l; - - if (!astr || !strcpy(astr, str) || - (delim = strchr(astr, '/')) == NULL || (*(delim++) = 0) || - sscanf(delim, "%i", &l) == 0 || l > 128 || - inet_pton(AF_INET6, astr, &iface->pref64_addr) == 0) - iface->pref64_length = 0; - else - iface->pref64_length = l; + struct in6_addr addr; + + odhcpd_parse_addr6_prefix(blobmsg_get_string(c), + &addr, &iface->pref64_length); - if (astr) - free(astr); + iface->pref64_prefix[0] = addr.s6_addr32[0]; + switch (iface->pref64_length) { + case 96: + iface->pref64_plc = 0; + iface->pref64_prefix[1] = addr.s6_addr32[1]; + iface->pref64_prefix[2] = addr.s6_addr32[2]; + break; + case 64: + iface->pref64_plc = 1; + iface->pref64_prefix[1] = addr.s6_addr32[1]; + iface->pref64_prefix[2] = 0; + break; + case 56: + iface->pref64_plc = 2; + iface->pref64_prefix[1] = addr.s6_addr32[1] & htonl(0xffffff00); + iface->pref64_prefix[2] = 0; + break; + case 48: + iface->pref64_plc = 3; + iface->pref64_prefix[1] = addr.s6_addr32[1] & htonl(0xffff0000); + iface->pref64_prefix[2] = 0; + break; + case 40: + iface->pref64_plc = 4; + iface->pref64_prefix[1] = addr.s6_addr32[1] & htonl(0xff000000); + iface->pref64_prefix[2] = 0; + break; + case 32: + iface->pref64_plc = 5; + iface->pref64_prefix[1] = 0; + iface->pref64_prefix[2] = 0; + break; + default: + syslog(LOG_WARNING, "Invalid PREF64 prefix size (%d), " + "ignoring ra_pref64 option!", iface->pref64_length); + iface->pref64_length = 0; + } } if ((c = tb[IFACE_ATTR_RA_PREFERENCE])) { @@ -1121,23 +1494,10 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr if ((c = tb[IFACE_ATTR_NDPROXY_SLAVE])) iface->external = blobmsg_get_bool(c); - if ((c = tb[IFACE_ATTR_PREFIX_FILTER])) { - const char *str = blobmsg_get_string(c); - char *astr = malloc(strlen(str) + 1); - char *delim; - int l; - - if (!astr || !strcpy(astr, str) || - (delim = strchr(astr, '/')) == NULL || (*(delim++) = 0) || - sscanf(delim, "%i", &l) == 0 || l > 128 || - inet_pton(AF_INET6, astr, &iface->pio_filter_addr) == 0) - iface->pio_filter_length = 0; - else - iface->pio_filter_length = l; - - if (astr) - free(astr); - } + if ((c = tb[IFACE_ATTR_PREFIX_FILTER])) + odhcpd_parse_addr6_prefix(blobmsg_get_string(c), + &iface->pio_filter_addr, + &iface->pio_filter_length); if (overwrite && (c = tb[IFACE_ATTR_NTP])) { struct blob_attr *cur; @@ -1369,6 +1729,29 @@ void reload_services(struct interface *iface) } } +static int ipv6_pxe_from_uci(struct uci_section* s) +{ + blob_buf_init(&b, 0); + uci_to_blob(&b, s, &ipv6_pxe_attr_list); + + void* data = blob_data(b.head); + size_t len = blob_len(b.head); + + struct blob_attr* tb[IFACE_ATTR_MAX]; + blobmsg_parse(ipv6_pxe_attrs, IPV6_PXE_MAX, tb, data, len); + + if (!tb[IPV6_PXE_URL]) + return -1; + + const char* url = blobmsg_get_string(tb[IPV6_PXE_URL]); + + uint32_t arch = 0xFFFFFFFF; + if (tb[IPV6_PXE_ARCH]) + arch = blobmsg_get_u32(tb[IPV6_PXE_ARCH]); + + return ipv6_pxe_entry_new(arch, url) ? -1 : 0; +} + void odhcpd_reload(void) { struct uci_context *uci = uci_alloc_context(); @@ -1405,6 +1788,15 @@ void odhcpd_reload(void) if (!strcmp(s->type, "host")) set_lease_from_uci(s); } + + /* 4. IPv6 PxE */ + ipv6_pxe_clear(); + uci_foreach_element(&dhcp->sections, e) { + struct uci_section* s = uci_to_section(e); + if (!strcmp(s->type, "boot6")) + ipv6_pxe_from_uci(s); + } + ipv6_pxe_dump(); } if (config.dhcp_statefile) { diff --git a/src/dhcpv4.c b/src/dhcpv4.c index cb0484f..1ae7254 100644 --- a/src/dhcpv4.c +++ b/src/dhcpv4.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,7 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface, enum dhcpv4_msg msg, const uint8_t *mac, const uint32_t reqaddr, uint32_t *leasetime, const char *hostname, const size_t hostname_len, const bool accept_fr_nonce, bool *incl_fr_opt, uint32_t *fr_serverid, - const char *reqopts, const size_t reqopts_len); + const uint8_t *reqopts, const size_t reqopts_len); static struct netevent_handler dhcpv4_netevent_handler = { .cb = dhcpv4_netevent_cb, }; static struct uloop_timeout valid_until_timeout = {.cb = valid_until_cb}; @@ -607,6 +608,14 @@ static void handle_dhcpv4(void *addr, void *data, size_t len, dhcpv4_handle_msg(addr, data, len, iface, dest_addr, dhcpv4_send_reply, &sock); } +/* DNR */ +struct dhcpv4_dnr { + uint16_t len; + uint16_t priority; + uint8_t adn_len; + uint8_t body[]; +}; + void dhcpv4_handle_msg(void *addr, void *data, size_t len, struct interface *iface, _unused void *dest_addr, send_reply_cb_t send_reply, void *opaque) @@ -654,10 +663,10 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len, uint32_t reqaddr = INADDR_ANY; uint32_t leasetime = 0; + char hostname[256]; size_t hostname_len = 0; + uint8_t *reqopts = NULL; size_t reqopts_len = 0; - char hostname[256]; - char reqopts[256]; bool accept_fr_nonce = false; bool incl_fr_opt = false; @@ -669,8 +678,8 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len, reqmsg = opt->data[0]; else if (opt->type == DHCPV4_OPT_REQOPTS && opt->len > 0) { reqopts_len = opt->len; + reqopts = alloca(reqopts_len); memcpy(reqopts, opt->data, reqopts_len); - reqopts[reqopts_len] = 0; } else if (opt->type == DHCPV4_OPT_HOSTNAME && opt->len > 0) { hostname_len = opt->len; memcpy(hostname, opt->data, hostname_len); @@ -850,12 +859,73 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len, dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER, 4 * iface->dhcpv4_dns_cnt, iface->dhcpv4_dns); - if (a && a->reqopts && iface->dhcpv4_ntp_cnt != 0) { - for(size_t opts = 0; a->reqopts[opts]; opts++) { - if (a->reqopts[opts] == DHCPV4_OPT_NTPSERVER) { - dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NTPSERVER, - 4 * iface->dhcpv4_ntp_cnt, iface->dhcpv4_ntp); + for (size_t opt = 0; a && opt < a->reqopts_len; opt++) { + switch (a->reqopts[opt]) { + case DHCPV4_OPT_NTPSERVER: + dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NTPSERVER, + 4 * iface->dhcpv4_ntp_cnt, iface->dhcpv4_ntp); + break; + + case DHCPV4_OPT_DNR: + struct dhcpv4_dnr *dnrs; + size_t dnrs_len = 0; + + for (size_t i = 0; i < iface->dnr_cnt; i++) { + struct dnr_options *dnr = &iface->dnr[i]; + + if (dnr->addr4_cnt == 0 && dnr->addr6_cnt > 0) + continue; + + dnrs_len += sizeof(struct dhcpv4_dnr); + dnrs_len += dnr->adn_len; + + if (dnr->addr4_cnt > 0 || dnr->svc_len > 0) { + dnrs_len += sizeof(uint8_t); + dnrs_len += dnr->addr4_cnt * sizeof(*dnr->addr4); + dnrs_len += dnr->svc_len; + } + } + + dnrs = alloca(dnrs_len); + uint8_t *pos = (uint8_t *)dnrs; + + for (size_t i = 0; i < iface->dnr_cnt; i++) { + struct dnr_options *dnr = &iface->dnr[i]; + struct dhcpv4_dnr *d4dnr = (struct dhcpv4_dnr *)pos; + uint16_t d4dnr_len = sizeof(uint16_t) + sizeof(uint8_t) + dnr->adn_len; + uint16_t d4dnr_priority_be = htons(dnr->priority); + uint16_t d4dnr_len_be; + + if (dnr->addr4_cnt == 0 && dnr->addr6_cnt > 0) + continue; + + /* memcpy as the struct is unaligned */ + memcpy(&d4dnr->priority, &d4dnr_priority_be, sizeof(d4dnr_priority_be)); + + d4dnr->adn_len = dnr->adn_len; + pos = d4dnr->body; + memcpy(pos, dnr->adn, dnr->adn_len); + pos += dnr->adn_len; + + if (dnr->addr4_cnt > 0 || dnr->svc_len > 0) { + uint8_t addr4_len = dnr->addr4_cnt * sizeof(*dnr->addr4); + + *(pos++) = addr4_len; + memcpy(pos, dnr->addr4, addr4_len); + pos += addr4_len; + memcpy(pos, dnr->svc, dnr->svc_len); + pos += dnr->svc_len; + + d4dnr_len += sizeof(addr4_len) + addr4_len + dnr->svc_len; + } + + d4dnr_len_be = htons(d4dnr_len); + memcpy(&d4dnr->len, &d4dnr_len_be, sizeof(d4dnr_len_be)); } + + dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNR, + dnrs_len, dnrs); + break; } } @@ -1035,7 +1105,7 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface, enum dhcpv4_msg msg, const uint8_t *mac, const uint32_t reqaddr, uint32_t *leasetime, const char *hostname, const size_t hostname_len, const bool accept_fr_nonce, bool *incl_fr_opt, - uint32_t *fr_serverid, const char* reqopts, const size_t reqopts_len) + uint32_t *fr_serverid, const uint8_t *reqopts, const size_t reqopts_len) { struct dhcp_assignment *a = find_assignment_by_hwaddr(iface, mac); struct lease *l = config_find_lease_by_mac(mac); @@ -1127,10 +1197,10 @@ dhcpv4_lease(struct interface *iface, enum dhcpv4_msg msg, const uint8_t *mac, } if (reqopts_len > 0) { - a->reqopts = realloc(a->reqopts, reqopts_len + 1); + a->reqopts = realloc(a->reqopts, reqopts_len); if (a->reqopts) { memcpy(a->reqopts, reqopts, reqopts_len); - a->reqopts[reqopts_len] = 0; + a->reqopts_len = reqopts_len; } } diff --git a/src/dhcpv4.h b/src/dhcpv4.h index b378bc1..f02529f 100644 --- a/src/dhcpv4.h +++ b/src/dhcpv4.h @@ -60,6 +60,7 @@ enum dhcpv4_opt { DHCPV4_OPT_AUTHENTICATION = 90, DHCPV4_OPT_SEARCH_DOMAIN = 119, DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE = 145, + DHCPV4_OPT_DNR = 162, DHCPV4_OPT_END = 255, }; diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index dde224d..b4ed9a0 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -666,15 +666,17 @@ static void managed_handle_pd_data(struct ustream *s, _unused int bytes_new) char *saveptr; for (char *line = strtok_r(data, "\n", &saveptr); line; line = strtok_r(NULL, "\n", &saveptr)) { - c->managed = realloc(c->managed, (c->managed_size + 1) * sizeof(*c->managed)); - struct odhcpd_ipaddr *n = &c->managed[c->managed_size]; + struct odhcpd_ipaddr *n; + char *saveptr2, *x; - char *saveptr2, *x = strtok_r(line, "/", &saveptr2); - if (!x || inet_pton(AF_INET6, x, &n->addr) < 1) + n = realloc(c->managed, (c->managed_size + 1) * sizeof(*c->managed)); + if (!n) continue; + c->managed = n; + n = &c->managed[c->managed_size]; - x = strtok_r(NULL, ",", &saveptr2); - if (sscanf(x, "%hhu", &n->prefix) < 1) + x = strtok_r(line, ",", &saveptr2); + if (odhcpd_parse_addr6_prefix(x, &n->addr.in6, &n->prefix)) continue; x = strtok_r(NULL, ",", &saveptr2); diff --git a/src/dhcpv6-pxe.c b/src/dhcpv6-pxe.c new file mode 100644 index 0000000..7169602 --- /dev/null +++ b/src/dhcpv6-pxe.c @@ -0,0 +1,104 @@ +#include +#include + +#include + +#include "dhcpv6.h" +#include "dhcpv6-pxe.h" + +struct ipv6_pxe_entry { + struct list_head list; // List head for linking + uint32_t arch; + + // Ready to send + struct __attribute__((packed)) { + uint16_t type; // In network endianess + uint16_t len; // In network endianess, without /0 + char payload[]; // Null-terminated here + } bootfile_url; +}; + +static struct ipv6_pxe_entry* ipv6_pxe_default = NULL; +LIST_HEAD(ipv6_pxe_list); + +const struct ipv6_pxe_entry* ipv6_pxe_entry_new(uint32_t arch, const char* url) { + size_t url_len = strlen(url); + struct ipv6_pxe_entry* ipe = malloc(sizeof(struct ipv6_pxe_entry) + url_len + 1); + if (!ipe) + return NULL; + + memcpy(ipe->bootfile_url.payload, url, url_len + 1); + ipe->bootfile_url.len = htons(url_len); + ipe->bootfile_url.type = htons(DHCPV6_OPT_BOOTFILE_URL); + + if (arch == 0xFFFFFFFF) { + ipv6_pxe_default = ipe; + } + else { + ipe->arch = arch; + list_add(&ipe->list, &ipv6_pxe_list); + } + + return ipe; +} + +const struct ipv6_pxe_entry* ipv6_pxe_of_arch(uint16_t arch) { + struct ipv6_pxe_entry* entry; + list_for_each_entry(entry, &ipv6_pxe_list, list) { + if (arch == entry->arch) + return entry; + } + + return ipv6_pxe_default; +} + +void ipv6_pxe_serve_boot_url(uint16_t arch, struct iovec* iov) { + const struct ipv6_pxe_entry* entry = ipv6_pxe_of_arch(arch); + + if (entry == NULL) { + // No IPv6 PxE bootfile defined + iov->iov_base = NULL; + iov->iov_len = 0; + } + else { + iov->iov_base = (void*)&(entry->bootfile_url); + iov->iov_len = 4 + ntohs(entry->bootfile_url.len); + syslog(LOG_INFO, "Serve IPv6 PxE, arch = %d, url = %s", arch, entry->bootfile_url.payload); + } +} + +void ipv6_pxe_dump(void) { + struct ipv6_pxe_entry* entry; + int count = 0; + + if (ipv6_pxe_default) + count++; + + list_for_each_entry(entry, &ipv6_pxe_list, list) { + count++; + } + + if (count) { + syslog(LOG_INFO, "IPv6 PxE URLs:\n"); + + list_for_each_entry(entry, &ipv6_pxe_list, list) { + syslog(LOG_INFO, " arch %04d = %s\n", entry->arch, entry->bootfile_url.payload); + } + + if (ipv6_pxe_default) + syslog(LOG_INFO, " Default = %s\n", ipv6_pxe_default->bootfile_url.payload); + } +} + +void ipv6_pxe_clear(void) { + struct ipv6_pxe_entry* entry, * temp; + list_for_each_entry_safe(entry, temp, &ipv6_pxe_list, list) { + list_del(&entry->list); + free(entry); + } + + if (ipv6_pxe_default) { + free(ipv6_pxe_default); + ipv6_pxe_default = NULL; + } +} diff --git a/src/dhcpv6-pxe.h b/src/dhcpv6-pxe.h new file mode 100644 index 0000000..0e3c227 --- /dev/null +++ b/src/dhcpv6-pxe.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +// The detail is hidden except for dhcpv6-pxe.c +struct ipv6_pxe_entry; + +const struct ipv6_pxe_entry* ipv6_pxe_entry_new(uint32_t arch, const char* url); +const struct ipv6_pxe_entry* ipv6_pxe_of_arch(uint16_t arch); +void ipv6_pxe_serve_boot_url(uint16_t arch, struct iovec* iov); +void ipv6_pxe_dump(void); +void ipv6_pxe_clear(void); diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 58b7d9f..3c0289d 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -25,6 +25,7 @@ #include "odhcpd.h" #include "dhcpv6.h" +#include "dhcpv6-pxe.h" #ifdef DHCPV4_SUPPORT #include "dhcpv4.h" #endif @@ -182,6 +183,8 @@ enum { IOV_SNTP_ADDR, IOV_RELAY_MSG, IOV_DHCPV4O6_SERVER, + IOV_DNR, + IOV_BOOTFILE_URL, IOV_TOTAL }; @@ -383,7 +386,6 @@ static void handle_client_request(void *addr, void *data, size_t len, /* SNTP */ struct in6_addr *sntp_addr_ptr = iface->dhcpv6_sntp; size_t sntp_cnt = 0; - struct { uint16_t type; uint16_t len; @@ -393,35 +395,113 @@ static void handle_client_request(void *addr, void *data, size_t len, uint8_t *ntp_ptr = iface->dhcpv6_ntp; uint16_t ntp_len = iface->dhcpv6_ntp_len; size_t ntp_cnt = 0; - struct { uint16_t type; uint16_t len; } ntp; + /* DNR */ + struct dhcpv6_dnr { + uint16_t type; + uint16_t len; + uint16_t priority; + uint16_t adn_len; + uint8_t body[]; + }; + struct dhcpv6_dnr *dnrs = NULL; + size_t dnrs_len = 0; + uint16_t otype, olen; - uint16_t *reqopts = NULL; uint8_t *odata; - size_t reqopts_len = 0; + uint16_t *reqopts = NULL; + size_t reqopts_cnt = 0; + /* FIXME: this should be merged with the second loop further down */ dhcpv6_for_each_option(opts, opts_end, otype, olen, odata) { + /* Requested options, array of uint16_t, RFC 8415 §21.7 */ if (otype == DHCPV6_OPT_ORO) { - reqopts_len = olen; + reqopts_cnt = olen / sizeof(uint16_t); reqopts = (uint16_t *)odata; + break; } } - for(size_t opt = 0; opt < reqopts_len/2; opt++) { - if (iface->dhcpv6_sntp_cnt != 0 && - DHCPV6_OPT_SNTP_SERVERS == ntohs(reqopts[opt])) { + /* Requested options */ + for (size_t i = 0; i < reqopts_cnt; i++) { + uint16_t opt = ntohs(reqopts[i]); + + switch (opt) { + case DHCPV6_OPT_SNTP_SERVERS: sntp_cnt = iface->dhcpv6_sntp_cnt; dhcpv6_sntp.type = htons(DHCPV6_OPT_SNTP_SERVERS); dhcpv6_sntp.len = htons(sntp_cnt * sizeof(*sntp_addr_ptr)); - } else if (iface->dhcpv6_ntp_cnt != 0 && - DHCPV6_OPT_NTP_SERVERS == ntohs(reqopts[opt])) { + break; + + case DHCPV6_OPT_NTP_SERVERS: ntp_cnt = iface->dhcpv6_ntp_cnt; ntp.type = htons(DHCPV6_OPT_NTP_SERVERS); ntp.len = htons(ntp_len); + break; + + case DHCPV6_OPT_DNR: + for (size_t i = 0; i < iface->dnr_cnt; i++) { + struct dnr_options *dnr = &iface->dnr[i]; + + if (dnr->addr6_cnt == 0 && dnr->addr4_cnt > 0) + continue; + + dnrs_len += sizeof(struct dhcpv6_dnr); + dnrs_len += dnr->adn_len; + + if (dnr->addr6_cnt > 0 || dnr->svc_len > 0) { + dnrs_len += sizeof(uint16_t); + dnrs_len += dnr->addr6_cnt * sizeof(*dnr->addr6); + dnrs_len += dnr->svc_len; + } + } + + dnrs = alloca(dnrs_len); + uint8_t *pos = (uint8_t *)dnrs; + + for (size_t i = 0; i < iface->dnr_cnt; i++) { + struct dnr_options *dnr = &iface->dnr[i]; + struct dhcpv6_dnr *d6dnr = (struct dhcpv6_dnr *)pos; + uint16_t d6dnr_type_be = htons(DHCPV6_OPT_DNR); + uint16_t d6dnr_len = 2 * sizeof(uint16_t) + dnr->adn_len; + uint16_t d6dnr_len_be; + uint16_t d6dnr_priority_be = htons(dnr->priority); + uint16_t d6dnr_adn_len_be = htons(dnr->adn_len); + + if (dnr->addr6_cnt == 0 && dnr->addr4_cnt > 0) + continue; + + /* memcpy as the struct is unaligned */ + memcpy(&d6dnr->type, &d6dnr_type_be, sizeof(d6dnr_type_be)); + memcpy(&d6dnr->priority, &d6dnr_priority_be, sizeof(d6dnr_priority_be)); + memcpy(&d6dnr->adn_len, &d6dnr_adn_len_be, sizeof(d6dnr_adn_len_be)); + + pos = d6dnr->body; + memcpy(pos, dnr->adn, dnr->adn_len); + pos += dnr->adn_len; + + if (dnr->addr6_cnt > 0 || dnr->svc_len > 0) { + uint16_t addr6_len = dnr->addr6_cnt * sizeof(*dnr->addr6); + uint16_t addr6_len_be = htons(addr6_len); + + memcpy(pos, &addr6_len_be, sizeof(addr6_len_be)); + pos += sizeof(addr6_len_be); + memcpy(pos, dnr->addr6, addr6_len); + pos += addr6_len; + memcpy(pos, dnr->svc, dnr->svc_len); + pos += dnr->svc_len; + + d6dnr_len += sizeof(addr6_len_be) + addr6_len + dnr->svc_len; + } + + d6dnr_len_be = htons(d6dnr_len); + memcpy(&d6dnr->len, &d6dnr_len_be, sizeof(d6dnr_len_be)); + } + break; } } @@ -477,8 +557,10 @@ static void handle_client_request(void *addr, void *data, size_t len, [IOV_NTP_ADDR] = {ntp_ptr, (ntp_cnt) ? ntp_len : 0}, [IOV_SNTP] = {&dhcpv6_sntp, (sntp_cnt) ? sizeof(dhcpv6_sntp) : 0}, [IOV_SNTP_ADDR] = {sntp_addr_ptr, sntp_cnt * sizeof(*sntp_addr_ptr)}, + [IOV_DNR] = {dnrs, dnrs_len}, [IOV_RELAY_MSG] = {NULL, 0}, [IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0}, + [IOV_BOOTFILE_URL] = {NULL, 0} }; if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW) @@ -584,6 +666,9 @@ static void handle_client_request(void *addr, void *data, size_t len, break; } } + } else if (otype == DHCPV6_OPT_CLIENT_ARCH) { + uint16_t arch_code = ntohs(((uint16_t*)odata)[0]); + ipv6_pxe_serve_boot_url(arch_code, &iov[IOV_BOOTFILE_URL]); } } @@ -634,6 +719,7 @@ static void handle_client_request(void *addr, void *data, size_t len, dest.msg_type = DHCPV6_MSG_DHCPV4_RESPONSE; } else #endif /* DHCPV4_SUPPORT */ + if (hdr->msg_type != DHCPV6_MSG_INFORMATION_REQUEST) { ssize_t ialen = dhcpv6_ia_handle_IAs(pdbuf, sizeof(pdbuf), iface, addr, (const void *)hdr, opts_end); @@ -651,7 +737,8 @@ static void handle_client_request(void *addr, void *data, size_t 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_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len + + iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].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); diff --git a/src/dhcpv6.h b/src/dhcpv6.h index b925928..a394453 100644 --- a/src/dhcpv6.h +++ b/src/dhcpv6.h @@ -59,10 +59,14 @@ #define DHCPV6_OPT_INFO_REFRESH 32 #define DHCPV6_OPT_FQDN 39 #define DHCPV6_OPT_NTP_SERVERS 56 +#define DHCPV6_OPT_BOOTFILE_URL 59 +#define DHCPV6_OPT_BOOTFILE_PARAM 60 +#define DHCPV6_OPT_CLIENT_ARCH 61 #define DHCPV6_OPT_SOL_MAX_RT 82 #define DHCPV6_OPT_INF_MAX_RT 83 #define DHCPV6_OPT_DHCPV4_MSG 87 #define DHCPV6_OPT_4O6_SERVER 88 +#define DHCPV6_OPT_DNR 144 #define DHCPV6_DUID_VENDOR 2 diff --git a/src/netlink.c b/src/netlink.c index 368e69c..6b38caa 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -807,12 +807,6 @@ static int cb_linklocal_valid(struct nl_msg *msg, void *arg) return NL_SKIP; memset(&addrs[ctxt->ret], 0, sizeof(addrs[ctxt->ret])); - - addrs = realloc(addrs, sizeof(*addrs)*(ctxt->ret + 1)); - if (!addrs) - return NL_SKIP; - - memcpy(&addrs[ctxt->ret].addr, &addr, sizeof(addrs[ctxt->ret].addr)); if (ifa->ifa_flags & IFA_F_TENTATIVE) diff --git a/src/odhcpd.c b/src/odhcpd.c index 0849b43..7b6420c 100644 --- a/src/odhcpd.c +++ b/src/odhcpd.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -539,6 +540,39 @@ void odhcpd_bmemcpy(void *av, const void *bv, size_t bits) } +int odhcpd_parse_addr6_prefix(const char *str, struct in6_addr *addr, uint8_t *prefix) +{ + size_t len; + char *delim; + + *prefix = 0; + if (!str) + return -1; + + len = strlen(str); + + char buf[len + 1]; + memcpy(buf, str, len); + buf[len] = '\0'; + + delim = memchr(buf, '/', len); + if (!delim) + return -1; + + *(delim++) = '\0'; + + if (inet_pton(AF_INET6, buf, addr) != 1) + return -1; + + if (sscanf(delim, "%" SCNu8, prefix) != 1 || *prefix > 128) { + *prefix = 0; + return -1; + } + + return 0; +} + + int odhcpd_netmask2bitlen(bool inet6, void *mask) { int bits; diff --git a/src/odhcpd.h b/src/odhcpd.h index f424ebd..ae2e571 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -37,6 +37,9 @@ // RFC 8781 defines PREF64 option #define ND_OPT_PREF64 38 +// RFC 9463 - Discovery of Network-designated Resolvers (DNR) +#define ND_OPT_DNR 144 + #define INFINITE_VALID(x) ((x) == 0) #define _unused __attribute__((unused)) @@ -240,7 +243,9 @@ struct dhcp_assignment { unsigned int flags; uint32_t leasetime; char *hostname; - char *reqopts; + uint8_t *reqopts; + size_t reqopts_len; + #define hwaddr mac uint8_t mac[6]; @@ -249,6 +254,26 @@ struct dhcp_assignment { }; +// DNR - RFC9463 +struct dnr_options { + uint16_t priority; + + uint32_t lifetime; + bool lifetime_set; + + uint8_t *adn; + uint16_t adn_len; + + struct in_addr *addr4; + size_t addr4_cnt; + struct in6_addr *addr6; + size_t addr6_cnt; + + uint8_t *svc; + uint16_t svc_len; +}; + + struct interface { struct avl_node avl; @@ -314,7 +339,8 @@ struct interface { bool ra_useleasetime; bool ra_dns; uint8_t pref64_length; - struct in6_addr pref64_addr; + uint8_t pref64_plc; + uint32_t pref64_prefix[3]; struct odhcpd_ip6prefix *ra_addroutes; size_t ra_addroutes_cnt; bool no_dynamic_dhcp; @@ -379,6 +405,10 @@ struct interface { // SNTP struct in6_addr *dhcpv6_sntp; size_t dhcpv6_sntp_cnt; + + // DNR + struct dnr_options *dnr; + size_t dnr_cnt; }; extern struct avl_tree interfaces; @@ -435,6 +465,7 @@ const char *odhcpd_print_mac(const uint8_t *mac, const size_t len); int odhcpd_bmemcmp(const void *av, const void *bv, size_t bits); void odhcpd_bmemcpy(void *av, const void *bv, size_t bits); +int odhcpd_parse_addr6_prefix(const char *str, struct in6_addr *addr, uint8_t *prefix); int odhcpd_netmask2bitlen(bool v6, void *mask); bool odhcpd_bitlen2netmask(bool v6, unsigned int bits, void *mask); bool odhcpd_valid_hostname(const char *name); diff --git a/src/router.c b/src/router.c index 363d36c..274edeb 100644 --- a/src/router.c +++ b/src/router.c @@ -396,6 +396,7 @@ enum { IOV_RA_DNS, IOV_RA_SEARCH, IOV_RA_PREF64, + IOV_RA_DNR, IOV_RA_ADV_INTERVAL, IOV_RA_TOTAL, }; @@ -437,7 +438,16 @@ struct nd_opt_pref64_info { uint8_t type; uint8_t len; uint16_t lifetime_plc; - uint32_t addr[3]; + uint32_t prefix[3]; +}; + +struct nd_opt_dnr_info { + uint8_t type; + uint8_t len; + uint16_t priority; + uint32_t lifetime; + uint16_t adn_len; + uint8_t body[]; }; /* Returns 0 on success, -1 on memory error (`routes` is unmodified in that case) */ @@ -496,10 +506,11 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr struct nd_opt_search_list *search = NULL; struct nd_opt_route_info *routes = NULL; struct nd_opt_pref64_info *pref64 = NULL; + struct nd_opt_dnr_info *dnrs = NULL; struct nd_opt_adv_interval adv_interval; struct iovec iov[IOV_RA_TOTAL]; struct sockaddr_in6 dest; - size_t dns_sz = 0, search_sz = 0, pref64_sz = 0; + size_t dns_sz = 0, search_sz = 0, pref64_sz = 0, dnrs_sz = 0; size_t pfxs_cnt = 0, routes_cnt = 0; ssize_t valid_addr_cnt = 0, invalid_addr_cnt = 0; /* @@ -776,62 +787,66 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr if (iface->pref64_length) { /* RFC 8781 § 4.1 rounding up lifetime to multiple of 8 */ uint16_t pref64_lifetime = lifetime < (UINT16_MAX - 7) ? lifetime + 7 : UINT16_MAX; - uint8_t prefix_length_code; - uint32_t mask_a1, mask_a2; - - switch (iface->pref64_length) { - case 96: - prefix_length_code = 0; - mask_a1 = 0xffffffff; - mask_a2 = 0xffffffff; - break; - case 64: - prefix_length_code = 1; - mask_a1 = 0xffffffff; - mask_a2 = 0x00000000; - break; - case 56: - prefix_length_code = 2; - mask_a1 = 0xffffff00; - mask_a2 = 0x00000000; - break; - case 48: - prefix_length_code = 3; - mask_a1 = 0xffff0000; - mask_a2 = 0x00000000; - break; - case 40: - prefix_length_code = 4; - mask_a1 = 0xff000000; - mask_a2 = 0x00000000; - break; - case 32: - prefix_length_code = 5; - mask_a1 = 0x00000000; - mask_a2 = 0x00000000; - break; - default: - syslog(LOG_WARNING, "Invalid PREF64 prefix size (%d), " - "ignoring ra_pref64 option!", iface->pref64_length); - goto pref64_out; - break; - } pref64_sz = sizeof(*pref64); pref64 = alloca(pref64_sz); - memset(pref64, 0, pref64_sz); pref64->type = ND_OPT_PREF64; pref64->len = 2; pref64->lifetime_plc = htons((0xfff8 & pref64_lifetime) | - (0x7 & prefix_length_code)); - pref64->addr[0] = iface->pref64_addr.s6_addr32[0]; - pref64->addr[1] = iface->pref64_addr.s6_addr32[1] & htonl(mask_a1); - pref64->addr[2] = iface->pref64_addr.s6_addr32[2] & htonl(mask_a2); + (0x7 & iface->pref64_plc)); + memcpy(pref64->prefix, iface->pref64_prefix, sizeof(pref64->prefix)); } -pref64_out: iov[IOV_RA_PREF64].iov_base = (char *)pref64; iov[IOV_RA_PREF64].iov_len = pref64_sz; + if (iface->dnr_cnt) { + size_t dnr_sz[iface->dnr_cnt]; + + for (unsigned i = 0; i < iface->dnr_cnt; i++) { + dnr_sz[i] = sizeof(struct nd_opt_dnr_info) + iface->dnr[i].adn_len; + if (iface->dnr[i].addr6_cnt > 0 || iface->dnr[i].svc_len > 0) { + dnr_sz[i] += 2 + iface->dnr[i].addr6_cnt * sizeof(struct in6_addr); + dnr_sz[i] += 2 + iface->dnr[i].svc_len; + } + dnr_sz[i] = (dnr_sz[i] + 7) & ~7; + dnrs_sz += dnr_sz[i]; + } + + /* dnrs are sized in multiples of 8, so each dnr should be aligned */ + dnrs = alloca(dnrs_sz); + memset(dnrs, 0, dnrs_sz); + + uint8_t *pos = (uint8_t *)dnrs; + for (unsigned i = 0; i < iface->dnr_cnt; pos += dnr_sz[i], i++) { + struct nd_opt_dnr_info *dnr = (struct nd_opt_dnr_info *)pos; + size_t dnr_addr6_sz = iface->dnr[i].addr6_cnt * sizeof(struct in6_addr); + uint8_t *tmp = dnr->body; + + dnr->type = ND_OPT_DNR; + dnr->len = dnr_sz[i] / 8; + dnr->priority = htons(iface->dnr[i].priority); + if (iface->dnr[i].lifetime_set) + dnr->lifetime = htonl(iface->dnr[i].lifetime); + else + dnr->lifetime = htonl(lifetime); + + dnr->adn_len = htons(iface->dnr[i].adn_len); + memcpy(tmp, iface->dnr[i].adn, iface->dnr[i].adn_len); + tmp += iface->dnr[i].adn_len; + + *(tmp++) = dnr_addr6_sz >> 8; + *(tmp++) = dnr_addr6_sz & 0xff; + memcpy(tmp, iface->dnr[i].addr6, dnr_addr6_sz); + tmp += dnr_addr6_sz; + + *(tmp++) = iface->dnr[i].svc_len >> 8; + *(tmp++) = iface->dnr[i].svc_len & 0xff; + memcpy(tmp, iface->dnr[i].svc, iface->dnr[i].svc_len); + } + } + iov[IOV_RA_DNR].iov_base = (char *)dnrs; + iov[IOV_RA_DNR].iov_len = dnrs_sz; + /* * RFC7084 § 4.3 : * L-3: An IPv6 CE router MUST advertise itself as a router for the diff --git a/src/ubus.c b/src/ubus.c index 45b29a4..be3deac 100644 --- a/src/ubus.c +++ b/src/ubus.c @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -49,13 +50,11 @@ static int handle_dhcpv4_leases(struct ubus_context *ctx, _unused struct ubus_ob blobmsg_add_string(&b, "hostname", (c->hostname) ? c->hostname : ""); blobmsg_add_u8(&b, "accept-reconf-nonce", c->accept_fr_nonce); - if (c->reqopts) { - int opt = 0; - int chars = 0; - buf = blobmsg_alloc_string_buffer(&b, "reqopts", strlen(c->reqopts) * 4 + 1); - for(; c->reqopts[opt]; opt++) - chars += snprintf(buf + chars, 6, "%u,", (uint8_t)c->reqopts[opt]); - buf[chars - 1] = '\0'; + if (c->reqopts_len > 0) { + buf = blobmsg_alloc_string_buffer(&b, "reqopts", c->reqopts_len * 4 + 1); + for (size_t i = 0; i < c->reqopts_len; i++) + buf += snprintf(buf, 5, "%" PRIu8 ",", c->reqopts[i]); + buf[-1] = '\0'; blobmsg_add_string_buffer(&b); }