Skip to content

Commit

Permalink
Merge branch 'ipv4-Enable-support-for-IPv6-gateway-with-IPv4-routes'
Browse files Browse the repository at this point in the history
David Ahern says:

====================
ipv4: Enable support for IPv6 gateway with IPv4 routes

Last set of three with the end goal of enabling IPv6 gateways with IPv4
routes.

This set adds fib6_nh_init and release to the IPv6 stubs, and adds neighbor
helpers that IPv4 code invokes to resolve an IPv6 address. When using
an IPv6 neighbor entry the hh_cache is bypassed as it contains the wrong
ethernet header for an IPv4 packet.

The nh_common nhc_has_gw was a temporary field used to convert existing
code from fib{6}_nh to fib_nh_common. That field is now converted to
nhc_gw_family to differentiate the address family of the gateway entry
as opposed to the address family of the container of fib_nh_common.

Existing code for rtable and fib_config is refactored to prepare
for a v6 address and then support is added. From there various
miscellaneous functions are updated to handle a v6 gateway - from
validating the v6 address to lookups in bpf code to verifying the
nexthop state.

Offload drivers - mlxsw and rocker - are modified to detect the v6
gateway and reject the route as 'unsupported'. e.g.,

    $ ip ro add 172.16.101.0/24 via inet6 fe80::202:ff:fe00:b dev swp1s0
    Error: mlxsw_spectrum: IPv6 gateway with IPv4 route is not supported.

This can be removed in time once support is added to each.

With the infrastructure changes in place, patch 17 enables it by adding
support for RTA_VIA to IPv4. RTA_VIA can be used for IPv4 addresses as
well. Only one of RTA_VIA and RTA_GATEWAY can be passed in a request.

Patch 18 adds a few test cases to fib_tests.sh.

v2
- comments from Ido - fixed typos as noted and updated messages
- add commit message to patch 1
- In patch 9, ipv4: Add fib_check_nh_v6_gw, moved the call to
  fib6_nh_release under the 'if (!err)' check as the intention is
  that release should not be called if init fails.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Apr 8, 2019
2 parents 3b8b11f + 228ddb3 commit 0ed8c3d
Show file tree
Hide file tree
Showing 32 changed files with 696 additions and 257 deletions.
2 changes: 1 addition & 1 deletion drivers/infiniband/core/addr.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ static bool has_gateway(const struct dst_entry *dst, sa_family_t family)

if (family == AF_INET) {
rt = container_of(dst, struct rtable, dst);
return rt->rt_uses_gateway;
return rt->rt_gw_family == AF_INET;
}

rt6 = container_of(dst, struct rt6_info, dst);
Expand Down
2 changes: 1 addition & 1 deletion drivers/infiniband/hw/nes/nes_cm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1407,7 +1407,7 @@ static int nes_addr_resolve_neigh(struct nes_vnic *nesvnic, u32 dst_ip, int arpi
if (neigh->nud_state & NUD_VALID) {
nes_debug(NES_DBG_CM, "Neighbor MAC address for 0x%08X"
" is %pM, Gateway is 0x%08X \n", dst_ip,
neigh->ha, ntohl(rt->rt_gateway));
neigh->ha, ntohl(rt->rt_gw4));

if (arpindex >= 0) {
if (ether_addr_equal(nesadapter->arp_table[arpindex].mac_addr, neigh->ha)) {
Expand Down
6 changes: 5 additions & 1 deletion drivers/net/appletalk/ipddp.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,15 @@ static struct net_device * __init ipddp_init(void)
*/
static netdev_tx_t ipddp_xmit(struct sk_buff *skb, struct net_device *dev)
{
__be32 paddr = skb_rtable(skb)->rt_gateway;
struct rtable *rtable = skb_rtable(skb);
__be32 paddr = 0;
struct ddpehdr *ddp;
struct ipddp_route *rt;
struct atalk_addr *our_addr;

if (rtable->rt_gw_family == AF_INET)
paddr = rtable->rt_gw4;

spin_lock(&ipddp_route_lock);

/*
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static int mlx5e_route_lookup_ipv4(struct mlx5e_priv *priv,
if (ret)
return ret;

if (mlx5_lag_is_multipath(mdev) && !rt->rt_gateway)
if (mlx5_lag_is_multipath(mdev) && rt->rt_gw_family != AF_INET)
return -ENETUNREACH;
#else
return -EOPNOTSUPP;
Expand Down
12 changes: 10 additions & 2 deletions drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
Original file line number Diff line number Diff line change
Expand Up @@ -4915,7 +4915,7 @@ static void mlxsw_sp_rt6_destroy(struct mlxsw_sp_rt6 *mlxsw_sp_rt6)
static bool mlxsw_sp_fib6_rt_can_mp(const struct fib6_info *rt)
{
/* RTF_CACHE routes are ignored */
return !(rt->fib6_flags & RTF_ADDRCONF) && rt->fib6_nh.fib_nh_has_gw;
return !(rt->fib6_flags & RTF_ADDRCONF) && rt->fib6_nh.fib_nh_gw_family;
}

static struct fib6_info *
Expand Down Expand Up @@ -5055,7 +5055,7 @@ static void mlxsw_sp_nexthop6_fini(struct mlxsw_sp *mlxsw_sp,
static bool mlxsw_sp_rt6_is_gateway(const struct mlxsw_sp *mlxsw_sp,
const struct fib6_info *rt)
{
return rt->fib6_nh.fib_nh_has_gw ||
return rt->fib6_nh.fib_nh_gw_family ||
mlxsw_sp_nexthop6_ipip_type(mlxsw_sp, rt, NULL);
}

Expand Down Expand Up @@ -6092,6 +6092,14 @@ static int mlxsw_sp_router_fib_event(struct notifier_block *nb,
NL_SET_ERR_MSG_MOD(info->extack, "FIB offload was aborted. Not configuring route");
return notifier_from_errno(-EINVAL);
}
if (info->family == AF_INET) {
struct fib_entry_notifier_info *fen_info = ptr;

if (fen_info->fi->fib_nh_is_v6) {
NL_SET_ERR_MSG_MOD(info->extack, "IPv6 gateway with IPv4 route is not supported");
return notifier_from_errno(-EINVAL);
}
}
break;
}

Expand Down
6 changes: 5 additions & 1 deletion drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,11 @@ mlxsw_sp_span_gretap4_route(const struct net_device *to_dev,

dev = rt->dst.dev;
*saddrp = fl4.saddr;
*daddrp = rt->rt_gateway;
if (rt->rt_gw_family == AF_INET)
*daddrp = rt->rt_gw4;
/* can not offload if route has an IPv6 gateway */
else if (rt->rt_gw_family == AF_INET6)
dev = NULL;

out:
ip_rt_put(rt);
Expand Down
9 changes: 9 additions & 0 deletions drivers/net/ethernet/rocker/rocker_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -2207,6 +2207,15 @@ static int rocker_router_fib_event(struct notifier_block *nb,
switch (event) {
case FIB_EVENT_ENTRY_ADD: /* fall through */
case FIB_EVENT_ENTRY_DEL:
if (info->family == AF_INET) {
struct fib_entry_notifier_info *fen_info = ptr;

if (fen_info->fi->fib_nh_is_v6) {
NL_SET_ERR_MSG_MOD(info->extack, "IPv6 gateway with IPv4 route is not supported");
return notifier_from_errno(-EINVAL);
}
}

memcpy(&fib_work->fen_info, ptr, sizeof(fib_work->fen_info));
/* Take referece on fib_info to prevent it from being
* freed while work is queued. Release it afterwards.
Expand Down
12 changes: 5 additions & 7 deletions drivers/net/vrf.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ static int vrf_finish_output6(struct net *net, struct sock *sk,
neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
if (!IS_ERR(neigh)) {
sock_confirm_neigh(skb, neigh);
ret = neigh_output(neigh, skb);
ret = neigh_output(neigh, skb, false);
rcu_read_unlock_bh();
return ret;
}
Expand Down Expand Up @@ -549,7 +549,7 @@ static int vrf_finish_output(struct net *net, struct sock *sk, struct sk_buff *s
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
u32 nexthop;
bool is_v6gw = false;
int ret = -EINVAL;

nf_reset(skb);
Expand All @@ -572,13 +572,11 @@ static int vrf_finish_output(struct net *net, struct sock *sk, struct sk_buff *s

rcu_read_lock_bh();

nexthop = (__force u32)rt_nexthop(rt, ip_hdr(skb)->daddr);
neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
if (unlikely(!neigh))
neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
neigh = ip_neigh_for_gw(rt, skb, &is_v6gw);
if (!IS_ERR(neigh)) {
sock_confirm_neigh(skb, neigh);
ret = neigh_output(neigh, skb);
/* if crossing protocols, can not use the cached header */
ret = neigh_output(neigh, skb, is_v6gw);
rcu_read_unlock_bh();
return ret;
}
Expand Down
2 changes: 1 addition & 1 deletion include/net/ip6_route.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ static inline bool rt6_need_strict(const struct in6_addr *daddr)
static inline bool rt6_qualify_for_ecmp(const struct fib6_info *f6i)
{
return !(f6i->fib6_flags & (RTF_ADDRCONF|RTF_DYNAMIC)) &&
f6i->fib6_nh.fib_nh_has_gw;
f6i->fib6_nh.fib_nh_gw_family;
}

void ip6_route_input(struct sk_buff *skb);
Expand Down
18 changes: 12 additions & 6 deletions include/net/ip_fib.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ struct fib_config {
u8 fc_protocol;
u8 fc_scope;
u8 fc_type;
/* 3 bytes unused */
u8 fc_gw_family;
/* 2 bytes unused */
u32 fc_table;
__be32 fc_dst;
__be32 fc_gw;
union {
__be32 fc_gw4;
struct in6_addr fc_gw6;
};
int fc_oif;
u32 fc_flags;
u32 fc_priority;
Expand Down Expand Up @@ -83,8 +87,8 @@ struct fib_nh_common {
struct lwtunnel_state *nhc_lwtstate;
unsigned char nhc_scope;
u8 nhc_family;
u8 nhc_has_gw:1,
unused:7;
u8 nhc_gw_family;

union {
__be32 ipv4;
struct in6_addr ipv6;
Expand Down Expand Up @@ -112,8 +116,7 @@ struct fib_nh {
#define fib_nh_flags nh_common.nhc_flags
#define fib_nh_lws nh_common.nhc_lwtstate
#define fib_nh_scope nh_common.nhc_scope
#define fib_nh_family nh_common.nhc_family
#define fib_nh_has_gw nh_common.nhc_has_gw
#define fib_nh_gw_family nh_common.nhc_gw_family
#define fib_nh_gw4 nh_common.nhc_gw.ipv4
#define fib_nh_gw6 nh_common.nhc_gw.ipv6
#define fib_nh_weight nh_common.nhc_weight
Expand Down Expand Up @@ -144,6 +147,7 @@ struct fib_info {
#define fib_rtt fib_metrics->metrics[RTAX_RTT-1]
#define fib_advmss fib_metrics->metrics[RTAX_ADVMSS-1]
int fib_nhs;
bool fib_nh_is_v6;
struct rcu_head rcu;
struct fib_nh fib_nh[0];
#define fib_dev fib_nh[0].fib_nh_dev
Expand Down Expand Up @@ -397,6 +401,8 @@ static inline bool fib4_rules_early_flow_dissect(struct net *net,
/* Exported by fib_frontend.c */
extern const struct nla_policy rtm_ipv4_policy[];
void ip_fib_init(void);
int fib_gw_from_via(struct fib_config *cfg, struct nlattr *nla,
struct netlink_ext_ack *extack);
__be32 fib_compute_spec_dst(struct sk_buff *skb);
bool fib_info_nh_uses_dev(struct fib_info *fi, const struct net_device *dev);
int fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst,
Expand Down
6 changes: 6 additions & 0 deletions include/net/ipv6_stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

/* structs from net/ip6_fib.h */
struct fib6_info;
struct fib6_nh;
struct fib6_config;

/* This is ugly, ideally these symbols should be built
* into the core kernel.
Expand Down Expand Up @@ -40,6 +42,10 @@ struct ipv6_stub {
u32 (*ip6_mtu_from_fib6)(struct fib6_info *f6i, struct in6_addr *daddr,
struct in6_addr *saddr);

int (*fib6_nh_init)(struct net *net, struct fib6_nh *fib6_nh,
struct fib6_config *cfg, gfp_t gfp_flags,
struct netlink_ext_ack *extack);
void (*fib6_nh_release)(struct fib6_nh *fib6_nh);
void (*udpv6_encap_enable)(void);
void (*ndisc_send_na)(struct net_device *dev, const struct in6_addr *daddr,
const struct in6_addr *solicited_addr,
Expand Down
40 changes: 40 additions & 0 deletions include/net/ndisc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#ifndef _NDISC_H
#define _NDISC_H

#include <net/ipv6_stubs.h>

/*
* ICMP codes for neighbour discovery messages
*/
Expand Down Expand Up @@ -379,6 +381,14 @@ static inline struct neighbour *__ipv6_neigh_lookup_noref(struct net_device *dev
return ___neigh_lookup_noref(&nd_tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);
}

static inline
struct neighbour *__ipv6_neigh_lookup_noref_stub(struct net_device *dev,
const void *pkey)
{
return ___neigh_lookup_noref(ipv6_stub->nd_tbl, neigh_key_eq128,
ndisc_hashfn, pkey, dev);
}

static inline struct neighbour *__ipv6_neigh_lookup(struct net_device *dev, const void *pkey)
{
struct neighbour *n;
Expand Down Expand Up @@ -409,6 +419,36 @@ static inline void __ipv6_confirm_neigh(struct net_device *dev,
rcu_read_unlock_bh();
}

static inline void __ipv6_confirm_neigh_stub(struct net_device *dev,
const void *pkey)
{
struct neighbour *n;

rcu_read_lock_bh();
n = __ipv6_neigh_lookup_noref_stub(dev, pkey);
if (n) {
unsigned long now = jiffies;

/* avoid dirtying neighbour */
if (n->confirmed != now)
n->confirmed = now;
}
rcu_read_unlock_bh();
}

/* uses ipv6_stub and is meant for use outside of IPv6 core */
static inline struct neighbour *ip_neigh_gw6(struct net_device *dev,
const void *addr)
{
struct neighbour *neigh;

neigh = __ipv6_neigh_lookup_noref_stub(dev, addr);
if (unlikely(!neigh))
neigh = __neigh_create(ipv6_stub->nd_tbl, addr, dev, false);

return neigh;
}

int ndisc_init(void);
int ndisc_late_init(void);

Expand Down
5 changes: 3 additions & 2 deletions include/net/neighbour.h
Original file line number Diff line number Diff line change
Expand Up @@ -498,11 +498,12 @@ static inline int neigh_hh_output(const struct hh_cache *hh, struct sk_buff *skb
return dev_queue_xmit(skb);
}

static inline int neigh_output(struct neighbour *n, struct sk_buff *skb)
static inline int neigh_output(struct neighbour *n, struct sk_buff *skb,
bool skip_cache)
{
const struct hh_cache *hh = &n->hh;

if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
if ((n->nud_state & NUD_CONNECTED) && hh->hh_len && !skip_cache)
return neigh_hh_output(hh, skb);
else
return n->output(n, skb);
Expand Down
43 changes: 39 additions & 4 deletions include/net/route.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include <net/flow.h>
#include <net/inet_sock.h>
#include <net/ip_fib.h>
#include <net/arp.h>
#include <net/ndisc.h>
#include <linux/in_route.h>
#include <linux/rtnetlink.h>
#include <linux/rcupdate.h>
Expand All @@ -55,12 +57,15 @@ struct rtable {
unsigned int rt_flags;
__u16 rt_type;
__u8 rt_is_input;
__u8 rt_uses_gateway;
u8 rt_gw_family;

int rt_iif;

/* Info on neighbour */
__be32 rt_gateway;
union {
__be32 rt_gw4;
struct in6_addr rt_gw6;
};

/* Miscellaneous cached information */
u32 rt_mtu_locked:1,
Expand All @@ -82,8 +87,8 @@ static inline bool rt_is_output_route(const struct rtable *rt)

static inline __be32 rt_nexthop(const struct rtable *rt, __be32 daddr)
{
if (rt->rt_gateway)
return rt->rt_gateway;
if (rt->rt_gw_family == AF_INET)
return rt->rt_gw4;
return daddr;
}

Expand Down Expand Up @@ -347,4 +352,34 @@ static inline int ip4_dst_hoplimit(const struct dst_entry *dst)
return hoplimit;
}

static inline struct neighbour *ip_neigh_gw4(struct net_device *dev,
__be32 daddr)
{
struct neighbour *neigh;

neigh = __ipv4_neigh_lookup_noref(dev, daddr);
if (unlikely(!neigh))
neigh = __neigh_create(&arp_tbl, &daddr, dev, false);

return neigh;
}

static inline struct neighbour *ip_neigh_for_gw(struct rtable *rt,
struct sk_buff *skb,
bool *is_v6gw)
{
struct net_device *dev = rt->dst.dev;
struct neighbour *neigh;

if (likely(rt->rt_gw_family == AF_INET)) {
neigh = ip_neigh_gw4(dev, rt->rt_gw4);
} else if (rt->rt_gw_family == AF_INET6) {
neigh = ip_neigh_gw6(dev, &rt->rt_gw6);
*is_v6gw = true;
} else {
neigh = ip_neigh_gw4(dev, ip_hdr(skb)->daddr);
}
return neigh;
}

#endif /* _ROUTE_H */
4 changes: 2 additions & 2 deletions include/trace/events/fib.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ TRACE_EVENT(fib_table_lookup,
__assign_str(name, dev ? dev->name : "-");

if (nhc) {
if (nhc->nhc_family == AF_INET) {
if (nhc->nhc_gw_family == AF_INET) {
p32 = (__be32 *) __entry->gw4;
*p32 = nhc->nhc_gw.ipv4;

in6 = (struct in6_addr *)__entry->gw6;
*in6 = in6_zero;
} else if (nhc->nhc_family == AF_INET6) {
} else if (nhc->nhc_gw_family == AF_INET6) {
p32 = (__be32 *) __entry->gw4;
*p32 = 0;

Expand Down
Loading

0 comments on commit 0ed8c3d

Please sign in to comment.