mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
![David Ahern](/assets/img/avatar_default.png)
The fundamental premise of VRF and l3mdev core code is binding a socket to a device (l3mdev or netdev with an L3 domain) to indicate L3 scope. Legacy code resets flowi_oif to the l3mdev losing any original port device binding. Ben (among others) has demonstrated use cases where the original port device binding is important and needs to be retained. This patch handles that by adding a new entry to the common flow struct that can indicate the l3mdev index for later rule and table matching avoiding the need to reset flowi_oif. In addition to allowing more use cases that require port device binds, this patch brings a few datapath simplications: 1. l3mdev_fib_rule_match is only called when walking fib rules and always after l3mdev_update_flow. That allows an optimization to bail early for non-VRF type uses cases when flowi_l3mdev is not set. Also, only that index needs to be checked for the FIB table id. 2. l3mdev_update_flow can be called with flowi_oif set to a l3mdev (e.g., VRF) device. By resetting flowi_oif only for this case the FLOWI_FLAG_SKIP_NH_OIF flag is not longer needed and can be removed, removing several checks in the datapath. The flowi_iif path can be simplified to only be called if the it is not loopback (loopback can not be assigned to an L3 domain) and the l3mdev index is not already set. 3. Avoid another device lookup in the output path when the fib lookup returns a reject failure. Note: 2 functional tests for local traffic with reject fib rules are updated to reflect the new direct failure at FIB lookup time for ping rather than the failure on packet path. The current code fails like this: HINT: Fails since address on vrf device is out of device scope COMMAND: ip netns exec ns-A ping -c1 -w1 -I eth1 172.16.3.1 ping: Warning: source address might be selected on device other than: eth1 PING 172.16.3.1 (172.16.3.1) from 172.16.3.1 eth1: 56(84) bytes of data. --- 172.16.3.1 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms where the test now directly fails: HINT: Fails since address on vrf device is out of device scope COMMAND: ip netns exec ns-A ping -c1 -w1 -I eth1 172.16.3.1 ping: connect: No route to host Signed-off-by: David Ahern <dsahern@kernel.org> Tested-by: Ben Greear <greearb@candelatech.com> Link: https://lore.kernel.org/r/20220314204551.16369-1-dsahern@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
259 lines
5.9 KiB
C
259 lines
5.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* xfrm4_policy.c
|
|
*
|
|
* Changes:
|
|
* Kazunori MIYAZAWA @USAGI
|
|
* YOSHIFUJI Hideaki @USAGI
|
|
* Split up af-specific portion
|
|
*
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/inetdevice.h>
|
|
#include <net/dst.h>
|
|
#include <net/xfrm.h>
|
|
#include <net/ip.h>
|
|
#include <net/l3mdev.h>
|
|
|
|
static struct dst_entry *__xfrm4_dst_lookup(struct net *net, struct flowi4 *fl4,
|
|
int tos, int oif,
|
|
const xfrm_address_t *saddr,
|
|
const xfrm_address_t *daddr,
|
|
u32 mark)
|
|
{
|
|
struct rtable *rt;
|
|
|
|
memset(fl4, 0, sizeof(*fl4));
|
|
fl4->daddr = daddr->a4;
|
|
fl4->flowi4_tos = tos;
|
|
fl4->flowi4_l3mdev = l3mdev_master_ifindex_by_index(net, oif);
|
|
fl4->flowi4_mark = mark;
|
|
if (saddr)
|
|
fl4->saddr = saddr->a4;
|
|
|
|
rt = __ip_route_output_key(net, fl4);
|
|
if (!IS_ERR(rt))
|
|
return &rt->dst;
|
|
|
|
return ERR_CAST(rt);
|
|
}
|
|
|
|
static struct dst_entry *xfrm4_dst_lookup(struct net *net, int tos, int oif,
|
|
const xfrm_address_t *saddr,
|
|
const xfrm_address_t *daddr,
|
|
u32 mark)
|
|
{
|
|
struct flowi4 fl4;
|
|
|
|
return __xfrm4_dst_lookup(net, &fl4, tos, oif, saddr, daddr, mark);
|
|
}
|
|
|
|
static int xfrm4_get_saddr(struct net *net, int oif,
|
|
xfrm_address_t *saddr, xfrm_address_t *daddr,
|
|
u32 mark)
|
|
{
|
|
struct dst_entry *dst;
|
|
struct flowi4 fl4;
|
|
|
|
dst = __xfrm4_dst_lookup(net, &fl4, 0, oif, NULL, daddr, mark);
|
|
if (IS_ERR(dst))
|
|
return -EHOSTUNREACH;
|
|
|
|
saddr->a4 = fl4.saddr;
|
|
dst_release(dst);
|
|
return 0;
|
|
}
|
|
|
|
static int xfrm4_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
|
|
const struct flowi *fl)
|
|
{
|
|
struct rtable *rt = (struct rtable *)xdst->route;
|
|
const struct flowi4 *fl4 = &fl->u.ip4;
|
|
|
|
xdst->u.rt.rt_iif = fl4->flowi4_iif;
|
|
|
|
xdst->u.dst.dev = dev;
|
|
dev_hold_track(dev, &xdst->u.dst.dev_tracker, GFP_ATOMIC);
|
|
|
|
/* Sheit... I remember I did this right. Apparently,
|
|
* it was magically lost, so this code needs audit */
|
|
xdst->u.rt.rt_is_input = rt->rt_is_input;
|
|
xdst->u.rt.rt_flags = rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST |
|
|
RTCF_LOCAL);
|
|
xdst->u.rt.rt_type = rt->rt_type;
|
|
xdst->u.rt.rt_uses_gateway = rt->rt_uses_gateway;
|
|
xdst->u.rt.rt_gw_family = rt->rt_gw_family;
|
|
if (rt->rt_gw_family == AF_INET)
|
|
xdst->u.rt.rt_gw4 = rt->rt_gw4;
|
|
else if (rt->rt_gw_family == AF_INET6)
|
|
xdst->u.rt.rt_gw6 = rt->rt_gw6;
|
|
xdst->u.rt.rt_pmtu = rt->rt_pmtu;
|
|
xdst->u.rt.rt_mtu_locked = rt->rt_mtu_locked;
|
|
INIT_LIST_HEAD(&xdst->u.rt.rt_uncached);
|
|
rt_add_uncached_list(&xdst->u.rt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xfrm4_update_pmtu(struct dst_entry *dst, struct sock *sk,
|
|
struct sk_buff *skb, u32 mtu,
|
|
bool confirm_neigh)
|
|
{
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
struct dst_entry *path = xdst->route;
|
|
|
|
path->ops->update_pmtu(path, sk, skb, mtu, confirm_neigh);
|
|
}
|
|
|
|
static void xfrm4_redirect(struct dst_entry *dst, struct sock *sk,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
struct dst_entry *path = xdst->route;
|
|
|
|
path->ops->redirect(path, sk, skb);
|
|
}
|
|
|
|
static void xfrm4_dst_destroy(struct dst_entry *dst)
|
|
{
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
dst_destroy_metrics_generic(dst);
|
|
if (xdst->u.rt.rt_uncached_list)
|
|
rt_del_uncached_list(&xdst->u.rt);
|
|
xfrm_dst_destroy(xdst);
|
|
}
|
|
|
|
static void xfrm4_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
|
|
int unregister)
|
|
{
|
|
if (!unregister)
|
|
return;
|
|
|
|
xfrm_dst_ifdown(dst, dev);
|
|
}
|
|
|
|
static struct dst_ops xfrm4_dst_ops_template = {
|
|
.family = AF_INET,
|
|
.update_pmtu = xfrm4_update_pmtu,
|
|
.redirect = xfrm4_redirect,
|
|
.cow_metrics = dst_cow_metrics_generic,
|
|
.destroy = xfrm4_dst_destroy,
|
|
.ifdown = xfrm4_dst_ifdown,
|
|
.local_out = __ip_local_out,
|
|
.gc_thresh = 32768,
|
|
};
|
|
|
|
static const struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
|
|
.dst_ops = &xfrm4_dst_ops_template,
|
|
.dst_lookup = xfrm4_dst_lookup,
|
|
.get_saddr = xfrm4_get_saddr,
|
|
.fill_dst = xfrm4_fill_dst,
|
|
.blackhole_route = ipv4_blackhole_route,
|
|
};
|
|
|
|
#ifdef CONFIG_SYSCTL
|
|
static struct ctl_table xfrm4_policy_table[] = {
|
|
{
|
|
.procname = "xfrm4_gc_thresh",
|
|
.data = &init_net.xfrm.xfrm4_dst_ops.gc_thresh,
|
|
.maxlen = sizeof(int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{ }
|
|
};
|
|
|
|
static __net_init int xfrm4_net_sysctl_init(struct net *net)
|
|
{
|
|
struct ctl_table *table;
|
|
struct ctl_table_header *hdr;
|
|
|
|
table = xfrm4_policy_table;
|
|
if (!net_eq(net, &init_net)) {
|
|
table = kmemdup(table, sizeof(xfrm4_policy_table), GFP_KERNEL);
|
|
if (!table)
|
|
goto err_alloc;
|
|
|
|
table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh;
|
|
}
|
|
|
|
hdr = register_net_sysctl(net, "net/ipv4", table);
|
|
if (!hdr)
|
|
goto err_reg;
|
|
|
|
net->ipv4.xfrm4_hdr = hdr;
|
|
return 0;
|
|
|
|
err_reg:
|
|
if (!net_eq(net, &init_net))
|
|
kfree(table);
|
|
err_alloc:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static __net_exit void xfrm4_net_sysctl_exit(struct net *net)
|
|
{
|
|
struct ctl_table *table;
|
|
|
|
if (!net->ipv4.xfrm4_hdr)
|
|
return;
|
|
|
|
table = net->ipv4.xfrm4_hdr->ctl_table_arg;
|
|
unregister_net_sysctl_table(net->ipv4.xfrm4_hdr);
|
|
if (!net_eq(net, &init_net))
|
|
kfree(table);
|
|
}
|
|
#else /* CONFIG_SYSCTL */
|
|
static inline int xfrm4_net_sysctl_init(struct net *net)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void xfrm4_net_sysctl_exit(struct net *net)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static int __net_init xfrm4_net_init(struct net *net)
|
|
{
|
|
int ret;
|
|
|
|
memcpy(&net->xfrm.xfrm4_dst_ops, &xfrm4_dst_ops_template,
|
|
sizeof(xfrm4_dst_ops_template));
|
|
ret = dst_entries_init(&net->xfrm.xfrm4_dst_ops);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = xfrm4_net_sysctl_init(net);
|
|
if (ret)
|
|
dst_entries_destroy(&net->xfrm.xfrm4_dst_ops);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __net_exit xfrm4_net_exit(struct net *net)
|
|
{
|
|
xfrm4_net_sysctl_exit(net);
|
|
dst_entries_destroy(&net->xfrm.xfrm4_dst_ops);
|
|
}
|
|
|
|
static struct pernet_operations __net_initdata xfrm4_net_ops = {
|
|
.init = xfrm4_net_init,
|
|
.exit = xfrm4_net_exit,
|
|
};
|
|
|
|
static void __init xfrm4_policy_init(void)
|
|
{
|
|
xfrm_policy_register_afinfo(&xfrm4_policy_afinfo, AF_INET);
|
|
}
|
|
|
|
void __init xfrm4_init(void)
|
|
{
|
|
xfrm4_state_init();
|
|
xfrm4_policy_init();
|
|
xfrm4_protocol_init();
|
|
register_pernet_subsys(&xfrm4_net_ops);
|
|
}
|