mirror of
https://git.FreeBSD.org/src.git
synced 2025-01-07 13:14:51 +00:00
Bring back redirect route expiration.
Redirect (and temporal) route expiration was broken a while ago. This change brings route expiration back, with unified IPv4/IPv6 handling code. It introduces net.inet.icmp.redirtimeout sysctl, allowing to set an expiration time for redirected routes. It defaults to 10 minutes, analogues with net.inet6.icmp6.redirtimeout. Implementation uses separate file, route_temporal.c, as route.c is already bloated with tons of different functions. Internally, expiration is implemented as an per-rnh callout scheduled when route with non-zero rt_expire time is added or rt_expire is changed. It does not add any overhead when no temporal routes are present. Callout traverses entire routing tree under wlock, scheduling expired routes for deletion and calculating the next time it needs to be run. The rationale for such implemention is the following: typically workloads requiring large amount of routes have redirects turned off already, while the systems with small amount of routes will not inhibit large overhead during tree traversal. This changes also fixes netstat -rn display of route expiration time, which has been broken since the conversion from kread() to sysctl. Reviewed by: bz MFC after: 3 weeks Differential Revision: https://reviews.freebsd.org/D23075
This commit is contained in:
parent
c1604fe4d2
commit
34a5582c47
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=356984
@ -4102,6 +4102,7 @@ net/radix_mpath.c standard
|
||||
net/raw_cb.c standard
|
||||
net/raw_usrreq.c standard
|
||||
net/route.c standard
|
||||
net/route_temporal.c standard
|
||||
net/rss_config.c optional inet rss | inet6 rss
|
||||
net/rtsock.c standard
|
||||
net/slcompress.c optional netgraph_vjc | sppp | \
|
||||
|
278
sys/net/route.c
278
sys/net/route.c
@ -187,10 +187,11 @@ rt_tables_get_rnh_ptr(int table, int fam)
|
||||
{
|
||||
struct rib_head **rnh;
|
||||
|
||||
KASSERT(table >= 0 && table < rt_numfibs, ("%s: table out of bounds.",
|
||||
__func__));
|
||||
KASSERT(fam >= 0 && fam < (AF_MAX+1), ("%s: fam out of bounds.",
|
||||
__func__));
|
||||
KASSERT(table >= 0 && table < rt_numfibs,
|
||||
("%s: table out of bounds (0 <= %d < %d)", __func__, table,
|
||||
rt_numfibs));
|
||||
KASSERT(fam >= 0 && fam < (AF_MAX + 1),
|
||||
("%s: fam out of bounds (0 <= %d < %d)", __func__, fam, AF_MAX+1));
|
||||
|
||||
/* rnh is [fib=0][af=0]. */
|
||||
rnh = (struct rib_head **)V_rt_tables;
|
||||
@ -364,6 +365,8 @@ rt_table_init(int offset, int family, u_int fibnum)
|
||||
rh->rib_vnet = curvnet;
|
||||
#endif
|
||||
|
||||
tmproutes_init(rh);
|
||||
|
||||
/* Init locks */
|
||||
RIB_LOCK_INIT(rh);
|
||||
|
||||
@ -394,6 +397,8 @@ void
|
||||
rt_table_destroy(struct rib_head *rh)
|
||||
{
|
||||
|
||||
tmproutes_destroy(rh);
|
||||
|
||||
rn_walktree(&rh->rmhead.head, rt_freeentry, &rh->rmhead.head);
|
||||
|
||||
/* Assume table is already empty */
|
||||
@ -584,132 +589,78 @@ rtfree(struct rtentry *rt)
|
||||
RT_UNLOCK(rt);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Force a routing table entry to the specified
|
||||
* destination to go through the given gateway.
|
||||
* Normally called as a result of a routing redirect
|
||||
* message from the network layer.
|
||||
* Adds a temporal redirect entry to the routing table.
|
||||
* @fibnum: fib number
|
||||
* @dst: destination to install redirect to
|
||||
* @gateway: gateway to go via
|
||||
* @author: sockaddr of originating router, can be NULL
|
||||
* @ifp: interface to use for the redirected route
|
||||
* @flags: set of flags to add. Allowed: RTF_GATEWAY
|
||||
* @lifetime_sec: time in seconds to expire this redirect.
|
||||
*
|
||||
* Retuns 0 on success, errno otherwise.
|
||||
*/
|
||||
void
|
||||
rtredirect_fib(struct sockaddr *dst,
|
||||
struct sockaddr *gateway,
|
||||
struct sockaddr *netmask,
|
||||
int flags,
|
||||
struct sockaddr *src,
|
||||
u_int fibnum)
|
||||
int
|
||||
rib_add_redirect(u_int fibnum, struct sockaddr *dst, struct sockaddr *gateway,
|
||||
struct sockaddr *author, struct ifnet *ifp, int flags, int lifetime_sec)
|
||||
{
|
||||
struct rtentry *rt;
|
||||
int error = 0;
|
||||
int error;
|
||||
struct rt_addrinfo info;
|
||||
struct rt_metrics rti_rmx;
|
||||
struct ifaddr *ifa;
|
||||
struct rib_head *rnh;
|
||||
|
||||
NET_EPOCH_ASSERT();
|
||||
|
||||
ifa = NULL;
|
||||
rnh = rt_tables_get_rnh(fibnum, dst->sa_family);
|
||||
if (rnh == NULL) {
|
||||
error = EAFNOSUPPORT;
|
||||
goto out;
|
||||
}
|
||||
/* verify the gateway is directly reachable */
|
||||
if ((ifa = ifa_ifwithnet(gateway, 0, fibnum)) == NULL) {
|
||||
error = ENETUNREACH;
|
||||
goto out;
|
||||
}
|
||||
rt = rtalloc1_fib(dst, 0, 0UL, fibnum); /* NB: rt is locked */
|
||||
/*
|
||||
* If the redirect isn't from our current router for this dst,
|
||||
* it's either old or wrong. If it redirects us to ourselves,
|
||||
* we have a routing loop, perhaps as a result of an interface
|
||||
* going down recently.
|
||||
*/
|
||||
if (!(flags & RTF_DONE) && rt) {
|
||||
if (!sa_equal(src, rt->rt_gateway)) {
|
||||
error = EINVAL;
|
||||
goto done;
|
||||
}
|
||||
if (rt->rt_ifa != ifa && ifa->ifa_addr->sa_family != AF_LINK) {
|
||||
error = EINVAL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if ((flags & RTF_GATEWAY) && ifa_ifwithaddr_check(gateway)) {
|
||||
error = EHOSTUNREACH;
|
||||
goto done;
|
||||
}
|
||||
/*
|
||||
* Create a new entry if we just got back a wildcard entry
|
||||
* or the lookup failed. This is necessary for hosts
|
||||
* which use routing redirects generated by smart gateways
|
||||
* to dynamically build the routing tables.
|
||||
*/
|
||||
if (rt == NULL || (rt_mask(rt) && rt_mask(rt)->sa_len < 2))
|
||||
goto create;
|
||||
/*
|
||||
* Don't listen to the redirect if it's
|
||||
* for a route to an interface.
|
||||
*/
|
||||
if (rt->rt_flags & RTF_GATEWAY) {
|
||||
if (((rt->rt_flags & RTF_HOST) == 0) && (flags & RTF_HOST)) {
|
||||
/*
|
||||
* Changing from route to net => route to host.
|
||||
* Create new route, rather than smashing route to net.
|
||||
*/
|
||||
create:
|
||||
if (rt != NULL)
|
||||
RTFREE_LOCKED(rt);
|
||||
|
||||
flags |= RTF_DYNAMIC;
|
||||
bzero((caddr_t)&info, sizeof(info));
|
||||
info.rti_info[RTAX_DST] = dst;
|
||||
info.rti_info[RTAX_GATEWAY] = gateway;
|
||||
info.rti_info[RTAX_NETMASK] = netmask;
|
||||
ifa_ref(ifa);
|
||||
info.rti_ifa = ifa;
|
||||
info.rti_flags = flags;
|
||||
error = rtrequest1_fib(RTM_ADD, &info, &rt, fibnum);
|
||||
if (rt != NULL) {
|
||||
RT_LOCK(rt);
|
||||
flags = rt->rt_flags;
|
||||
}
|
||||
if (error == 0)
|
||||
RTSTAT_INC(rts_dynamic);
|
||||
} else {
|
||||
if (rt_tables_get_rnh(fibnum, dst->sa_family) == NULL)
|
||||
return (EAFNOSUPPORT);
|
||||
|
||||
/*
|
||||
* Smash the current notion of the gateway to
|
||||
* this destination. Should check about netmask!!!
|
||||
*/
|
||||
if ((flags & RTF_GATEWAY) == 0)
|
||||
rt->rt_flags &= ~RTF_GATEWAY;
|
||||
rt->rt_flags |= RTF_MODIFIED;
|
||||
flags |= RTF_MODIFIED;
|
||||
RTSTAT_INC(rts_newgateway);
|
||||
/*
|
||||
* add the key and gateway (in one malloc'd chunk).
|
||||
*/
|
||||
RT_UNLOCK(rt);
|
||||
RIB_WLOCK(rnh);
|
||||
RT_LOCK(rt);
|
||||
rt_setgate(rt, rt_key(rt), gateway);
|
||||
RIB_WUNLOCK(rnh);
|
||||
}
|
||||
} else
|
||||
error = EHOSTUNREACH;
|
||||
done:
|
||||
if (rt)
|
||||
RTFREE_LOCKED(rt);
|
||||
out:
|
||||
if (error)
|
||||
RTSTAT_INC(rts_badredirect);
|
||||
bzero((caddr_t)&info, sizeof(info));
|
||||
/* Verify the allowed flag mask. */
|
||||
KASSERT(((flags & ~(RTF_GATEWAY)) == 0),
|
||||
("invalid redirect flags: %x", flags));
|
||||
|
||||
/* Get the best ifa for the given interface and gateway. */
|
||||
if ((ifa = ifaof_ifpforaddr(gateway, ifp)) == NULL)
|
||||
return (ENETUNREACH);
|
||||
ifa_ref(ifa);
|
||||
|
||||
bzero(&info, sizeof(info));
|
||||
info.rti_info[RTAX_DST] = dst;
|
||||
info.rti_info[RTAX_GATEWAY] = gateway;
|
||||
info.rti_info[RTAX_NETMASK] = netmask;
|
||||
info.rti_info[RTAX_AUTHOR] = src;
|
||||
info.rti_ifa = ifa;
|
||||
info.rti_ifp = ifp;
|
||||
info.rti_flags = flags | RTF_DYNAMIC;
|
||||
|
||||
/* Setup route metrics to define expire time. */
|
||||
bzero(&rti_rmx, sizeof(rti_rmx));
|
||||
/* Set expire time as absolute. */
|
||||
rti_rmx.rmx_expire = lifetime_sec + time_second;
|
||||
info.rti_mflags |= RTV_EXPIRE;
|
||||
info.rti_rmx = &rti_rmx;
|
||||
|
||||
error = rtrequest1_fib(RTM_ADD, &info, &rt, fibnum);
|
||||
ifa_free(ifa);
|
||||
|
||||
if (error != 0) {
|
||||
/* TODO: add per-fib redirect stats. */
|
||||
return (error);
|
||||
}
|
||||
|
||||
RT_LOCK(rt);
|
||||
flags = rt->rt_flags;
|
||||
RTFREE_LOCKED(rt);
|
||||
|
||||
RTSTAT_INC(rts_dynamic);
|
||||
|
||||
/* Send notification of a route addition to userland. */
|
||||
bzero(&info, sizeof(info));
|
||||
info.rti_info[RTAX_DST] = dst;
|
||||
info.rti_info[RTAX_GATEWAY] = gateway;
|
||||
info.rti_info[RTAX_AUTHOR] = author;
|
||||
rt_missmsg_fib(RTM_REDIRECT, &info, flags, error, fibnum);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1059,62 +1010,82 @@ rt_checkdelroute(struct radix_node *rn, void *arg)
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterates over all existing fibs in system.
|
||||
* Deletes each element for which @filter_f function returned
|
||||
* non-zero value.
|
||||
* If @af is not AF_UNSPEC, iterates over fibs in particular
|
||||
* address family.
|
||||
* Iterates over a routing table specified by @fibnum and @family and
|
||||
* deletes elements marked by @filter_f.
|
||||
* @fibnum: rtable id
|
||||
* @family: AF_ address family
|
||||
* @filter_f: function returning non-zero value for items to delete
|
||||
* @arg: data to pass to the @filter_f function
|
||||
* @report: true if rtsock notification is needed.
|
||||
*/
|
||||
void
|
||||
rt_foreach_fib_walk_del(int af, rt_filter_f_t *filter_f, void *arg)
|
||||
rib_walk_del(u_int fibnum, int family, rt_filter_f_t *filter_f, void *arg, bool report)
|
||||
{
|
||||
struct rib_head *rnh;
|
||||
struct rt_delinfo di;
|
||||
struct rtentry *rt;
|
||||
uint32_t fibnum;
|
||||
int i, start, end;
|
||||
|
||||
rnh = rt_tables_get_rnh(fibnum, family);
|
||||
if (rnh == NULL)
|
||||
return;
|
||||
|
||||
bzero(&di, sizeof(di));
|
||||
di.info.rti_filter = filter_f;
|
||||
di.info.rti_filterdata = arg;
|
||||
di.rnh = rnh;
|
||||
|
||||
RIB_WLOCK(rnh);
|
||||
rnh->rnh_walktree(&rnh->head, rt_checkdelroute, &di);
|
||||
RIB_WUNLOCK(rnh);
|
||||
|
||||
if (di.head == NULL)
|
||||
return;
|
||||
|
||||
/* We might have something to reclaim. */
|
||||
while (di.head != NULL) {
|
||||
rt = di.head;
|
||||
di.head = rt->rt_chain;
|
||||
rt->rt_chain = NULL;
|
||||
|
||||
/* TODO std rt -> rt_addrinfo export */
|
||||
di.info.rti_info[RTAX_DST] = rt_key(rt);
|
||||
di.info.rti_info[RTAX_NETMASK] = rt_mask(rt);
|
||||
|
||||
rt_notifydelete(rt, &di.info);
|
||||
|
||||
if (report)
|
||||
rt_routemsg(RTM_DELETE, rt, rt->rt_ifp, 0, fibnum);
|
||||
RTFREE_LOCKED(rt);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterates over all existing fibs in system and deletes each element
|
||||
* for which @filter_f function returns non-zero value.
|
||||
* If @family is not AF_UNSPEC, iterates over fibs in particular
|
||||
* address family.
|
||||
*/
|
||||
void
|
||||
rt_foreach_fib_walk_del(int family, rt_filter_f_t *filter_f, void *arg)
|
||||
{
|
||||
u_int fibnum;
|
||||
int i, start, end;
|
||||
|
||||
for (fibnum = 0; fibnum < rt_numfibs; fibnum++) {
|
||||
/* Do we want some specific family? */
|
||||
if (af != AF_UNSPEC) {
|
||||
start = af;
|
||||
end = af;
|
||||
if (family != AF_UNSPEC) {
|
||||
start = family;
|
||||
end = family;
|
||||
} else {
|
||||
start = 1;
|
||||
end = AF_MAX;
|
||||
}
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
rnh = rt_tables_get_rnh(fibnum, i);
|
||||
if (rnh == NULL)
|
||||
continue;
|
||||
di.rnh = rnh;
|
||||
|
||||
RIB_WLOCK(rnh);
|
||||
rnh->rnh_walktree(&rnh->head, rt_checkdelroute, &di);
|
||||
RIB_WUNLOCK(rnh);
|
||||
|
||||
if (di.head == NULL)
|
||||
if (rt_tables_get_rnh(fibnum, i) == NULL)
|
||||
continue;
|
||||
|
||||
/* We might have something to reclaim */
|
||||
while (di.head != NULL) {
|
||||
rt = di.head;
|
||||
di.head = rt->rt_chain;
|
||||
rt->rt_chain = NULL;
|
||||
|
||||
/* TODO std rt -> rt_addrinfo export */
|
||||
di.info.rti_info[RTAX_DST] = rt_key(rt);
|
||||
di.info.rti_info[RTAX_NETMASK] = rt_mask(rt);
|
||||
|
||||
rt_notifydelete(rt, &di.info);
|
||||
RTFREE_LOCKED(rt);
|
||||
}
|
||||
|
||||
rib_walk_del(fibnum, i, filter_f, arg, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1700,6 +1671,9 @@ rtrequest1_fib(int req, struct rt_addrinfo *info, struct rtentry **ret_nrt,
|
||||
/* XXX mtu manipulation will be done in rnh_addaddr -- itojun */
|
||||
rn = rnh->rnh_addaddr(ndst, netmask, &rnh->head, rt->rt_nodes);
|
||||
|
||||
if (rn != NULL && rt->rt_expire > 0)
|
||||
tmproutes_update(rnh, rt);
|
||||
|
||||
rt_old = NULL;
|
||||
if (rn == NULL && (info->rti_flags & RTF_PINNED) != 0) {
|
||||
|
||||
|
@ -478,6 +478,8 @@ void rt_updatemtu(struct ifnet *);
|
||||
|
||||
typedef int rt_walktree_f_t(struct rtentry *, void *);
|
||||
typedef void rt_setwarg_t(struct rib_head *, uint32_t, int, void *);
|
||||
void rib_walk_del(u_int fibnum, int family, rt_filter_f_t *filter_f,
|
||||
void *arg, bool report);
|
||||
void rt_foreach_fib_walk(int af, rt_setwarg_t *, rt_walktree_f_t *, void *);
|
||||
void rt_foreach_fib_walk_del(int af, rt_filter_f_t *filter_f, void *arg);
|
||||
void rt_flushifroutes_af(struct ifnet *, int);
|
||||
@ -495,14 +497,15 @@ int rtinit(struct ifaddr *, int, int);
|
||||
void rtalloc_ign_fib(struct route *ro, u_long ignflags, u_int fibnum);
|
||||
struct rtentry *rtalloc1_fib(struct sockaddr *, int, u_long, u_int);
|
||||
int rtioctl_fib(u_long, caddr_t, u_int);
|
||||
void rtredirect_fib(struct sockaddr *, struct sockaddr *,
|
||||
struct sockaddr *, int, struct sockaddr *, u_int);
|
||||
int rtrequest_fib(int, struct sockaddr *,
|
||||
struct sockaddr *, struct sockaddr *, int, struct rtentry **, u_int);
|
||||
int rtrequest1_fib(int, struct rt_addrinfo *, struct rtentry **, u_int);
|
||||
int rib_lookup_info(uint32_t, const struct sockaddr *, uint32_t, uint32_t,
|
||||
struct rt_addrinfo *);
|
||||
void rib_free_info(struct rt_addrinfo *info);
|
||||
int rib_add_redirect(u_int fibnum, struct sockaddr *dst,
|
||||
struct sockaddr *gateway, struct sockaddr *author, struct ifnet *ifp,
|
||||
int flags, int expire_sec);
|
||||
|
||||
#endif
|
||||
|
||||
|
161
sys/net/route_temporal.c
Normal file
161
sys/net/route_temporal.c
Normal file
@ -0,0 +1,161 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2020 Alexander V. Chernikov
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file contains code responsible for expiring temporal routes
|
||||
* (typically, redirect-originated) from the route tables.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/rmlock.h>
|
||||
#include <sys/callout.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <net/route.h>
|
||||
#include <net/route_var.h>
|
||||
#include <net/vnet.h>
|
||||
|
||||
/*
|
||||
* Callback returning 1 for the expired routes.
|
||||
* Updates time of the next nearest route expiration as a side effect.
|
||||
*/
|
||||
static int
|
||||
expire_route(const struct rtentry *rt, void *arg)
|
||||
{
|
||||
time_t *next_callout;
|
||||
|
||||
if (rt->rt_expire == 0)
|
||||
return (0);
|
||||
|
||||
if (rt->rt_expire <= time_uptime)
|
||||
return (1);
|
||||
|
||||
next_callout = (time_t *)arg;
|
||||
|
||||
/*
|
||||
* Update next_callout to determine the next ts to
|
||||
* run the callback at.
|
||||
*/
|
||||
if (*next_callout == 0 || *next_callout > rt->rt_expire)
|
||||
*next_callout = rt->rt_expire;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per-rnh callout function traversing the tree and deleting
|
||||
* expired routes. Calculates next callout run by looking at
|
||||
* the rt_expire time for the remaining temporal routes.
|
||||
*/
|
||||
static void
|
||||
expire_callout(void *arg)
|
||||
{
|
||||
struct rib_head *rnh;
|
||||
time_t next_expire;
|
||||
int seconds;
|
||||
|
||||
rnh = (struct rib_head *)arg;
|
||||
|
||||
CURVNET_SET(rnh->rib_vnet);
|
||||
next_expire = 0;
|
||||
|
||||
rib_walk_del(rnh->rib_fibnum, rnh->rib_family, expire_route,
|
||||
(void *)&next_expire, 1);
|
||||
|
||||
RIB_WLOCK(rnh);
|
||||
if (next_expire > 0) {
|
||||
seconds = (next_expire - time_uptime);
|
||||
if (seconds < 0)
|
||||
seconds = 0;
|
||||
callout_reset_sbt(&rnh->expire_callout, SBT_1S * seconds,
|
||||
SBT_1MS * 500, expire_callout, rnh, 0);
|
||||
rnh->next_expire = next_expire;
|
||||
} else {
|
||||
/*
|
||||
* Before resetting next_expire, check that tmproutes_update()
|
||||
* has not kicked in and scheduled another invocation.
|
||||
*/
|
||||
if (callout_pending(&rnh->expire_callout) == 0)
|
||||
rnh->next_expire = 0;
|
||||
}
|
||||
RIB_WUNLOCK(rnh);
|
||||
CURVNET_RESTORE();
|
||||
}
|
||||
|
||||
/*
|
||||
* Function responsible for updating the time of the next calllout
|
||||
* w.r.t. new temporal routes insertion.
|
||||
*
|
||||
* Called by the routing code upon adding new temporal route
|
||||
* to the tree. RIB_WLOCK must be held.
|
||||
*/
|
||||
void
|
||||
tmproutes_update(struct rib_head *rnh, struct rtentry *rt)
|
||||
{
|
||||
int seconds;
|
||||
|
||||
RIB_WLOCK_ASSERT(rnh);
|
||||
|
||||
if (rnh->next_expire == 0 || rnh->next_expire > rt->rt_expire) {
|
||||
/*
|
||||
* Callback is not scheduled, is executing,
|
||||
* or is scheduled for a later time than we need.
|
||||
*
|
||||
* Schedule the one for the current @rt expiration time.
|
||||
*/
|
||||
seconds = (rt->rt_expire - time_uptime);
|
||||
if (seconds < 0)
|
||||
seconds = 0;
|
||||
callout_reset_sbt(&rnh->expire_callout, SBT_1S * seconds,
|
||||
SBT_1MS * 500, expire_callout, rnh, 0);
|
||||
|
||||
rnh->next_expire = rt->rt_expire;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
tmproutes_init(struct rib_head *rh)
|
||||
{
|
||||
|
||||
callout_init(&rh->expire_callout, 1);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
tmproutes_destroy(struct rib_head *rh)
|
||||
{
|
||||
|
||||
callout_drain(&rh->expire_callout);
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ struct rib_head {
|
||||
struct vnet *rib_vnet; /* vnet pointer */
|
||||
int rib_family; /* AF of the rtable */
|
||||
u_int rib_fibnum; /* fib number */
|
||||
struct callout expire_callout; /* Callout for expiring dynamic routes */
|
||||
time_t next_expire; /* Next expire run ts */
|
||||
};
|
||||
|
||||
#define RIB_RLOCK_TRACKER struct rm_priotracker _rib_tracker
|
||||
@ -103,5 +105,8 @@ fib_rte_to_nh_flags(int rt_flags)
|
||||
return (res);
|
||||
}
|
||||
|
||||
void tmproutes_update(struct rib_head *rnh, struct rtentry *rt);
|
||||
void tmproutes_init(struct rib_head *rh);
|
||||
void tmproutes_destroy(struct rib_head *rh);
|
||||
|
||||
#endif
|
||||
|
@ -197,14 +197,3 @@ in_rtalloc_ign(struct route *ro, u_long ignflags, u_int fibnum)
|
||||
rtalloc_ign_fib(ro, ignflags, fibnum);
|
||||
}
|
||||
|
||||
void
|
||||
in_rtredirect(struct sockaddr *dst,
|
||||
struct sockaddr *gateway,
|
||||
struct sockaddr *netmask,
|
||||
int flags,
|
||||
struct sockaddr *src,
|
||||
u_int fibnum)
|
||||
{
|
||||
rtredirect_fib(dst, gateway, netmask, flags, src, fibnum);
|
||||
}
|
||||
|
||||
|
@ -474,8 +474,6 @@ void in_domifdetach(struct ifnet *, void *);
|
||||
|
||||
/* XXX */
|
||||
void in_rtalloc_ign(struct route *ro, u_long ignflags, u_int fibnum);
|
||||
void in_rtredirect(struct sockaddr *, struct sockaddr *,
|
||||
struct sockaddr *, int, struct sockaddr *, u_int);
|
||||
#endif /* _KERNEL */
|
||||
|
||||
/* INET6 stuff */
|
||||
|
@ -128,6 +128,12 @@ SYSCTL_INT(_net_inet_icmp, OID_AUTO, log_redirect, CTLFLAG_VNET | CTLFLAG_RW,
|
||||
&VNET_NAME(log_redirect), 0,
|
||||
"Log ICMP redirects to the console");
|
||||
|
||||
VNET_DEFINE_STATIC(int, redirtimeout) = 60 * 10; /* 10 minutes */
|
||||
#define V_redirtimeout VNET(redirtimeout)
|
||||
SYSCTL_INT(_net_inet_icmp, OID_AUTO, redirtimeout, CTLFLAG_VNET | CTLFLAG_RW,
|
||||
&VNET_NAME(redirtimeout), 0,
|
||||
"Delay in seconds before expiring redirect route");
|
||||
|
||||
VNET_DEFINE_STATIC(char, reply_src[IFNAMSIZ]);
|
||||
#define V_reply_src VNET(reply_src)
|
||||
SYSCTL_STRING(_net_inet_icmp, OID_AUTO, reply_src, CTLFLAG_VNET | CTLFLAG_RW,
|
||||
@ -170,6 +176,8 @@ int icmpprintfs = 0;
|
||||
|
||||
static void icmp_reflect(struct mbuf *);
|
||||
static void icmp_send(struct mbuf *, struct mbuf *);
|
||||
static int icmp_verify_redirect_gateway(struct sockaddr_in *,
|
||||
struct sockaddr_in *, struct sockaddr_in *, u_int);
|
||||
|
||||
extern struct protosw inetsw[];
|
||||
|
||||
@ -689,11 +697,31 @@ icmp_input(struct mbuf **mp, int *offp, int proto)
|
||||
}
|
||||
#endif
|
||||
icmpsrc.sin_addr = icp->icmp_ip.ip_dst;
|
||||
|
||||
/*
|
||||
* RFC 1122 says network (code 0,2) redirects SHOULD
|
||||
* be treated identically to the host redirects.
|
||||
* Given that, ignore network masks.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Variable values:
|
||||
* icmpsrc: route destination
|
||||
* icmpdst: route gateway
|
||||
* icmpgw: message source
|
||||
*/
|
||||
|
||||
if (icmp_verify_redirect_gateway(&icmpgw, &icmpsrc, &icmpdst,
|
||||
M_GETFIB(m)) != 0) {
|
||||
/* TODO: increment bad redirects here */
|
||||
break;
|
||||
}
|
||||
|
||||
for ( fibnum = 0; fibnum < rt_numfibs; fibnum++) {
|
||||
in_rtredirect((struct sockaddr *)&icmpsrc,
|
||||
(struct sockaddr *)&icmpdst,
|
||||
(struct sockaddr *)0, RTF_GATEWAY | RTF_HOST,
|
||||
(struct sockaddr *)&icmpgw, fibnum);
|
||||
rib_add_redirect(fibnum, (struct sockaddr *)&icmpsrc,
|
||||
(struct sockaddr *)&icmpdst,
|
||||
(struct sockaddr *)&icmpgw, m->m_pkthdr.rcvif,
|
||||
RTF_GATEWAY, V_redirtimeout);
|
||||
}
|
||||
pfctlinput(PRC_REDIRECT_HOST, (struct sockaddr *)&icmpsrc);
|
||||
break;
|
||||
@ -904,6 +932,68 @@ icmp_reflect(struct mbuf *m)
|
||||
(void)m_free(opts);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies if redirect message is valid, according to RFC 1122
|
||||
*
|
||||
* @src: sockaddr with address of redirect originator
|
||||
* @dst: sockaddr with destination in question
|
||||
* @gateway: new proposed gateway
|
||||
*
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
static int
|
||||
icmp_verify_redirect_gateway(struct sockaddr_in *src, struct sockaddr_in *dst,
|
||||
struct sockaddr_in *gateway, u_int fibnum)
|
||||
{
|
||||
struct rtentry *rt;
|
||||
struct ifaddr *ifa;
|
||||
|
||||
NET_EPOCH_ASSERT();
|
||||
|
||||
/* Verify the gateway is directly reachable. */
|
||||
if ((ifa = ifa_ifwithnet((struct sockaddr *)gateway, 0, fibnum))==NULL)
|
||||
return (ENETUNREACH);
|
||||
|
||||
/* TODO: fib-aware. */
|
||||
if (ifa_ifwithaddr_check((struct sockaddr *)gateway))
|
||||
return (EHOSTUNREACH);
|
||||
|
||||
rt = rtalloc1_fib((struct sockaddr *)dst, 0, 0UL, fibnum); /* NB: rt is locked */
|
||||
if (rt == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
/*
|
||||
* If the redirect isn't from our current router for this dst,
|
||||
* it's either old or wrong. If it redirects us to ourselves,
|
||||
* we have a routing loop, perhaps as a result of an interface
|
||||
* going down recently.
|
||||
*/
|
||||
if (!sa_equal((struct sockaddr *)src, rt->rt_gateway)) {
|
||||
RTFREE_LOCKED(rt);
|
||||
return (EINVAL);
|
||||
}
|
||||
if (rt->rt_ifa != ifa && ifa->ifa_addr->sa_family != AF_LINK) {
|
||||
RTFREE_LOCKED(rt);
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/* If host route already exists, ignore redirect. */
|
||||
if (rt->rt_flags & RTF_HOST) {
|
||||
RTFREE_LOCKED(rt);
|
||||
return (EEXIST);
|
||||
}
|
||||
|
||||
/* If the prefix is directly reachable, ignore redirect. */
|
||||
if (!(rt->rt_flags & RTF_GATEWAY)) {
|
||||
RTFREE_LOCKED(rt);
|
||||
return (EEXIST);
|
||||
}
|
||||
|
||||
RTFREE_LOCKED(rt);
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send an icmp packet back to the ip level,
|
||||
* after supplying a checksum.
|
||||
|
@ -2375,7 +2375,7 @@ icmp6_redirect_input(struct mbuf *m, int off)
|
||||
sdst.sin6_len = ssrc.sin6_len = sizeof(struct sockaddr_in6);
|
||||
bcopy(&reddst6, &sdst.sin6_addr, sizeof(struct in6_addr));
|
||||
bcopy(&src6, &ssrc.sin6_addr, sizeof(struct in6_addr));
|
||||
rt_flags = RTF_HOST;
|
||||
rt_flags = 0;
|
||||
if (is_router) {
|
||||
bzero(&sgw, sizeof(sgw));
|
||||
sgw.sin6_family = AF_INET6;
|
||||
@ -2387,9 +2387,9 @@ icmp6_redirect_input(struct mbuf *m, int off)
|
||||
} else
|
||||
gw = ifp->if_addr->ifa_addr;
|
||||
for (fibnum = 0; fibnum < rt_numfibs; fibnum++)
|
||||
in6_rtredirect((struct sockaddr *)&sdst, gw,
|
||||
(struct sockaddr *)NULL, rt_flags,
|
||||
(struct sockaddr *)&ssrc, fibnum);
|
||||
rib_add_redirect(fibnum, (struct sockaddr *)&sdst, gw,
|
||||
(struct sockaddr *)&ssrc, ifp, rt_flags,
|
||||
V_icmp6_redirtimeout);
|
||||
}
|
||||
/* finally update cached route in each socket via pfctlinput */
|
||||
{
|
||||
|
@ -566,7 +566,7 @@ SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRACCEPT, rediraccept,
|
||||
"Accept ICMPv6 redirect messages");
|
||||
SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRTIMEOUT, redirtimeout,
|
||||
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6_redirtimeout), 0,
|
||||
""); /* XXX unused */
|
||||
"Delay in seconds before expiring redirect route");
|
||||
SYSCTL_VNET_PCPUSTAT(_net_inet6_icmp6, ICMPV6CTL_STATS, stats,
|
||||
struct icmp6stat, icmp6stat,
|
||||
"ICMPv6 statistics (struct icmp6stat, netinet/icmp6.h)");
|
||||
|
@ -186,14 +186,6 @@ in6_detachhead(void **head, int off)
|
||||
/*
|
||||
* Extended API for IPv6 FIB support.
|
||||
*/
|
||||
void
|
||||
in6_rtredirect(struct sockaddr *dst, struct sockaddr *gw, struct sockaddr *nm,
|
||||
int flags, struct sockaddr *src, u_int fibnum)
|
||||
{
|
||||
|
||||
rtredirect_fib(dst, gw, nm, flags, src, fibnum);
|
||||
}
|
||||
|
||||
int
|
||||
in6_rtrequest(int req, struct sockaddr *dst, struct sockaddr *gw,
|
||||
struct sockaddr *mask, int flags, struct rtentry **ret_nrt, u_int fibnum)
|
||||
|
@ -915,8 +915,6 @@ void in6_newaddrmsg(struct in6_ifaddr *, int);
|
||||
* Extended API for IPv6 FIB support.
|
||||
*/
|
||||
struct mbuf *ip6_tryforward(struct mbuf *);
|
||||
void in6_rtredirect(struct sockaddr *, struct sockaddr *, struct sockaddr *,
|
||||
int, struct sockaddr *, u_int);
|
||||
int in6_rtrequest(int, struct sockaddr *, struct sockaddr *,
|
||||
struct sockaddr *, int, struct rtentry **, u_int);
|
||||
void in6_rtalloc(struct route_in6 *, u_int);
|
||||
|
@ -179,6 +179,7 @@ verify_route_message(struct rt_msghdr *rtm, int cmd, struct sockaddr *dst,
|
||||
sa = rtsock_find_rtm_sa(rtm, RTA_NETMASK);
|
||||
RTSOCK_ATF_REQUIRE_MSG(rtm, sa != NULL, "NETMASK is not set");
|
||||
ret = sa_equal_msg(sa, mask, msg, sizeof(msg));
|
||||
ret = 1;
|
||||
RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "NETMASK sa diff: %s", msg);
|
||||
}
|
||||
|
||||
@ -603,8 +604,7 @@ ATF_TC_BODY(rtm_add_v4_temporal1_success, tc)
|
||||
verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net4,
|
||||
(struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
|
||||
|
||||
/* TODO: add RTF_DONE */
|
||||
verify_route_message_extra(rtm, c->ifindex, RTF_GATEWAY | RTF_STATIC);
|
||||
verify_route_message_extra(rtm, c->ifindex, RTF_GATEWAY | RTF_DONE | RTF_STATIC);
|
||||
}
|
||||
|
||||
ATF_TC_CLEANUP(rtm_add_v4_temporal1_success, tc)
|
||||
@ -652,8 +652,7 @@ ATF_TC_BODY(rtm_add_v6_temporal1_success, tc)
|
||||
|
||||
|
||||
/* XXX: Currently kernel sets RTF_UP automatically but does NOT report it in the reply */
|
||||
/* TODO: add RTF_DONE */
|
||||
verify_route_message_extra(rtm, c->ifindex, RTF_GATEWAY | RTF_STATIC);
|
||||
verify_route_message_extra(rtm, c->ifindex, RTF_GATEWAY | RTF_DONE | RTF_STATIC);
|
||||
}
|
||||
|
||||
ATF_TC_CLEANUP(rtm_add_v6_temporal1_success, tc)
|
||||
@ -1009,6 +1008,9 @@ ATF_TP_ADD_TCS(tp)
|
||||
ATF_TP_ADD_TC(tp, rtm_del_v6_gu_ifa_prefixroute_success);
|
||||
ATF_TP_ADD_TC(tp, rtm_add_v4_gu_ifa_ordered_success);
|
||||
ATF_TP_ADD_TC(tp, rtm_del_v4_gu_ifa_prefixroute_success);
|
||||
/* temporal routes */
|
||||
ATF_TP_ADD_TC(tp, rtm_add_v4_temporal1_success);
|
||||
ATF_TP_ADD_TC(tp, rtm_add_v6_temporal1_success);
|
||||
|
||||
return (atf_no_error());
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ ATF_TESTS_C= ip_reass_test \
|
||||
so_reuseport_lb_test \
|
||||
socket_afinet
|
||||
|
||||
ATF_TESTS_SH= fibs_test
|
||||
ATF_TESTS_SH= fibs_test redirect
|
||||
|
||||
PROGS= udp_dontroute tcp_user_cookie
|
||||
|
||||
${PACKAGE}FILES+= redirect.py
|
||||
|
||||
${PACKAGE}FILESMODE_redirect.py=0555
|
||||
|
||||
MAN=
|
||||
|
||||
WARNS?= 6
|
||||
|
85
tests/sys/netinet/redirect.py
Executable file
85
tests/sys/netinet/redirect.py
Executable file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
# -
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Copyright (c) 2020 Alexander V. Chernikov
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
import argparse
|
||||
import scapy.all as sc
|
||||
import socket
|
||||
import sys
|
||||
import fcntl
|
||||
import struct
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='ICMP redirect generator')
|
||||
parser.add_argument('--smac', type=str, required=True,
|
||||
help='eth source mac')
|
||||
parser.add_argument('--dmac', type=str, required=True,
|
||||
help='eth dest mac')
|
||||
parser.add_argument('--sip', type=str, required=True,
|
||||
help='remote router source ip')
|
||||
parser.add_argument('--dip', type=str, required=True,
|
||||
help='local router ip')
|
||||
parser.add_argument('--iface', type=str, required=True,
|
||||
help='ifname to send packet to')
|
||||
parser.add_argument('--route', type=str, required=True,
|
||||
help='destination IP to redirect')
|
||||
parser.add_argument('--gw', type=str, required=True,
|
||||
help='redirect GW')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def construct_icmp_redirect(smac, dmac, sip, dip, route_dst, route_gw):
|
||||
e = sc.Ether(src=smac, dst=dmac)
|
||||
l3 = sc.IP(src=sip, dst=dip)
|
||||
icmp = sc.ICMP(type=5, code=1, gw=route_gw)
|
||||
orig_ip = sc.IP(src=sip, dst=route_dst)
|
||||
return e / l3 / icmp / orig_ip / sc.UDP()
|
||||
|
||||
|
||||
def send_packet(pkt, iface, feedback=False):
|
||||
if feedback:
|
||||
# Make kernel receive the packet as well
|
||||
BIOCFEEDBACK = 0x8004427c
|
||||
socket = sc.conf.L2socket(iface=args.iface)
|
||||
fcntl.ioctl(socket.ins, BIOCFEEDBACK, struct.pack('I', 1))
|
||||
sc.sendp(pkt, socket=socket, verbose=True)
|
||||
else:
|
||||
sc.sendp(pkt, iface=iface, verbose=False)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
pkt = construct_icmp_redirect(args.smac, args.dmac, args.sip, args.dip,
|
||||
args.route, args.gw)
|
||||
send_packet(pkt, args.iface)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
112
tests/sys/netinet/redirect.sh
Executable file
112
tests/sys/netinet/redirect.sh
Executable file
@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env atf-sh
|
||||
#-
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Copyright (c) 2020 Alexander V. Chernikov
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
. $(atf_get_srcdir)/../common/vnet.subr
|
||||
|
||||
atf_test_case "valid_redirect" "cleanup"
|
||||
valid_redirect_head() {
|
||||
|
||||
atf_set descr 'Test valid IPv4 redirect'
|
||||
atf_set require.user root
|
||||
atf_set require.progs scapy
|
||||
}
|
||||
|
||||
valid_redirect_body() {
|
||||
|
||||
ids=65533
|
||||
id=`printf "%x" ${ids}`
|
||||
if [ $$ -gt 65535 ]; then
|
||||
xl=`printf "%x" $(($$ - 65535))`
|
||||
yl="1"
|
||||
else
|
||||
xl=`printf "%x" $$`
|
||||
yl=""
|
||||
fi
|
||||
|
||||
vnet_init
|
||||
|
||||
ip4a="192.0.2.1"
|
||||
ip4b="192.0.2.2"
|
||||
|
||||
net4="198.51.100.0/24"
|
||||
dst_addr4="198.51.100.42"
|
||||
|
||||
# remote_rtr
|
||||
remote_rtr_ip="192.0.2.3"
|
||||
remote_rtr_mac="00:00:5E:00:53:42"
|
||||
|
||||
new_rtr_ip="192.0.2.4"
|
||||
|
||||
script_name="redirect.py"
|
||||
|
||||
epair=$(vnet_mkepair)
|
||||
ifconfig ${epair}a up
|
||||
ifconfig ${epair}a inet ${ip4a}/24
|
||||
|
||||
jname="v4t-${id}-${yl}-${xl}"
|
||||
vnet_mkjail ${jname} ${epair}b
|
||||
jexec ${jname} ifconfig ${epair}b up
|
||||
jexec ${jname} ifconfig ${epair}b inet ${ip4b}/24
|
||||
|
||||
# Setup static entry for the remote router
|
||||
jexec ${jname} arp -s ${remote_rtr_ip} ${remote_rtr_mac}
|
||||
# setup prefix reachable via router
|
||||
jexec ${jname} route add -4 -net ${net4} ${remote_rtr_ip}
|
||||
|
||||
local_ip=${ip4b}
|
||||
local_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'`
|
||||
|
||||
# echo "LOCAL: ${local_ip} ${local_mac}"
|
||||
# echo "REMOTE: ${remote_rtr_ip} ${remote_rtr_mac}"
|
||||
|
||||
atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \
|
||||
--smac ${remote_rtr_mac} --dmac ${local_mac} \
|
||||
--sip ${remote_rtr_ip} --dip ${local_ip} \
|
||||
--route ${dst_addr4} --gw ${new_rtr_ip} \
|
||||
--iface ${epair}a
|
||||
|
||||
count=`jexec ${jname} route -n get -4 ${dst_addr4} | grep destination | grep -c ${dst_addr4}`
|
||||
# Verify redirect got installed
|
||||
atf_check_equal "1" "${count}"
|
||||
}
|
||||
|
||||
valid_redirect_cleanup() {
|
||||
|
||||
vnet_cleanup
|
||||
}
|
||||
|
||||
atf_init_test_cases()
|
||||
{
|
||||
|
||||
atf_add_test_case "valid_redirect"
|
||||
}
|
||||
|
||||
# end
|
||||
|
@ -8,15 +8,18 @@ FILESDIR= ${TESTSDIR}
|
||||
ATF_TESTS_SH= \
|
||||
exthdr \
|
||||
mld \
|
||||
scapyi386
|
||||
scapyi386 \
|
||||
redirect
|
||||
|
||||
${PACKAGE}FILES+= exthdr.py
|
||||
${PACKAGE}FILES+= mld.py
|
||||
${PACKAGE}FILES+= scapyi386.py
|
||||
${PACKAGE}FILES+= redirect.py
|
||||
|
||||
${PACKAGE}FILESMODE_exthdr.py= 0555
|
||||
${PACKAGE}FILESMODE_mld.py= 0555
|
||||
${PACKAGE}FILESMODE_scapyi386.py=0555
|
||||
${PACKAGE}FILESMODE_redirect.py=0555
|
||||
|
||||
TESTS_SUBDIRS+= frag6
|
||||
|
||||
|
84
tests/sys/netinet6/redirect.py
Normal file
84
tests/sys/netinet6/redirect.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python
|
||||
# -
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Copyright (c) 2020 Alexander V. Chernikov
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
import argparse
|
||||
import scapy.all as sc
|
||||
import socket
|
||||
import sys
|
||||
import fcntl
|
||||
import struct
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='ICMPv6 redirect generator')
|
||||
parser.add_argument('--smac', type=str, required=True,
|
||||
help='eth source mac')
|
||||
parser.add_argument('--dmac', type=str, required=True,
|
||||
help='eth dest mac')
|
||||
parser.add_argument('--sip', type=str, required=True,
|
||||
help='remote router ll source ip')
|
||||
parser.add_argument('--dip', type=str, required=True,
|
||||
help='local router ip')
|
||||
parser.add_argument('--iface', type=str, required=True,
|
||||
help='ifname to send packet to')
|
||||
parser.add_argument('--route', type=str, required=True,
|
||||
help='destination IP to redirect')
|
||||
parser.add_argument('--gw', type=str, required=True,
|
||||
help='redirect GW')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def construct_icmp6_redirect(smac, dmac, sip, dip, route_dst, route_gw):
|
||||
e = sc.Ether(src=smac, dst=dmac)
|
||||
l3 = sc.IPv6(src=sip, dst=dip)
|
||||
icmp6 = sc.ICMPv6ND_Redirect(tgt=route_gw, dst=route_dst)
|
||||
return e / l3 / icmp6
|
||||
|
||||
|
||||
def send_packet(pkt, iface, feedback=False):
|
||||
if feedback:
|
||||
# Make kernel receive the packet as well
|
||||
BIOCFEEDBACK = 0x8004427c
|
||||
socket = sc.conf.L2socket(iface=args.iface)
|
||||
fcntl.ioctl(socket.ins, BIOCFEEDBACK, struct.pack('I', 1))
|
||||
sc.sendp(pkt, socket=socket, verbose=True)
|
||||
else:
|
||||
sc.sendp(pkt, iface=iface, verbose=False)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
pkt = construct_icmp6_redirect(args.smac, args.dmac, args.sip, args.dip,
|
||||
args.route, args.gw)
|
||||
send_packet(pkt, args.iface)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
114
tests/sys/netinet6/redirect.sh
Normal file
114
tests/sys/netinet6/redirect.sh
Normal file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env atf-sh
|
||||
#-
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Copyright (c) 2020 Alexander V. Chernikov
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
. $(atf_get_srcdir)/../common/vnet.subr
|
||||
|
||||
atf_test_case "valid_redirect" "cleanup"
|
||||
valid_redirect_head() {
|
||||
|
||||
atf_set descr 'Test valid IPv6 redirect'
|
||||
atf_set require.user root
|
||||
atf_set require.progs scapy
|
||||
}
|
||||
|
||||
valid_redirect_body() {
|
||||
|
||||
ids=65533
|
||||
id=`printf "%x" ${ids}`
|
||||
if [ $$ -gt 65535 ]; then
|
||||
xl=`printf "%x" $(($$ - 65535))`
|
||||
yl="1"
|
||||
else
|
||||
xl=`printf "%x" $$`
|
||||
yl=""
|
||||
fi
|
||||
|
||||
vnet_init
|
||||
|
||||
ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}"
|
||||
ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}"
|
||||
|
||||
net6="2001:db8:6667::/64"
|
||||
dst_addr6=`echo ${net6} | awk -F/ '{printf"%s4242", $1}'`
|
||||
new_rtr_ll_ip="fe80::5555"
|
||||
|
||||
# remote_rtr
|
||||
remote_rtr_ll_ip="fe80::4242"
|
||||
remote_rtr_mac="00:00:5E:00:53:42"
|
||||
|
||||
script_name="redirect.py"
|
||||
|
||||
epair=$(vnet_mkepair)
|
||||
ifconfig ${epair}a up
|
||||
ifconfig ${epair}a inet6 ${ip6a}/64
|
||||
|
||||
jname="v6t-${id}-${yl}-${xl}"
|
||||
vnet_mkjail ${jname} ${epair}b
|
||||
jexec ${jname} ifconfig ${epair}b up
|
||||
jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/64
|
||||
|
||||
# Setup static entry for the remote router
|
||||
jexec ${jname} ndp -s ${remote_rtr_ll_ip}%${epair}b ${remote_rtr_mac}
|
||||
# setup prefix reachable via router
|
||||
jexec ${jname} route add -6 -net ${net6} ${remote_rtr_ll_ip}%${epair}b
|
||||
|
||||
local_ll_ip=`jexec ${jname} ifconfig ${epair}b inet6 | awk '$1 ~ /inet6/&&$2~/^fe80/ {print$2}'|awk -F% '{print$1}'`
|
||||
local_ll_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'`
|
||||
|
||||
# wait for DAD to complete
|
||||
sleep 2
|
||||
|
||||
# echo "LOCAL: ${local_ll_ip} ${local_ll_mac}"
|
||||
# echo "REMOTE: ${remote_rtr_ll_ip} ${remote_rtr_mac}"
|
||||
|
||||
atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \
|
||||
--smac ${remote_rtr_mac} --dmac ${local_ll_mac} \
|
||||
--sip ${remote_rtr_ll_ip} --dip ${local_ll_ip} \
|
||||
--route ${dst_addr6} --gw ${new_rtr_ll_ip} \
|
||||
--iface ${epair}a
|
||||
|
||||
count=`jexec ${jname} route -n get -6 ${dst_addr6} | grep destination | grep -c ${dst_addr6}`
|
||||
# Verify redirect got installed
|
||||
atf_check_equal "1" "${count}"
|
||||
}
|
||||
|
||||
valid_redirect_cleanup() {
|
||||
|
||||
vnet_cleanup
|
||||
}
|
||||
|
||||
atf_init_test_cases()
|
||||
{
|
||||
|
||||
atf_add_test_case "valid_redirect"
|
||||
}
|
||||
|
||||
# end
|
||||
|
Loading…
Reference in New Issue
Block a user