Skip to content

Commit

Permalink
Merge branch 'blackhole-device-to-invalidate-dst'
Browse files Browse the repository at this point in the history
Mahesh Bandewar says:

====================
blackhole device to invalidate dst

When we invalidate dst or mark it "dead", we assign 'lo' to
dst->dev. First of all this assignment is racy and more over,
it has MTU implications.

The standard dev MTU is 1500 while the Loopback MTU is 64k. TCP
code when dereferencing the dst don't check if the dst is valid
or not. TCP when dereferencing a dead-dst while negotiating a
new connection, may use dst device which is 'lo' instead of
using the correct device. Consider the following scenario:

A SYN arrives on an interface and tcp-layer while processing
SYNACK finds a dst and associates it with SYNACK skb. Now before
skb gets passed to L3 for processing, if that dst gets "dead"
(because of the virtual device getting disappeared & then reappeared),
the 'lo' gets assigned to that dst (lo MTU = 64k). Let's assume
the SYN has ADV_MSS set as 9k while the output device through
which this SYNACK is going to go out has standard MTU of 1500.
The MTU check during the route check passes since MIN(9K, 64K)
is 9k and TCP successfully negotiates 9k MSS. The subsequent
data packet; bigger in size gets passed to the device and it
won't be marked as GSO since the assumed MTU of the device is
9k.

This either crashes the NIC and we have seen fixes that went
into drivers to handle this scenario. 8914a59 ('bnx2x:
disable GSO where gso_size is too big for hardware') and
2b16f04 ('net: create skb_gso_validate_mac_len()') and
with those fixes TCP eventually recovers but not before
few dropped segments.

Well, I'm not a TCP expert and though we have experienced
these corner cases in our environment, I could not reproduce
this case reliably in my test setup to try this fix myself.
However, Michael Chan <michael.chan@broadcom.com> had a setup
where these fixes helped him mitigate the issue and not cause
the crash.

The idea here is to not alter the data-path with additional
locks or smb()/rmb() barriers to avoid racy assignments but
to create a new device that has really low MTU that has
.ndo_start_xmit essentially a kfree_skb(). Make use of this
device instead of 'lo' when marking the dst dead.

First patch implements the blackhole device and second
patch uses it in IPv4 and IPv6 stack while the third patch
is the self test that ensures the sanity of this device.

v1->v2
  fixed the self-test patch to handle the conflict

v2 -> v3
  fixed Kconfig text/string.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Jul 2, 2019
2 parents 8909783 + 509e56b commit 2a8d8e0
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 14 deletions.
76 changes: 67 additions & 9 deletions drivers/net/loopback.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@
#include <net/net_namespace.h>
#include <linux/u64_stats_sync.h>

/* blackhole_netdev - a device used for dsts that are marked expired!
* This is global device (instead of per-net-ns) since it's not needed
* to be per-ns and gets initialized at boot time.
*/
struct net_device *blackhole_netdev;
EXPORT_SYMBOL(blackhole_netdev);

/* The higher levels take care of making this non-reentrant (it's
* called with bh's disabled).
*/
Expand Down Expand Up @@ -150,12 +157,14 @@ static const struct net_device_ops loopback_ops = {
.ndo_set_mac_address = eth_mac_addr,
};

/* The loopback device is special. There is only one instance
* per network namespace.
*/
static void loopback_setup(struct net_device *dev)
static void gen_lo_setup(struct net_device *dev,
unsigned int mtu,
const struct ethtool_ops *eth_ops,
const struct header_ops *hdr_ops,
const struct net_device_ops *dev_ops,
void (*dev_destructor)(struct net_device *dev))
{
dev->mtu = 64 * 1024;
dev->mtu = mtu;
dev->hard_header_len = ETH_HLEN; /* 14 */
dev->min_header_len = ETH_HLEN; /* 14 */
dev->addr_len = ETH_ALEN; /* 6 */
Expand All @@ -174,11 +183,20 @@ static void loopback_setup(struct net_device *dev)
| NETIF_F_NETNS_LOCAL
| NETIF_F_VLAN_CHALLENGED
| NETIF_F_LOOPBACK;
dev->ethtool_ops = &loopback_ethtool_ops;
dev->header_ops = &eth_header_ops;
dev->netdev_ops = &loopback_ops;
dev->ethtool_ops = eth_ops;
dev->header_ops = hdr_ops;
dev->netdev_ops = dev_ops;
dev->needs_free_netdev = true;
dev->priv_destructor = loopback_dev_free;
dev->priv_destructor = dev_destructor;
}

/* The loopback device is special. There is only one instance
* per network namespace.
*/
static void loopback_setup(struct net_device *dev)
{
gen_lo_setup(dev, (64 * 1024), &loopback_ethtool_ops, &eth_header_ops,
&loopback_ops, loopback_dev_free);
}

/* Setup and register the loopback device. */
Expand Down Expand Up @@ -213,3 +231,43 @@ static __net_init int loopback_net_init(struct net *net)
struct pernet_operations __net_initdata loopback_net_ops = {
.init = loopback_net_init,
};

/* blackhole netdevice */
static netdev_tx_t blackhole_netdev_xmit(struct sk_buff *skb,
struct net_device *dev)
{
kfree_skb(skb);
net_warn_ratelimited("%s(): Dropping skb.\n", __func__);
return NETDEV_TX_OK;
}

static const struct net_device_ops blackhole_netdev_ops = {
.ndo_start_xmit = blackhole_netdev_xmit,
};

/* This is a dst-dummy device used specifically for invalidated
* DSTs and unlike loopback, this is not per-ns.
*/
static void blackhole_netdev_setup(struct net_device *dev)
{
gen_lo_setup(dev, ETH_MIN_MTU, NULL, NULL, &blackhole_netdev_ops, NULL);
}

/* Setup and register the blackhole_netdev. */
static int __init blackhole_netdev_init(void)
{
blackhole_netdev = alloc_netdev(0, "blackhole_dev", NET_NAME_UNKNOWN,
blackhole_netdev_setup);
if (!blackhole_netdev)
return -ENOMEM;

dev_init_scheduler(blackhole_netdev);
dev_activate(blackhole_netdev);

blackhole_netdev->flags |= IFF_UP | IFF_RUNNING;
dev_net_set(blackhole_netdev, &init_net);

return 0;
}

device_initcall(blackhole_netdev_init);
2 changes: 2 additions & 0 deletions include/linux/netdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -4870,4 +4870,6 @@ do { \
#define PTYPE_HASH_SIZE (16)
#define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)

extern struct net_device *blackhole_netdev;

#endif /* _LINUX_NETDEVICE_H */
9 changes: 9 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,15 @@ config TEST_BPF

If unsure, say N.

config TEST_BLACKHOLE_DEV
tristate "Test blackhole netdev functionality"
depends on m && NET
help
This builds the "test_blackhole_dev" module that validates the
data path through this blackhole netdev.

If unsure, say N.

config FIND_BIT_BENCHMARK
tristate "Test find_bit functions"
help
Expand Down
1 change: 1 addition & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o
obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
obj-$(CONFIG_TEST_STACKINIT) += test_stackinit.o
obj-$(CONFIG_TEST_BLACKHOLE_DEV) += test_blackhole_dev.o

obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/

Expand Down
100 changes: 100 additions & 0 deletions lib/test_blackhole_dev.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This module tests the blackhole_dev that is created during the
* net subsystem initialization. The test this module performs is
* by injecting an skb into the stack with skb->dev as the
* blackhole_dev and expects kernel to behave in a sane manner
* (in other words, *not crash*)!
*
* Copyright (c) 2018, Mahesh Bandewar <maheshb@google.com>
*/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/udp.h>
#include <linux/ipv6.h>

#include <net/dst.h>

#define SKB_SIZE 256
#define HEAD_SIZE (14+40+8) /* Ether + IPv6 + UDP */
#define TAIL_SIZE 32 /* random tail-room */

#define UDP_PORT 1234

static int __init test_blackholedev_init(void)
{
struct ipv6hdr *ip6h;
struct sk_buff *skb;
struct ethhdr *ethh;
struct udphdr *uh;
int data_len;
int ret;

skb = alloc_skb(SKB_SIZE, GFP_KERNEL);
if (!skb)
return -ENOMEM;

/* Reserve head-room for the headers */
skb_reserve(skb, HEAD_SIZE);

/* Add data to the skb */
data_len = SKB_SIZE - (HEAD_SIZE + TAIL_SIZE);
memset(__skb_put(skb, data_len), 0xf, data_len);

/* Add protocol data */
/* (Transport) UDP */
uh = (struct udphdr *)skb_push(skb, sizeof(struct udphdr));
skb_set_transport_header(skb, 0);
uh->source = uh->dest = htons(UDP_PORT);
uh->len = htons(data_len);
uh->check = 0;
/* (Network) IPv6 */
ip6h = (struct ipv6hdr *)skb_push(skb, sizeof(struct ipv6hdr));
skb_set_network_header(skb, 0);
ip6h->hop_limit = 32;
ip6h->payload_len = data_len + sizeof(struct udphdr);
ip6h->nexthdr = IPPROTO_UDP;
ip6h->saddr = in6addr_loopback;
ip6h->daddr = in6addr_loopback;
/* Ether */
ethh = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr));
skb_set_mac_header(skb, 0);

skb->protocol = htons(ETH_P_IPV6);
skb->pkt_type = PACKET_HOST;
skb->dev = blackhole_netdev;

/* Now attempt to send the packet */
ret = dev_queue_xmit(skb);

switch (ret) {
case NET_XMIT_SUCCESS:
pr_warn("dev_queue_xmit() returned NET_XMIT_SUCCESS\n");
break;
case NET_XMIT_DROP:
pr_warn("dev_queue_xmit() returned NET_XMIT_DROP\n");
break;
case NET_XMIT_CN:
pr_warn("dev_queue_xmit() returned NET_XMIT_CN\n");
break;
default:
pr_err("dev_queue_xmit() returned UNKNOWN(%d)\n", ret);
}

return 0;
}

static void __exit test_blackholedev_exit(void)
{
pr_warn("test_blackholedev module terminating.\n");
}

module_init(test_blackholedev_init);
module_exit(test_blackholedev_exit);

MODULE_AUTHOR("Mahesh Bandewar <maheshb@google.com>");
MODULE_LICENSE("GPL");
2 changes: 1 addition & 1 deletion net/core/dst.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ void dst_dev_put(struct dst_entry *dst)
dst->ops->ifdown(dst, dev, true);
dst->input = dst_discard;
dst->output = dst_discard_out;
dst->dev = dev_net(dst->dev)->loopback_dev;
dst->dev = blackhole_netdev;
dev_hold(dst->dev);
dev_put(dev);
}
Expand Down
3 changes: 1 addition & 2 deletions net/ipv4/route.c
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,6 @@ static void ipv4_dst_destroy(struct dst_entry *dst)

void rt_flush_dev(struct net_device *dev)
{
struct net *net = dev_net(dev);
struct rtable *rt;
int cpu;

Expand All @@ -1543,7 +1542,7 @@ void rt_flush_dev(struct net_device *dev)
list_for_each_entry(rt, &ul->head, rt_uncached) {
if (rt->dst.dev != dev)
continue;
rt->dst.dev = net->loopback_dev;
rt->dst.dev = blackhole_netdev;
dev_hold(rt->dst.dev);
dev_put(dev);
}
Expand Down
2 changes: 1 addition & 1 deletion net/ipv6/route.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ static void rt6_uncached_list_flush_dev(struct net *net, struct net_device *dev)
}

if (rt_dev == dev) {
rt->dst.dev = loopback_dev;
rt->dst.dev = blackhole_netdev;
dev_hold(rt->dst.dev);
dev_put(rt_dev);
}
Expand Down
2 changes: 1 addition & 1 deletion tools/testing/selftests/net/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CFLAGS = -Wall -Wl,--no-as-needed -O2 -g
CFLAGS += -I../../../../usr/include/

TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh \
rtnetlink.sh xfrm_policy.sh
rtnetlink.sh xfrm_policy.sh test_blackhole_dev.sh
TEST_PROGS += fib_tests.sh fib-onlink-tests.sh pmtu.sh udpgso.sh ip_defrag.sh
TEST_PROGS += udpgso_bench.sh fib_rule_tests.sh msg_zerocopy.sh psock_snd.sh
TEST_PROGS += udpgro_bench.sh udpgro.sh test_vxlan_under_vrf.sh reuseport_addr_any.sh
Expand Down
1 change: 1 addition & 0 deletions tools/testing/selftests/net/config
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ CONFIG_NFT_CHAIN_NAT_IPV6=m
CONFIG_NFT_CHAIN_NAT_IPV4=m
CONFIG_NET_SCH_FQ=m
CONFIG_NET_SCH_ETF=m
CONFIG_TEST_BLACKHOLE_DEV=m
11 changes: 11 additions & 0 deletions tools/testing/selftests/net/test_blackhole_dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs blackhole-dev test using blackhole-dev kernel module

if /sbin/modprobe -q test_blackhole_dev ; then
/sbin/modprobe -q -r test_blackhole_dev;
echo "test_blackhole_dev: ok";
else
echo "test_blackhole_dev: [FAIL]";
exit 1;
fi

0 comments on commit 2a8d8e0

Please sign in to comment.