From ca463da189cc544a395e032e920ba4b5ba317714 Mon Sep 17 00:00:00 2001 From: Alin Nastac Date: Thu, 21 Dec 2023 10:04:25 +0100 Subject: [PATCH 1/2] router: implement RFC 7084 errata 7699 RFC 7084 L-3 requires router advertisement daemon to send RIO for every prefix delegation that allocates a prefix on the interface. There is one special case though where PIO advertise the on-link prefix route that conflicts with the RIO prefix. Example: - CE-Router receives IA-PD 2001:db8:1234::/64 - this prefix delegation gets assigned to the lan interface - odhcpd advertise on-link PIO with prefix 2001:db8:1234::/64 If original RFC 7084 L-3 would be followed, RA will also contain a RIO with prefix 2001:db8:1234::/64 which will require lan hosts to add the routes 2001:db8:1234::/64 dev br-lan # the on-link prefix route 2001:db8:1234::/64 dev br-lan nexthop fe80::1 # RIO route Signed-off-by: Alin Nastac --- src/router.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/router.c b/src/router.c index d5ef7f8..4c47771 100644 --- a/src/router.c +++ b/src/router.c @@ -782,7 +782,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr struct nd_opt_route_info *tmp; uint32_t valid; - if (addr->dprefix >= 64 || addr->dprefix == 0 || addr->valid <= (uint32_t)now) { + if (addr->dprefix > 64 || addr->dprefix == 0 || addr->valid <= (uint32_t)now) { syslog(LOG_INFO, "Address %s (dprefix %d, valid %u) not suitable as RA route on %s", inet_ntop(AF_INET6, &addr->addr.in6, buf, sizeof(buf)), addr->dprefix, addr->valid, iface->name); @@ -804,6 +804,21 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr addr->addr.in6.s6_addr32[1] = 0; } + if (!iface->ra_not_onlink) { + bool fully_used_in_pio = false; + for (size_t i = 0; i < pfxs_cnt; ++i) { + if (addr->dprefix == pfxs[i].nd_opt_pi_prefix_len && + !odhcpd_bmemcmp(&pfxs[i].nd_opt_pi_prefix, + &addr->addr.in6, addr->dprefix)) { + fully_used_in_pio = true; + break; + } + } + + if (fully_used_in_pio) + continue; /* correspondent PIO use the entire prefix delegation (see RFC 7084 errata 7699) */ + } + tmp = realloc(routes, sizeof(*routes) * (routes_cnt + 1)); if (!tmp) { syslog(LOG_ERR, "Realloc failed for RA route option on %s", iface->name); From 5c732e05e9e159d415c27115108282b46aeb98f0 Mon Sep 17 00:00:00 2001 From: Alin Nastac Date: Mon, 29 Jan 2024 10:33:55 +0100 Subject: [PATCH 2/2] advertise removed prefix delegation in RIO with lifetime 0 RFC 4191 section 4 states: ... When ceasing to be an advertising interface and sending Router Advertisements with a Router Lifetime of zero, the Router Advertisement SHOULD also set the Route Lifetime to zero in all Route Information Options. Since RIOs reflect the prefix delegations owned by the router, it is reasonable to assume that invalid RIOs should be sent along invalid PIOs required by RFC 7084 L-13. This extends the functionality introduced in commit 83e14f4558 where removed addresses were advertised as invalid PIO in 3 consecutive RAs. Other related improvements introduced here: - further precaution has been taken in netlink.c to prevent possible overlapping between current list of interface IPv6 addresses and the list of invalid addresses - IPv6 route parsing was moved to netlink.c and was splitted in 2 functions; this allows dprefix caching in iface->addr6 list, necessary for generating invalid RIOs after prefix get deleted - all prefixes are advertised as invalid on odhcpd shutdown Signed-off-by: Alin Nastac --- src/netlink.c | 85 ++++++++++++++++++++++++++++++++++++- src/odhcpd.c | 5 ++- src/odhcpd.h | 23 +++++++++- src/router.c | 114 +++++++++++++++----------------------------------- 4 files changed, 142 insertions(+), 85 deletions(-) diff --git a/src/netlink.c b/src/netlink.c index c2b9576..bff311f 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -26,6 +26,7 @@ #include #include +#include #include #include "odhcpd.h" @@ -54,8 +55,15 @@ static struct event_socket rtnl_event = { .sock_bufsize = 133120, }; -int netlink_init(void) +static FILE *fp_ipv6_route = NULL; + +int netlink_init(bool ipv6) { + if (ipv6 && !(fp_ipv6_route = fopen("/proc/net/ipv6_route", "r"))) { + syslog(LOG_ERR, "fopen(/proc/net/ipv6_route): %m"); + goto err; + } + rtnl_socket = create_socket(NETLINK_ROUTE); if (!rtnl_socket) { syslog(LOG_ERR, "Unable to open nl socket: %m"); @@ -100,9 +108,67 @@ int netlink_init(void) rtnl_event.ev.uloop.fd = -1; } + if (fp_ipv6_route) { + fclose(fp_ipv6_route); + fp_ipv6_route = NULL; + } + return -1; } +/* Detect whether a default IPv6 route exists */ +bool netlink_default_ipv6_route_exists() +{ + bool found_default = false; + char line[512], ifname[16]; + + if (!fp_ipv6_route) + return false; + rewind(fp_ipv6_route); + + while (fgets(line, sizeof(line), fp_ipv6_route)) { + if (sscanf(line, "00000000000000000000000000000000 00 " + "%*s %*s %*s %*s %*s %*s %*s %15s", ifname) && + strcmp(ifname, "lo")) { + found_default = true; + break; + } + } + + return found_default; +} + +/* Find the source prefixes of all IPv6 addresses */ +static void find_addr6_dprefix(struct odhcpd_ipaddr *n, ssize_t len) +{ + struct odhcpd_ipaddr p = { .addr.in6 = IN6ADDR_ANY_INIT, .prefix = 0, + .dprefix = 0, .preferred = 0, .valid = 0}; + char line[512]; + uint32_t rflags; + + if (!fp_ipv6_route || len <= 0) + return; + rewind(fp_ipv6_route); + + while (fgets(line, sizeof(line), fp_ipv6_route)) { + if (sscanf(line, "%8" SCNx32 "%8" SCNx32 "%*8" SCNx32 "%*8" SCNx32 " %hhx %*s " + "%*s 00000000000000000000000000000000 %*s %*s %*s %" SCNx32 " lo", + &p.addr.in6.s6_addr32[0], &p.addr.in6.s6_addr32[1], &p.prefix, &rflags) && + p.prefix > 0 && (rflags & RTF_NONEXTHOP) && (rflags & RTF_REJECT)) { + // Find source prefixes by scanning through unreachable-routes + p.addr.in6.s6_addr32[0] = htonl(p.addr.in6.s6_addr32[0]); + p.addr.in6.s6_addr32[1] = htonl(p.addr.in6.s6_addr32[1]); + + for (ssize_t i = 0; i < len; ++i) { + if (n[i].prefix <= 64 && n[i].prefix >= p.prefix && + !odhcpd_bmemcmp(&p.addr.in6, &n[i].addr.in6, p.prefix)) { + n[i].dprefix = p.prefix; + break; + } + } + } + } +} int netlink_add_netevent_handler(struct netevent_handler *handler) { @@ -212,6 +278,20 @@ static void refresh_iface_addr6(int ifindex) } if (change) { + /* + * Remove invalid prefixes that were added back to the interface. + */ + for (ssize_t i = 0; i < len; ++i) { + size_t j = 0; + while (j < iface->invalid_addr6_len) { + if (addr[i].prefix <= iface->invalid_addr6[j].prefix && + odhcpd_bmemcmp(&addr[i].addr.in6, &iface->invalid_addr6[j].addr.in6, iface->invalid_addr6[j].prefix) == 0) + odhcpd_del_intf_invalid_addr6(iface, j); + else + ++j; + } + } + /* * Keep track on removed prefixes, so we could advertise them as invalid * for at least a couple of times. @@ -756,6 +836,9 @@ ssize_t netlink_get_interface_addrs(int ifindex, bool v6, struct odhcpd_ipaddr * addr[i].valid += now; } + if (v6) + find_addr6_dprefix(addr, ctxt.ret); + free: nlmsg_free(msg); out: diff --git a/src/odhcpd.c b/src/odhcpd.c index 554e5f1..4be70c4 100644 --- a/src/odhcpd.c +++ b/src/odhcpd.c @@ -77,6 +77,7 @@ int main(int argc, char **argv) { openlog("odhcpd", LOG_PERROR | LOG_PID, LOG_DAEMON); int opt; + bool ipv6 = ipv6_enabled(); while ((opt = getopt(argc, argv, "hl:")) != -1) { switch (opt) { @@ -109,10 +110,10 @@ int main(int argc, char **argv) signal(SIGINT, sighandler); signal(SIGTERM, sighandler); - if (netlink_init()) + if (netlink_init(ipv6)) return 4; - if (ipv6_enabled()) { + if (ipv6) { if (router_init()) return 4; diff --git a/src/odhcpd.h b/src/odhcpd.h index 02b6ac0..552ffa7 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -399,6 +399,26 @@ inline static struct dhcp_assignment *alloc_assignment(size_t extra_len) return a; } +inline static void odhcpd_del_intf_invalid_addr6(struct interface *iface, size_t idx) +{ + if (idx + 1 < iface->invalid_addr6_len) + memmove(&iface->invalid_addr6[idx], &iface->invalid_addr6[idx + 1], + sizeof(*iface->invalid_addr6) * (iface->invalid_addr6_len - idx - 1)); + + iface->invalid_addr6_len--; + + if (iface->invalid_addr6_len) { + struct odhcpd_ipaddr *new_invalid_addr6 = realloc(iface->invalid_addr6, + sizeof(*iface->invalid_addr6) * iface->invalid_addr6_len); + + if (new_invalid_addr6) + iface->invalid_addr6 = new_invalid_addr6; + } else { + free(iface->invalid_addr6); + iface->invalid_addr6 = NULL; + } +} + // Exported main functions int odhcpd_register(struct odhcpd_event *event); int odhcpd_deregister(struct odhcpd_event *event); @@ -452,6 +472,7 @@ void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c, ti dhcpv6_binding_cb_handler_t func, void *arg); void dhcpv6_ia_write_statefile(void); +bool netlink_default_ipv6_route_exists(); int netlink_add_netevent_handler(struct netevent_handler *hdlr); ssize_t netlink_get_interface_addrs(const int ifindex, bool v6, struct odhcpd_ipaddr **addrs); @@ -468,7 +489,7 @@ void netlink_dump_neigh_table(const bool proxy); void netlink_dump_addr_table(const bool v6); // Exported module initializers -int netlink_init(void); +int netlink_init(bool ipv6); int router_init(void); int dhcpv6_init(void); int ndp_init(void); diff --git a/src/router.c b/src/router.c index 4c47771..594a334 100644 --- a/src/router.c +++ b/src/router.c @@ -22,7 +22,6 @@ #include #include #include -#include #include @@ -40,8 +39,6 @@ static void router_netevent_cb(unsigned long event, struct netevent_handler_info static struct netevent_handler router_netevent_handler = { .cb = router_netevent_cb, }; -static FILE *fp_route = NULL; - #define TIME_LEFT(t1, now) ((t1) != UINT32_MAX ? (t1) - (now) : UINT32_MAX) @@ -49,23 +46,11 @@ int router_init(void) { int ret = 0; - if (!(fp_route = fopen("/proc/net/ipv6_route", "r"))) { - syslog(LOG_ERR, "fopen(/proc/net/ipv6_route): %m"); - ret = -1; - goto out; - } - if (netlink_add_netevent_handler(&router_netevent_handler) < 0) { syslog(LOG_ERR, "Failed to add netevent handler"); ret = -1; } -out: - if (ret < 0 && fp_route) { - fclose(fp_route); - fp_route = NULL; - } - return ret; } @@ -76,12 +61,6 @@ int router_setup_interface(struct interface *iface, bool enable) enable = enable && (iface->ra != MODE_DISABLED); - if (!fp_route) { - ret = -1; - goto out; - } - - if (!enable && iface->router_event.uloop.fd >= 0) { if (!iface->master) { uloop_timeout_cancel(&iface->timer_rs); @@ -297,43 +276,6 @@ static bool router_icmpv6_valid(struct sockaddr_in6 *source, uint8_t *data, size } -/* Detect whether a default route exists, also find the source prefixes */ -static bool parse_routes(struct odhcpd_ipaddr *n, ssize_t len) -{ - struct odhcpd_ipaddr p = { .addr.in6 = IN6ADDR_ANY_INIT, .prefix = 0, - .dprefix = 0, .preferred = 0, .valid = 0}; - bool found_default = false; - char line[512], ifname[16]; - - rewind(fp_route); - - while (fgets(line, sizeof(line), fp_route)) { - uint32_t rflags; - if (sscanf(line, "00000000000000000000000000000000 00 " - "%*s %*s %*s %*s %*s %*s %*s %15s", ifname) && - strcmp(ifname, "lo")) { - found_default = true; - } else if (sscanf(line, "%8" SCNx32 "%8" SCNx32 "%*8" SCNx32 "%*8" SCNx32 " %hhx %*s " - "%*s 00000000000000000000000000000000 %*s %*s %*s %" SCNx32 " lo", - &p.addr.in6.s6_addr32[0], &p.addr.in6.s6_addr32[1], &p.prefix, &rflags) && - p.prefix > 0 && (rflags & RTF_NONEXTHOP) && (rflags & RTF_REJECT)) { - // Find source prefixes by scanning through unreachable-routes - p.addr.in6.s6_addr32[0] = htonl(p.addr.in6.s6_addr32[0]); - p.addr.in6.s6_addr32[1] = htonl(p.addr.in6.s6_addr32[1]); - - for (ssize_t i = 0; i < len; ++i) { - if (n[i].prefix <= 64 && n[i].prefix >= p.prefix && - !odhcpd_bmemcmp(&p.addr.in6, &n[i].addr.in6, p.prefix)) { - n[i].dprefix = p.prefix; - break; - } - } - } - } - - return found_default; -} - static int calc_adv_interval(struct interface *iface, uint32_t minvalid, uint32_t *maxival) { @@ -495,7 +437,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr iov[IOV_RA_ADV].iov_base = (char *)&adv; iov[IOV_RA_ADV].iov_len = sizeof(adv); - valid_addr_cnt = (iface->timer_rs.cb /* if not shutdown */ ? iface->addr6_len : 0); + valid_addr_cnt = iface->addr6_len; invalid_addr_cnt = iface->invalid_addr6_len; // check ra_default @@ -513,7 +455,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr memcpy(addrs, iface->addr6, sizeof(*addrs) * valid_addr_cnt); /* Check default route */ - if (!default_route && parse_routes(addrs, valid_addr_cnt)) + if (!default_route && netlink_default_ipv6_route_exists()) default_route = true; } @@ -524,25 +466,18 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr /* Remove invalid prefixes that were advertised 3 times */ while (i < iface->invalid_addr6_len) { - if (++iface->invalid_addr6[i].invalid_advertisements >= 3) { - if (i + 1 < iface->invalid_addr6_len) - memmove(&iface->invalid_addr6[i], &iface->invalid_addr6[i + 1], sizeof(*addrs) * (iface->invalid_addr6_len - i - 1)); - - iface->invalid_addr6_len--; - - if (iface->invalid_addr6_len) { - struct odhcpd_ipaddr *new_invalid_addr6 = realloc(iface->invalid_addr6, sizeof(*addrs) * iface->invalid_addr6_len); - - if (new_invalid_addr6) - iface->invalid_addr6 = new_invalid_addr6; - } else { - free(iface->invalid_addr6); - iface->invalid_addr6 = NULL; - } - } else + if (++iface->invalid_addr6[i].invalid_advertisements >= 3) + odhcpd_del_intf_invalid_addr6(iface, i); + else ++i; } } + + /* Advertise all prefixes as invalid on shutdown */ + if (iface->timer_rs.cb == NULL) { + invalid_addr_cnt += valid_addr_cnt; + valid_addr_cnt = 0; + } } /* Construct Prefix Information options */ @@ -587,7 +522,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr memset(p, 0, sizeof(*p)); } - if (addr->preferred > (uint32_t)now) { + if (i < valid_addr_cnt && addr->preferred > (uint32_t)now) { preferred = TIME_LEFT(addr->preferred, now); if (iface->ra_useleasetime && @@ -595,7 +530,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr preferred = iface->preferred_lifetime; } - if (addr->valid > (uint32_t)now) { + if (i < valid_addr_cnt && addr->valid > (uint32_t)now) { valid = TIME_LEFT(addr->valid, now); if (iface->ra_useleasetime && valid > iface->dhcp_leasetime) @@ -775,14 +710,20 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr * in Section 2.3 of [RFC4191]. This advertisement is * independent of having or not having IPv6 connectivity on the * WAN interface. + * + * RFC4191 ยง 4 also says that + * When ceasing to be an advertising + * interface and sending Router Advertisements with a Router Lifetime of + * zero, the Router Advertisement SHOULD also set the Route Lifetime to + * zero in all Route Information Options. */ - for (ssize_t i = 0; i < valid_addr_cnt; ++i) { + for (ssize_t i = 0; i < valid_addr_cnt + invalid_addr_cnt; ++i) { struct odhcpd_ipaddr *addr = &addrs[i]; struct nd_opt_route_info *tmp; uint32_t valid; - if (addr->dprefix > 64 || addr->dprefix == 0 || addr->valid <= (uint32_t)now) { + if (addr->dprefix > 64 || addr->dprefix == 0 || (i < valid_addr_cnt && addr->valid <= (uint32_t)now)) { syslog(LOG_INFO, "Address %s (dprefix %d, valid %u) not suitable as RA route on %s", inet_ntop(AF_INET6, &addr->addr.in6, buf, sizeof(buf)), addr->dprefix, addr->valid, iface->name); @@ -804,6 +745,17 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr addr->addr.in6.s6_addr32[1] = 0; } + tmp = NULL; + for (size_t i = 0; i < routes_cnt; ++i) { + if (addr->dprefix == routes[i].prefix && + !odhcpd_bmemcmp(&routes[i].addr, &addr->addr.in6, addr->dprefix)) { + tmp = &routes[i]; + break; + } + } + if (tmp) + continue; /* RIO already inserted */ + if (!iface->ra_not_onlink) { bool fully_used_in_pio = false; for (size_t i = 0; i < pfxs_cnt; ++i) { @@ -837,7 +789,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr else if (iface->route_preference > 0) routes[routes_cnt].flags |= ND_RA_PREF_HIGH; - valid = TIME_LEFT(addr->valid, now); + valid = (i < valid_addr_cnt && addr->valid > (uint32_t)now ? TIME_LEFT(addr->valid, now) : 0); routes[routes_cnt].lifetime = htonl(valid < lifetime ? valid : lifetime); routes[routes_cnt].addr[0] = addr->addr.in6.s6_addr32[0]; routes[routes_cnt].addr[1] = addr->addr.in6.s6_addr32[1];