Skip to content

Commit

Permalink
router: add option to include custom routes in RA
Browse files Browse the repository at this point in the history
Introduces new configuration option `ra_addroutes` for adding custom route(s)
in route advertisement (RA) messages when no default route is advertised.

This is useful when, for example, end devices are given only ULA IPv6
addresses (e.g. from fc00:0:100::/48), but one still needs inter subnet
communication to e.g. fc00:0:200::/48. Currently, this wouldn't be possible
without static route on each end device as the odhcpd server doesn't present
itself as default router and no route information is given except for local
ULA subnet (fc00:0:100::/48).

New configuration option `ra_addroutes` makes it possible to advertise
arbitrary routes to the end devices and thus making correct routing possible
even when no default route (or public IPv6 prefix) is available.

Option `ra_addroutes` exists under both `odhcpd` section and interfaces'
sections. Latter sets up interface-specific routes. Routes configured under
`odhcpd` are global and advertised for all interfaces (including those with
interface-specific additional routes).

Config example:

config odhcpd 'odhcpd'
	...
	list ra_addroutes 'fc00:10::/32'
	list ra_addroutes 'fc00:20::/32'

config dhcp 'lan'
	...
	list ra_addroutes 'fd50:60:70::/48'

Fixes: openwrt#74

Signed-off-by: Dávid Benko <[email protected]>
  • Loading branch information
DavidB137 committed Oct 12, 2024
1 parent a298823 commit b8f6ff0
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ leasefile string DHCP/v6 lease/hostfile
leasetrigger string Lease trigger script
hostsfile string DHCP/v6 hostfile
loglevel integer 6 Syslog level priority (0-7)
ra_addroutes list string Additional announced routes for all interfaces
[IPv6 prefix] (used if RA doesn't include default router info already)


Sections of type dhcp (configure DHCP / DHCPv6 / RA / NDP service)
Expand Down Expand Up @@ -146,6 +148,8 @@ ra_mtu integer - MTU to be advertised in
RA messages
ra_dns bool 1 Announce DNS configuration in
RA messages (RFC8106)
ra_addroutes list string Additional announced routes (used if RA
[IPv6 prefix] doesn't include default router info already)
ra_pref64 string Announce PREF64 option
[IPv6 prefix] for NAT64 prefix (RFC8781)
ndproxy_routing bool 1 Learn routes from NDP
Expand Down
91 changes: 90 additions & 1 deletion src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct vlist_tree leases = VLIST_TREE_INIT(leases, lease_cmp, lease_update, true
AVL_TREE(interfaces, avl_strcmp, false, NULL);
struct config config = {.legacy = false, .main_dhcpv4 = false,
.dhcp_cb = NULL, .dhcp_statefile = NULL, .dhcp_hostsfile = NULL,
.log_level = LOG_WARNING};
.log_level = LOG_WARNING, .ra_addroutes = NULL, .ra_addroutes_cnt = 0};

#define START_DEFAULT 100
#define LIMIT_DEFAULT 150
Expand Down Expand Up @@ -85,6 +85,7 @@ enum {
IFACE_ATTR_RA_HOPLIMIT,
IFACE_ATTR_RA_MTU,
IFACE_ATTR_RA_DNS,
IFACE_ATTR_RA_ADDROUTES,
IFACE_ATTR_RA_PREF64,
IFACE_ATTR_PD_MANAGER,
IFACE_ATTR_PD_CER,
Expand Down Expand Up @@ -140,6 +141,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
[IFACE_ATTR_RA_HOPLIMIT] = { .name = "ra_hoplimit", .type = BLOBMSG_TYPE_INT32 },
[IFACE_ATTR_RA_MTU] = { .name = "ra_mtu", .type = BLOBMSG_TYPE_INT32 },
[IFACE_ATTR_RA_DNS] = { .name = "ra_dns", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_RA_ADDROUTES] = { .name = "ra_addroutes", .type = BLOBMSG_TYPE_ARRAY },
[IFACE_ATTR_RA_PREF64] = { .name = "ra_pref64", .type = BLOBMSG_TYPE_STRING },
[IFACE_ATTR_NDPROXY_ROUTING] = { .name = "ndproxy_routing", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_NDPROXY_SLAVE] = { .name = "ndproxy_slave", .type = BLOBMSG_TYPE_BOOL },
Expand Down Expand Up @@ -181,6 +183,7 @@ enum {
ODHCPD_ATTR_LEASETRIGGER,
ODHCPD_ATTR_LOGLEVEL,
ODHCPD_ATTR_HOSTSFILE,
ODHCPD_ATTR_RA_ADDROUTES,
ODHCPD_ATTR_MAX
};

Expand All @@ -191,6 +194,7 @@ static const struct blobmsg_policy odhcpd_attrs[ODHCPD_ATTR_MAX] = {
[ODHCPD_ATTR_LEASETRIGGER] = { .name = "leasetrigger", .type = BLOBMSG_TYPE_STRING },
[ODHCPD_ATTR_LOGLEVEL] = { .name = "loglevel", .type = BLOBMSG_TYPE_INT32 },
[ODHCPD_ATTR_HOSTSFILE] = { .name = "hostsfile", .type = BLOBMSG_TYPE_STRING },
[ODHCPD_ATTR_RA_ADDROUTES] = { .name = "ra_addroutes", .type = BLOBMSG_TYPE_ARRAY },
};

const struct uci_blob_param_list odhcpd_attr_list = {
Expand Down Expand Up @@ -240,6 +244,7 @@ static void clean_interface(struct interface *iface)
free(iface->dhcpv4_router);
free(iface->dhcpv4_dns);
free(iface->dhcpv6_raw);
free(iface->ra_addroutes);
free(iface->filter_class);
free(iface->dhcpv4_ntp);
free(iface->dhcpv6_ntp);
Expand Down Expand Up @@ -312,6 +317,50 @@ static int parse_ra_flags(uint8_t *flags, struct blob_attr *attr)
return 0;
}

/* Returns 0 on success, -1 on invalid value, -2 on memory error
(`routes` is unmodified in that case) */
static int parse_ra_addroutes(struct blob_attr *attr, struct odhcpd_ip6prefix **routes,
size_t *routes_cnt)
{
if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING ||
!blobmsg_check_attr(attr, false))
return -1;

const char *str = blobmsg_get_string(attr);
char *astr = malloc(strlen(str) + 1);
char *delim;
int l;
struct odhcpd_ip6prefix prefix;

if (!astr || !strcpy(astr, str))
return -2;

if ((delim = strchr(astr, '/')) == NULL || (*(delim++) = 0) ||
sscanf(delim, "%i", &l) == 0 || l < 1 || l > 128 ||
inet_pton(AF_INET6, astr, &prefix.addr) == 0 ||
IN6_IS_ADDR_UNSPECIFIED(&prefix.addr)) {
return -1;
} else {
prefix.len = l;

struct odhcpd_ip6prefix *tmp = realloc(*routes, (*routes_cnt + 1) *
sizeof(**routes));
if (!tmp) {
if (astr)
free(astr);
return -2;
}

*routes = tmp;
(*routes)[*routes_cnt] = prefix;
++(*routes_cnt);
}

if (astr)
free(astr);
return 0;
}

static void set_config(struct uci_section *s)
{
struct blob_attr *tb[ODHCPD_ATTR_MAX], *c;
Expand Down Expand Up @@ -349,6 +398,28 @@ static void set_config(struct uci_section *s)
setlogmask(LOG_UPTO(config.log_level));
}
}

if ((c = tb[ODHCPD_ATTR_RA_ADDROUTES])) {
free(config.ra_addroutes);
config.ra_addroutes = NULL;
config.ra_addroutes_cnt = 0;

struct blob_attr *cur;
unsigned rem;

blobmsg_for_each_attr(cur, c, rem) {
int ec = parse_ra_addroutes(cur, &(config.ra_addroutes),
&(config.ra_addroutes_cnt));

if (ec == -1) {
syslog(LOG_ERR, "Invalid %s value",
odhcpd_attrs[ODHCPD_ATTR_RA_ADDROUTES].name);
} else if (ec == -2) {
syslog(LOG_ERR, "Memory allocation failed for %s",
odhcpd_attrs[ODHCPD_ATTR_RA_ADDROUTES].name);
}
}
}
}

static double parse_leasetime(struct blob_attr *c) {
Expand Down Expand Up @@ -984,6 +1055,24 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr

if ((c = tb[IFACE_ATTR_RA_DNS]))
iface->ra_dns = blobmsg_get_bool(c);

if ((c = tb[IFACE_ATTR_RA_ADDROUTES])) {
struct blob_attr *cur;
unsigned rem;

blobmsg_for_each_attr(cur, c, rem) {
int ec = parse_ra_addroutes(cur, &(iface->ra_addroutes),
&(iface->ra_addroutes_cnt));

if (ec == -1) {
syslog(LOG_ERR, "Invalid %s value configured for interface '%s'",
iface_attrs[IFACE_ATTR_RA_ADDROUTES].name, iface->name);
} else if (ec == -2) {
syslog(LOG_ERR, "Memory allocation failed for %s on interface '%s'",
iface_attrs[IFACE_ATTR_RA_ADDROUTES].name, iface->name);
}
}
}

if ((c = tb[IFACE_ATTR_RA_PREF64])) {
const char *str = blobmsg_get_string(c);
Expand Down
11 changes: 11 additions & 0 deletions src/odhcpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ struct odhcpd_ipaddr {
};
};

struct odhcpd_ip6prefix {
struct in6_addr addr;
uint8_t len;
};

enum odhcpd_mode {
MODE_DISABLED,
MODE_SERVER,
Expand All @@ -167,6 +172,10 @@ struct config {
char *dhcp_statefile;
char *dhcp_hostsfile;
int log_level;

// Global RA settings
struct odhcpd_ip6prefix *ra_addroutes;
size_t ra_addroutes_cnt;
};


Expand Down Expand Up @@ -306,6 +315,8 @@ struct interface {
bool ra_dns;
uint8_t pref64_length;
struct in6_addr pref64_addr;
struct odhcpd_ip6prefix *ra_addroutes;
size_t ra_addroutes_cnt;
bool no_dynamic_dhcp;
bool have_link_local;
uint8_t pio_filter_length;
Expand Down
72 changes: 72 additions & 0 deletions src/router.c
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,51 @@ struct nd_opt_pref64_info {
uint32_t addr[3];
};

/* Returns 0 on success, -1 on memory error (`routes` is unmodified in that case) */
static int append_router_advert_routes(struct nd_opt_route_info **routes,
size_t *routes_cnt, struct odhcpd_ip6prefix *addroutes, size_t addroutes_cnt,
int route_preference, uint32_t lifetime)
{
if (addroutes_cnt == 0) {
return 0;
}

struct nd_opt_route_info *routes_tmp = realloc(*routes,
sizeof(**routes) * (*routes_cnt + addroutes_cnt));

if (!routes_tmp) {
return -1;
}

*routes = routes_tmp;

memset(&((*routes)[*routes_cnt]), 0, addroutes_cnt *
sizeof(**routes));

for (size_t i = 0; i < addroutes_cnt; ++i) {
size_t routes_i = *routes_cnt + i;

(*routes)[routes_i].type = ND_OPT_ROUTE_INFO;
(*routes)[routes_i].len = sizeof(**routes) / 8;
(*routes)[routes_i].prefix = addroutes[i].len;
(*routes)[routes_i].flags = 0;
if (route_preference < 0)
(*routes)[routes_i].flags |= ND_RA_PREF_LOW;
else if (route_preference > 0)
(*routes)[routes_i].flags |= ND_RA_PREF_HIGH;

(*routes)[routes_i].lifetime = htonl(lifetime);
(*routes)[routes_i].addr[0] = addroutes[i].addr.s6_addr32[0];
(*routes)[routes_i].addr[1] = addroutes[i].addr.s6_addr32[1];
(*routes)[routes_i].addr[2] = addroutes[i].addr.s6_addr32[2];
(*routes)[routes_i].addr[3] = addroutes[i].addr.s6_addr32[3];
}

*routes_cnt += addroutes_cnt;

return 0;
}

/* Router Advert server mode */
static int send_router_advert(struct interface *iface, const struct in6_addr *from)
{
Expand Down Expand Up @@ -797,6 +842,33 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
* WAN interface.
*/

/* If router is not default */
if (!default_route || !valid_prefix) {
/* Additional global RA routes */
if (config.ra_addroutes_cnt > 0) {
int ec = append_router_advert_routes(&routes, &routes_cnt,
config.ra_addroutes, config.ra_addroutes_cnt,
iface->route_preference, lifetime);

if (ec == -1) {
syslog(LOG_ERR, "Realloc failed for additional global RA routes on %s",
iface->name);
}
}

/* Additional interface RA routes */
if (iface->ra_addroutes_cnt > 0) {
int ec = append_router_advert_routes(&routes, &routes_cnt,
iface->ra_addroutes, iface->ra_addroutes_cnt,
iface->route_preference, lifetime);

if (ec == -1) {
syslog(LOG_ERR, "Realloc failed for additional iface RA routes on %s",
iface->name);
}
}
}

for (ssize_t i = 0; i < valid_addr_cnt; ++i) {
struct odhcpd_ipaddr *addr = &addrs[i];
struct nd_opt_route_info *tmp;
Expand Down

0 comments on commit b8f6ff0

Please sign in to comment.