diff --git a/src/netlink/nl_l3.cc b/src/netlink/nl_l3.cc index a14e8015..7341fb80 100644 --- a/src/netlink/nl_l3.cc +++ b/src/netlink/nl_l3.cc @@ -716,6 +716,7 @@ int nl_l3::add_l3_neigh(struct rtnl_neigh *n) { return -EINVAL; addr = rtnl_neigh_get_dst(n); + if (family == AF_INET6) { auto p = nl_addr_alloc(16); nl_addr_parse("fe80::/10", AF_INET6, &p); @@ -729,6 +730,18 @@ int nl_l3::add_l3_neigh(struct rtnl_neigh *n) { } if (add_host_entry) { + // unless we have route, we should not route l3 neighbors + struct nh_stub nh { + addr, rtnl_neigh_get_ifindex(n) + }; + + if (is_l3_neigh_routable(n)) { + routable_l3_neighs.emplace(nh); + } else { + unroutable_l3_neighs.emplace(nh); + return -ENETUNREACH; + } + rv = add_l3_neigh_egress(n, &l3_interface_id, &vrf_id); LOG(INFO) << __FUNCTION__ << ": adding l3 neigh egress for neigh " << OBJ_CAST(n); @@ -827,6 +840,14 @@ int nl_l3::update_l3_neigh(struct rtnl_neigh *n_old, struct rtnl_neigh *n_new) { VLOG(1) << __FUNCTION__ << ": neighbour ll changed: new neighbor " << n_ll_new << " ifindex=" << ifindex; + struct nh_stub nh { + rtnl_neigh_get_dst(n_old), ifindex + }; + if (unroutable_l3_neighs.count(nh) > 0) { + VLOG(1) << __FUNCTION__ << ": ignoring update on unroutable neighbor"; + return -EINVAL; + } + auto link = nl->get_link_by_ifindex(ifindex); if (link.get() == nullptr) { @@ -923,6 +944,17 @@ int nl_l3::del_l3_neigh(struct rtnl_neigh *n) { if (!link) return -EINVAL; + struct nh_stub nh { + addr, rtnl_neigh_get_ifindex(n) + }; + if (unroutable_l3_neighs.erase(nh) > 0) { + VLOG(2) << __FUNCTION__ << ": l3 neigh was disabled, nothing to do for " + << OBJ_CAST(n); + return 0; + } + + routable_l3_neighs.erase(nh); + std::deque link_addresses; get_l3_addrs(link.get(), &link_addresses); for (auto i : link_addresses) { @@ -1808,6 +1840,25 @@ int nl_l3::add_l3_unicast_route(rtnl_route *r, bool update_route) { << " rv=" << rv; } + VLOG(2) << __FUNCTION__ << ": enabling l3 neighs reachable by route " + << OBJ_CAST(r); + auto l3_neighs = + get_l3_neighs_of_prefix(unroutable_l3_neighs, rtnl_route_get_dst(r)); + for (auto n : l3_neighs) { + auto neigh = nl->get_neighbour(n.ifindex, n.nh); + if (!neigh) { + // should we remove it from unroutable? + VLOG(2) << __FUNCTION__ << ": unknown l3 neigh ifindex=" << n.ifindex + << ", addr=" << n.nh; + continue; + } + + VLOG(2) << __FUNCTION__ << ": enabling l3 neigh " << OBJ_CAST(neigh); + unroutable_l3_neighs.erase(n); + add_l3_neigh(neigh); + rtnl_neigh_put(neigh); + } + // cleanup for (auto n : neighs) rtnl_neigh_put(n); @@ -1844,6 +1895,27 @@ int nl_l3::del_l3_unicast_route(rtnl_route *r, bool keep_route) { } } + VLOG(2) << __FUNCTION__ << ": disabling l3 neighs reachable by route " + << OBJ_CAST(r); + auto l3_neighs = + get_l3_neighs_of_prefix(routable_l3_neighs, rtnl_route_get_dst(r)); + for (auto n : l3_neighs) { + auto neigh = nl->get_neighbour(n.ifindex, n.nh); + if (!neigh) { + // neigh is already purged from nl cache, so neigh will be + // removed when handling the DEL_NEIGH notification + VLOG(2) << __FUNCTION__ + << ": skipping non-existing l3 neigh ifindex=" << n.ifindex + << ", addr=" << n.nh; + continue; + } + + VLOG(2) << __FUNCTION__ << ": disabling l3 neigh " << OBJ_CAST(neigh); + del_l3_neigh(neigh); + rtnl_neigh_put(neigh); + unroutable_l3_neighs.emplace(n); + } + std::deque neighs; std::deque unresolved_nh; get_neighbours_of_route(r, &neighs, &unresolved_nh); @@ -1934,4 +2006,41 @@ int nl_l3::del_l3_unicast_route(rtnl_route *r, bool keep_route) { return rv; } +bool nl_l3::is_l3_neigh_routable(struct rtnl_neigh *n) { + bool routable = false; + nl_route_query rq; + + auto route = rq.query_route(rtnl_neigh_get_dst(n)); + if (route) { + VLOG(2) << __FUNCTION__ << ": got route " << OBJ_CAST(route) + << " for neigh " << OBJ_CAST(n); + if (rtnl_route_get_nnexthops(route) > 0) { + std::deque nhs; + get_nexthops_of_route(route, &nhs); + if (nhs.size() > 0) { + VLOG(2) << __FUNCTION__ << ": neigh is routable in by us"; + routable = true; + } + } + nl_object_put(OBJ_CAST(route)); + } else { + VLOG(2) << __FUNCTION__ << ": no route for neigh " << OBJ_CAST(n); + } + + return routable; +}; + +std::deque nl_l3::get_l3_neighs_of_prefix(std::set &list, + nl_addr *prefix) { + std::deque ret; + + auto l3_neighs = + std::equal_range(list.begin(), list.end(), prefix, l3_prefix_comp); + std::deque nhs; + for (auto n = l3_neighs.first; n != l3_neighs.second; ++n) { + ret.push_back(*n); + } + return ret; +} + } // namespace basebox diff --git a/src/netlink/nl_l3.h b/src/netlink/nl_l3.h index adc70db5..97b2530c 100644 --- a/src/netlink/nl_l3.h +++ b/src/netlink/nl_l3.h @@ -32,6 +32,17 @@ class nl_vlan; class nl_bridge; class switch_interface; +class l3_prefix_comp final { +public: + bool operator()(const nh_stub &nh, const struct nl_addr *addr) const { + return nl_addr_cmp_prefix(nh.nh, addr) < 0; + } + + bool operator()(const struct nl_addr *addr, const nh_stub &nh) const { + return nl_addr_cmp_prefix(addr, nh.nh) < 0; + } +}; + class nl_l3 : public nh_reachable, public nh_unreachable { public: nl_l3(std::shared_ptr vlan, cnetlink *nl); @@ -133,12 +144,22 @@ class nl_l3 : public nh_reachable, public nh_unreachable { return !nl_addr_cmp_prefix(mc_addr.get(), addr); } + bool is_l3_neigh_routable(struct rtnl_neigh *n); + + std::deque get_l3_neighs_of_prefix(std::set &list, + nl_addr *prefix); + switch_interface *sw; std::shared_ptr vlan; cnetlink *nl; std::list> net_callbacks; std::list> nh_callbacks; std::list> nh_unreach_callbacks; + + std::set routable_l3_neighs; + std::set unroutable_l3_neighs; + struct l3_prefix_comp l3_prefix_comp; + const uint8_t MAIN_ROUTING_TABLE = 254; }; diff --git a/src/netlink/nl_l3_interfaces.h b/src/netlink/nl_l3_interfaces.h index dded89f8..aa1032a5 100644 --- a/src/netlink/nl_l3_interfaces.h +++ b/src/netlink/nl_l3_interfaces.h @@ -51,6 +51,13 @@ struct nh_stub { nl_addr_put(nh); } + bool operator<(const nh_stub& other) const { + if (nl_addr_cmp(nh, other.nh) < 0) + return true; + + return ifindex < other.ifindex; + } + nl_addr *nh; int ifindex; };