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 d5ef7f8..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,32 @@ 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) { + 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); @@ -822,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];