Changeset View
Changeset View
Standalone View
Standalone View
head/sys/net/route/route_ctl.c
Show First 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | |||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/rmlock.h> | #include <sys/rmlock.h> | ||||
#include <net/if.h> | #include <net/if.h> | ||||
#include <net/if_var.h> | #include <net/if_var.h> | ||||
#include <net/if_dl.h> | #include <net/if_dl.h> | ||||
#include <net/vnet.h> | #include <net/vnet.h> | ||||
#include <net/route.h> | #include <net/route.h> | ||||
#include <net/route/route_ctl.h> | |||||
#include <net/route/route_var.h> | #include <net/route/route_var.h> | ||||
#include <net/route/nhop_utils.h> | #include <net/route/nhop_utils.h> | ||||
#include <net/route/nhop.h> | #include <net/route/nhop.h> | ||||
#include <net/route/nhop_var.h> | #include <net/route/nhop_var.h> | ||||
#include <net/route/shared.h> | #include <net/route/shared.h> | ||||
#include <netinet/in.h> | #include <netinet/in.h> | ||||
#ifdef RADIX_MPATH | #ifdef RADIX_MPATH | ||||
#include <net/radix_mpath.h> | #include <net/radix_mpath.h> | ||||
#endif | #endif | ||||
#include <vm/uma.h> | #include <vm/uma.h> | ||||
/* | /* | ||||
* This file contains control plane routing tables functions. | * This file contains control plane routing tables functions. | ||||
* | * | ||||
* All functions assumes they are called in net epoch. | * All functions assumes they are called in net epoch. | ||||
*/ | */ | ||||
static void rib_notify(struct rib_head *rnh, enum rib_subscription_type type, | |||||
struct rib_cmd_info *rc); | |||||
static void rt_notifydelete(struct rtentry *rt, struct rt_addrinfo *info); | static void rt_notifydelete(struct rtentry *rt, struct rt_addrinfo *info); | ||||
static struct rib_head * | |||||
get_rnh(uint32_t fibnum, const struct rt_addrinfo *info) | |||||
{ | |||||
struct rib_head *rnh; | |||||
struct sockaddr *dst; | |||||
KASSERT((fibnum < rt_numfibs), ("rib_add_route: bad fibnum")); | |||||
dst = info->rti_info[RTAX_DST]; | |||||
rnh = rt_tables_get_rnh(fibnum, dst->sa_family); | |||||
return (rnh); | |||||
} | |||||
/* | |||||
* Adds route defined by @info into the kernel table specified by @fibnum and | |||||
* sa_family in @info->rti_info[RTAX_DST]. | |||||
* | |||||
* Returns 0 on success and fills in operation metadata into @rc. | |||||
*/ | |||||
int | int | ||||
rib_add_route(uint32_t fibnum, struct rt_addrinfo *info, | |||||
struct rib_cmd_info *rc) | |||||
{ | |||||
struct rib_head *rnh; | |||||
NET_EPOCH_ASSERT(); | |||||
rnh = get_rnh(fibnum, info); | |||||
if (rnh == NULL) | |||||
return (EAFNOSUPPORT); | |||||
/* | |||||
* Check consistency between RTF_HOST flag and netmask | |||||
* existence. | |||||
*/ | |||||
if (info->rti_flags & RTF_HOST) | |||||
info->rti_info[RTAX_NETMASK] = NULL; | |||||
else if (info->rti_info[RTAX_NETMASK] == NULL) | |||||
return (EINVAL); | |||||
bzero(rc, sizeof(struct rib_cmd_info)); | |||||
rc->rc_cmd = RTM_ADD; | |||||
return (add_route(rnh, info, rc)); | |||||
} | |||||
int | |||||
add_route(struct rib_head *rnh, struct rt_addrinfo *info, | add_route(struct rib_head *rnh, struct rt_addrinfo *info, | ||||
struct rtentry **ret_nrt) | struct rib_cmd_info *rc) | ||||
{ | { | ||||
struct sockaddr *dst, *ndst, *gateway, *netmask; | struct sockaddr *dst, *ndst, *gateway, *netmask; | ||||
struct rtentry *rt, *rt_old; | struct rtentry *rt, *rt_old; | ||||
struct nhop_object *nh; | struct nhop_object *nh; | ||||
struct radix_node *rn; | struct radix_node *rn; | ||||
struct ifaddr *ifa; | struct ifaddr *ifa; | ||||
int error, flags; | int error, flags; | ||||
struct epoch_tracker et; | struct epoch_tracker et; | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | add_route(struct rib_head *rnh, struct rt_addrinfo *info, | ||||
* We use the ifa reference returned by rt_getifa_fib(). | * We use the ifa reference returned by rt_getifa_fib(). | ||||
* This moved from below so that rnh->rnh_addaddr() can | * This moved from below so that rnh->rnh_addaddr() can | ||||
* examine the ifa and ifa->ifa_ifp if it so desires. | * examine the ifa and ifa->ifa_ifp if it so desires. | ||||
*/ | */ | ||||
ifa = info->rti_ifa; | ifa = info->rti_ifa; | ||||
rt->rt_weight = 1; | rt->rt_weight = 1; | ||||
rt_setmetrics(info, rt); | rt_setmetrics(info, rt); | ||||
rt_old = NULL; | |||||
RIB_WLOCK(rnh); | RIB_WLOCK(rnh); | ||||
RT_LOCK(rt); | RT_LOCK(rt); | ||||
#ifdef RADIX_MPATH | #ifdef RADIX_MPATH | ||||
/* do not permit exactly the same dst/mask/gw pair */ | /* do not permit exactly the same dst/mask/gw pair */ | ||||
if (rt_mpath_capable(rnh) && | if (rt_mpath_capable(rnh) && | ||||
rt_mpath_conflict(rnh, rt, netmask)) { | rt_mpath_conflict(rnh, rt, netmask)) { | ||||
RIB_WUNLOCK(rnh); | RIB_WUNLOCK(rnh); | ||||
nhop_free(nh); | nhop_free(nh); | ||||
uma_zfree(V_rtzone, rt); | uma_zfree(V_rtzone, rt); | ||||
return (EEXIST); | return (EEXIST); | ||||
} | } | ||||
#endif | #endif | ||||
rn = rnh->rnh_addaddr(ndst, netmask, &rnh->head, rt->rt_nodes); | rn = rnh->rnh_addaddr(ndst, netmask, &rnh->head, rt->rt_nodes); | ||||
if (rn != NULL && rt->rt_expire > 0) | if (rn != NULL) { | ||||
/* Most common usecase */ | |||||
if (rt->rt_expire > 0) | |||||
tmproutes_update(rnh, rt); | tmproutes_update(rnh, rt); | ||||
rt_old = NULL; | /* Finalize notification */ | ||||
if (rn == NULL && (info->rti_flags & RTF_PINNED) != 0) { | rnh->rnh_gen++; | ||||
rc->rc_rt = RNTORT(rn); | |||||
rc->rc_nh_new = nh; | |||||
rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); | |||||
} else if ((info->rti_flags & RTF_PINNED) != 0) { | |||||
/* | /* | ||||
* Force removal and re-try addition | * Force removal and re-try addition | ||||
* TODO: better multipath&pinned support | * TODO: better multipath&pinned support | ||||
*/ | */ | ||||
struct sockaddr *info_dst = info->rti_info[RTAX_DST]; | struct sockaddr *info_dst = info->rti_info[RTAX_DST]; | ||||
info->rti_info[RTAX_DST] = ndst; | info->rti_info[RTAX_DST] = ndst; | ||||
/* Do not delete existing PINNED(interface) routes */ | /* Do not delete existing PINNED(interface) routes */ | ||||
info->rti_flags &= ~RTF_PINNED; | info->rti_flags &= ~RTF_PINNED; | ||||
rt_old = rt_unlinkrte(rnh, info, &error); | rt_old = rt_unlinkrte(rnh, info, &error); | ||||
info->rti_flags |= RTF_PINNED; | info->rti_flags |= RTF_PINNED; | ||||
info->rti_info[RTAX_DST] = info_dst; | info->rti_info[RTAX_DST] = info_dst; | ||||
if (rt_old != NULL) | if (rt_old != NULL) { | ||||
rn = rnh->rnh_addaddr(ndst, netmask, &rnh->head, | rn = rnh->rnh_addaddr(ndst, netmask, &rnh->head, | ||||
rt->rt_nodes); | rt->rt_nodes); | ||||
/* Finalize notification */ | |||||
rnh->rnh_gen++; | |||||
if (rn != NULL) { | |||||
rc->rc_cmd = RTM_CHANGE; | |||||
rc->rc_rt = RNTORT(rn); | |||||
rc->rc_nh_old = rt_old->rt_nhop; | |||||
rc->rc_nh_new = nh; | |||||
} else { | |||||
rc->rc_cmd = RTM_DELETE; | |||||
rc->rc_rt = RNTORT(rn); | |||||
rc->rc_nh_old = rt_old->rt_nhop; | |||||
rc->rc_nh_new = nh; | |||||
} | } | ||||
rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); | |||||
} | |||||
} | |||||
RIB_WUNLOCK(rnh); | RIB_WUNLOCK(rnh); | ||||
if (rt_old != NULL) { | if (rt_old != NULL) { | ||||
rt_notifydelete(rt_old, info); | rt_notifydelete(rt_old, info); | ||||
rtfree(rt_old); | rtfree(rt_old); | ||||
} | } | ||||
/* | /* | ||||
* If it still failed to go into the tree, | * If it still failed to go into the tree, | ||||
* then un-make it (this should be a function) | * then un-make it (this should be a function) | ||||
*/ | */ | ||||
if (rn == NULL) { | if (rn == NULL) { | ||||
nhop_free(nh); | nhop_free(nh); | ||||
uma_zfree(V_rtzone, rt); | uma_zfree(V_rtzone, rt); | ||||
return (EEXIST); | return (EEXIST); | ||||
} | } | ||||
/* | /* | ||||
* If this protocol has something to add to this then | * If this protocol has something to add to this then | ||||
* allow it to do that as well. | * allow it to do that as well. | ||||
*/ | */ | ||||
if (ifa->ifa_rtrequest) | if (ifa->ifa_rtrequest) | ||||
ifa->ifa_rtrequest(RTM_ADD, rt, rt->rt_nhop, info); | ifa->ifa_rtrequest(RTM_ADD, rt, rt->rt_nhop, info); | ||||
/* | |||||
* actually return a resultant rtentry | |||||
*/ | |||||
if (ret_nrt) | |||||
*ret_nrt = rt; | |||||
rnh->rnh_gen++; /* Routing table updated */ | |||||
RT_UNLOCK(rt); | RT_UNLOCK(rt); | ||||
return (0); | return (0); | ||||
} | } | ||||
/* | /* | ||||
* Removes route defined by @info from the kernel table specified by @fibnum and | |||||
* sa_family in @info->rti_info[RTAX_DST]. | |||||
* | |||||
* Returns 0 on success and fills in operation metadata into @rc. | |||||
*/ | |||||
int | |||||
rib_del_route(uint32_t fibnum, struct rt_addrinfo *info, struct rib_cmd_info *rc) | |||||
{ | |||||
struct rib_head *rnh; | |||||
NET_EPOCH_ASSERT(); | |||||
rnh = get_rnh(fibnum, info); | |||||
if (rnh == NULL) | |||||
return (EAFNOSUPPORT); | |||||
bzero(rc, sizeof(struct rib_cmd_info)); | |||||
rc->rc_cmd = RTM_DELETE; | |||||
return (del_route(rnh, info, rc)); | |||||
} | |||||
/* | |||||
* Conditionally unlinks rtentry matching data inside @info from @rnh. | * Conditionally unlinks rtentry matching data inside @info from @rnh. | ||||
* Returns unlinked, locked and referenced @rtentry on success, | * Returns unlinked, locked and referenced @rtentry on success, | ||||
* Returns NULL and sets @perror to: | * Returns NULL and sets @perror to: | ||||
* ESRCH - if prefix was not found, | * ESRCH - if prefix was not found, | ||||
* EADDRINUSE - if trying to delete PINNED route without appropriate flag. | * EADDRINUSE - if trying to delete PINNED route without appropriate flag. | ||||
* ENOENT - if supplied filter function returned 0 (not matched). | * ENOENT - if supplied filter function returned 0 (not matched). | ||||
*/ | */ | ||||
struct rtentry * | struct rtentry * | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | #endif | ||||
*perror = 0; | *perror = 0; | ||||
return (rt); | return (rt); | ||||
} | } | ||||
int | int | ||||
del_route(struct rib_head *rnh, struct rt_addrinfo *info, | del_route(struct rib_head *rnh, struct rt_addrinfo *info, | ||||
struct rtentry **ret_nrt) | struct rib_cmd_info *rc) | ||||
{ | { | ||||
struct sockaddr *dst, *netmask; | struct sockaddr *dst, *netmask; | ||||
struct sockaddr_storage mdst; | struct sockaddr_storage mdst; | ||||
struct rtentry *rt; | struct rtentry *rt; | ||||
int error; | int error; | ||||
dst = info->rti_info[RTAX_DST]; | dst = info->rti_info[RTAX_DST]; | ||||
netmask = info->rti_info[RTAX_NETMASK]; | netmask = info->rti_info[RTAX_NETMASK]; | ||||
if (netmask) { | if (netmask) { | ||||
if (dst->sa_len > sizeof(mdst)) | if (dst->sa_len > sizeof(mdst)) | ||||
return (EINVAL); | return (EINVAL); | ||||
rt_maskedcopy(dst, (struct sockaddr *)&mdst, netmask); | rt_maskedcopy(dst, (struct sockaddr *)&mdst, netmask); | ||||
dst = (struct sockaddr *)&mdst; | dst = (struct sockaddr *)&mdst; | ||||
} | } | ||||
RIB_WLOCK(rnh); | RIB_WLOCK(rnh); | ||||
rt = rt_unlinkrte(rnh, info, &error); | rt = rt_unlinkrte(rnh, info, &error); | ||||
if (rt != NULL) { | |||||
/* Finalize notification */ | |||||
rnh->rnh_gen++; | |||||
rc->rc_rt = rt; | |||||
rc->rc_nh_old = rt->rt_nhop; | |||||
rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); | |||||
} | |||||
RIB_WUNLOCK(rnh); | RIB_WUNLOCK(rnh); | ||||
if (error != 0) | if (error != 0) | ||||
return (error); | return (error); | ||||
rt_notifydelete(rt, info); | rt_notifydelete(rt, info); | ||||
/* | /* | ||||
* If the caller wants it, then it can have it, | * If the caller wants it, then it can have it, | ||||
* the entry will be deleted after the end of the current epoch. | * the entry will be deleted after the end of the current epoch. | ||||
*/ | */ | ||||
if (ret_nrt) | |||||
*ret_nrt = rt; | |||||
rtfree(rt); | rtfree(rt); | ||||
return (0); | return (0); | ||||
} | } | ||||
int | |||||
rib_change_route(uint32_t fibnum, struct rt_addrinfo *info, | |||||
struct rib_cmd_info *rc) | |||||
{ | |||||
struct rib_head *rnh; | |||||
NET_EPOCH_ASSERT(); | |||||
rnh = get_rnh(fibnum, info); | |||||
if (rnh == NULL) | |||||
return (EAFNOSUPPORT); | |||||
bzero(rc, sizeof(struct rib_cmd_info)); | |||||
rc->rc_cmd = RTM_CHANGE; | |||||
return (change_route(rnh, info, rc)); | |||||
} | |||||
static int | static int | ||||
change_route_one(struct rib_head *rnh, struct rt_addrinfo *info, | change_route_one(struct rib_head *rnh, struct rt_addrinfo *info, | ||||
struct rtentry **ret_nrt) | struct rib_cmd_info *rc) | ||||
{ | { | ||||
RIB_RLOCK_TRACKER; | RIB_RLOCK_TRACKER; | ||||
struct rtentry *rt = NULL; | struct rtentry *rt = NULL; | ||||
int error = 0; | int error = 0; | ||||
int free_ifa = 0; | int free_ifa = 0; | ||||
struct nhop_object *nh, *nh_orig; | struct nhop_object *nh, *nh_orig; | ||||
RIB_RLOCK(rnh); | RIB_RLOCK(rnh); | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | if ((nh_orig->nh_ifa != nh->nh_ifa) && nh_orig->nh_ifa->ifa_rtrequest) | ||||
nh_orig->nh_ifa->ifa_rtrequest(RTM_DELETE, rt, nh_orig, info); | nh_orig->nh_ifa->ifa_rtrequest(RTM_DELETE, rt, nh_orig, info); | ||||
rt->rt_nhop = nh; | rt->rt_nhop = nh; | ||||
rt_setmetrics(info, rt); | rt_setmetrics(info, rt); | ||||
if ((nh_orig->nh_ifa != nh->nh_ifa) && nh_orig->nh_ifa->ifa_rtrequest) | if ((nh_orig->nh_ifa != nh->nh_ifa) && nh_orig->nh_ifa->ifa_rtrequest) | ||||
nh_orig->nh_ifa->ifa_rtrequest(RTM_DELETE, rt, nh_orig, info); | nh_orig->nh_ifa->ifa_rtrequest(RTM_DELETE, rt, nh_orig, info); | ||||
if (ret_nrt != NULL) | /* Finalize notification */ | ||||
*ret_nrt = rt; | rc->rc_rt = rt; | ||||
rc->rc_nh_old = nh_orig; | |||||
rc->rc_nh_new = rt->rt_nhop; | |||||
RT_UNLOCK(rt); | RT_UNLOCK(rt); | ||||
/* Update generation id to reflect rtable change */ | /* Update generation id to reflect rtable change */ | ||||
rnh->rnh_gen++; | rnh->rnh_gen++; | ||||
rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); | |||||
RIB_WUNLOCK(rnh); | RIB_WUNLOCK(rnh); | ||||
nhop_free(nh_orig); | nhop_free(nh_orig); | ||||
return (0); | return (0); | ||||
} | } | ||||
int | int | ||||
change_route(struct rib_head *rnh, struct rt_addrinfo *info, | change_route(struct rib_head *rnh, struct rt_addrinfo *info, | ||||
struct rtentry **ret_nrt) | struct rib_cmd_info *rc) | ||||
{ | { | ||||
int error; | int error; | ||||
/* Check if updated gateway exists */ | /* Check if updated gateway exists */ | ||||
if ((info->rti_flags & RTF_GATEWAY) && | if ((info->rti_flags & RTF_GATEWAY) && | ||||
(info->rti_info[RTAX_GATEWAY] == NULL)) | (info->rti_info[RTAX_GATEWAY] == NULL)) | ||||
return (EINVAL); | return (EINVAL); | ||||
/* | /* | ||||
* route change is done in multiple steps, with dropping and | * route change is done in multiple steps, with dropping and | ||||
* reacquiring lock. In the situations with multiple processes | * reacquiring lock. In the situations with multiple processes | ||||
* changes the same route in can lead to the case when route | * changes the same route in can lead to the case when route | ||||
* is changed between the steps. Address it by retrying the operation | * is changed between the steps. Address it by retrying the operation | ||||
* multiple times before failing. | * multiple times before failing. | ||||
*/ | */ | ||||
for (int i = 0; i < RIB_MAX_RETRIES; i++) { | for (int i = 0; i < RIB_MAX_RETRIES; i++) { | ||||
error = change_route_one(rnh, info, ret_nrt); | error = change_route_one(rnh, info, rc); | ||||
if (error != EAGAIN) | if (error != EAGAIN) | ||||
break; | break; | ||||
} | } | ||||
return (error); | return (error); | ||||
} | } | ||||
static void | static void | ||||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Lines | while (di.head != NULL) { | ||||
if (report) | if (report) | ||||
rt_routemsg(RTM_DELETE, rt, rt->rt_nhop->nh_ifp, 0, | rt_routemsg(RTM_DELETE, rt, rt->rt_nhop->nh_ifp, 0, | ||||
fibnum); | fibnum); | ||||
rtfree(rt); | rtfree(rt); | ||||
} | } | ||||
} | } | ||||
static void | |||||
rib_notify(struct rib_head *rnh, enum rib_subscription_type type, | |||||
struct rib_cmd_info *rc) | |||||
{ | |||||
struct rib_subscription *rs; | |||||
CK_STAILQ_FOREACH(rs, &rnh->rnh_subscribers, next) { | |||||
if (rs->type == type) | |||||
rs->func(rnh, rc, rs->arg); | |||||
} | |||||
} | |||||
struct rib_subscription * | |||||
rib_subscribe(uint32_t fibnum, int family, rib_subscription_cb_t *f, void *arg, | |||||
enum rib_subscription_type type, int waitok) | |||||
{ | |||||
struct rib_head *rnh; | |||||
struct rib_subscription *rs; | |||||
int flags = M_ZERO | (waitok ? M_WAITOK : 0); | |||||
NET_EPOCH_ASSERT(); | |||||
KASSERT((fibnum < rt_numfibs), ("%s: bad fibnum", __func__)); | |||||
rnh = rt_tables_get_rnh(fibnum, family); | |||||
rs = malloc(sizeof(struct rib_subscription), M_RTABLE, flags); | |||||
if (rs == NULL) | |||||
return (NULL); | |||||
rs->func = f; | |||||
rs->arg = arg; | |||||
rs->type = type; | |||||
RIB_WLOCK(rnh); | |||||
CK_STAILQ_INSERT_TAIL(&rnh->rnh_subscribers, rs, next); | |||||
RIB_WUNLOCK(rnh); | |||||
return (rs); | |||||
} | |||||
int | |||||
rib_unsibscribe(uint32_t fibnum, int family, struct rib_subscription *rs) | |||||
{ | |||||
struct rib_head *rnh; | |||||
NET_EPOCH_ASSERT(); | |||||
KASSERT((fibnum < rt_numfibs), ("%s: bad fibnum", __func__)); | |||||
rnh = rt_tables_get_rnh(fibnum, family); | |||||
if (rnh == NULL) | |||||
return (ENOENT); | |||||
RIB_WLOCK(rnh); | |||||
CK_STAILQ_REMOVE(&rnh->rnh_subscribers, rs, rib_subscription, next); | |||||
RIB_WUNLOCK(rnh); | |||||
free(rs, M_RTABLE); | |||||
return (0); | |||||
} | |||||