diff options
-rw-r--r-- | lib/route/route_obj.c | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/lib/route/route_obj.c b/lib/route/route_obj.c index cc29746..e092d80 100644 --- a/lib/route/route_obj.c +++ b/lib/route/route_obj.c @@ -441,6 +441,101 @@ nh_mismatch: #undef ROUTE_DIFF } +static int route_update(struct nl_object *old_obj, struct nl_object *new_obj) +{ + struct rtnl_route *new_route = (struct rtnl_route *) new_obj; + struct rtnl_route *old_route = (struct rtnl_route *) old_obj; + struct rtnl_nexthop *new_nh; + char buf[INET6_ADDRSTRLEN+5]; + int action = new_obj->ce_msgtype; + + /* + * ipv6 ECMP route notifications from the kernel come as + * separate notifications, one for every nexthop. This update + * function collapses such route msgs into a single + * route with multiple nexthops. The resulting object looks + * similar to a ipv4 ECMP route + */ + if (new_route->rt_family != AF_INET6 || + new_route->rt_table == RT_TABLE_LOCAL) + return -NLE_OPNOTSUPP; + + /* + * For routes that are already multipath, + * or dont have a nexthop dont do anything + */ + if (rtnl_route_get_nnexthops(new_route) != 1) + return -NLE_OPNOTSUPP; + + /* + * Get the only nexthop entry from the new route. For + * IPv6 we always get a route with a 0th NH + * filled or nothing at all + */ + new_nh = rtnl_route_nexthop_n(new_route, 0); + if (!new_nh || !rtnl_route_nh_get_gateway(new_nh)) + return -NLE_OPNOTSUPP; + + switch(action) { + case RTM_NEWROUTE : { + struct rtnl_nexthop *cloned_nh; + + /* + * Add the nexthop to old route + */ + cloned_nh = rtnl_route_nh_clone(new_nh); + if (!cloned_nh) + return -NLE_NOMEM; + rtnl_route_add_nexthop(old_route, cloned_nh); + + NL_DBG(2, "Route obj %p updated. Added " + "nexthop %p via %s\n", old_route, cloned_nh, + nl_addr2str(cloned_nh->rtnh_gateway, buf, + sizeof(buf))); + } + break; + case RTM_DELROUTE : { + struct rtnl_nexthop *old_nh; + + /* + * Only take care of nexthop deletes and not + * route deletes. So, if there is only one nexthop + * quite likely we did not update it. So dont do + * anything and return + */ + if (rtnl_route_get_nnexthops(old_route) <= 1) + return -NLE_OPNOTSUPP; + + /* + * Find the next hop in old route and delete it + */ + nl_list_for_each_entry(old_nh, &old_route->rt_nexthops, + rtnh_list) { + if (!rtnl_route_nh_compare(old_nh, new_nh, ~0, 0)) { + + rtnl_route_remove_nexthop(old_route, old_nh); + + NL_DBG(2, "Route obj %p updated. Removed " + "nexthop %p via %s\n", old_route, + old_nh, + nl_addr2str(old_nh->rtnh_gateway, buf, + sizeof(buf))); + + rtnl_route_nh_free(old_nh); + break; + } + } + } + break; + default: + NL_DBG(2, "Unknown action associated " + "to object %p during route update\n", new_obj); + return -NLE_OPNOTSUPP; + } + + return NLE_SUCCESS; +} + static const struct trans_tbl route_attrs[] = { __ADD(ROUTE_ATTR_FAMILY, family) __ADD(ROUTE_ATTR_TOS, tos) @@ -1201,6 +1296,7 @@ struct nl_object_ops route_obj_ops = { }, .oo_compare = route_compare, .oo_keygen = route_keygen, + .oo_update = route_update, .oo_attrs2str = route_attrs2str, .oo_id_attrs = (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS | ROUTE_ATTR_TABLE | ROUTE_ATTR_DST), |