Commit 2e2d5d76 authored by David S. Miller's avatar David S. Miller

Merge branch 'rtnetlink-fix-initial-rtnl-pushdown-fallout'

Florian Westphal says:

====================
rtnetlink: fix initial rtnl pushdown fallout

This series fixes various bugs and splats reported since the
allow-handler-to-run-with-no-rtnl series went in.

Last patch adds a script that can be used to add further
tests in case more bugs are reported.
In case you prefer reverting the original series instead of
fixing fallout I can resend this patch on its own.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 2d571645 33b01b7b
...@@ -172,7 +172,7 @@ int __rtnl_register(int protocol, int msgtype, ...@@ -172,7 +172,7 @@ int __rtnl_register(int protocol, int msgtype,
BUG_ON(protocol < 0 || protocol > RTNL_FAMILY_MAX); BUG_ON(protocol < 0 || protocol > RTNL_FAMILY_MAX);
msgindex = rtm_msgindex(msgtype); msgindex = rtm_msgindex(msgtype);
tab = rcu_dereference(rtnl_msg_handlers[protocol]); tab = rcu_dereference_raw(rtnl_msg_handlers[protocol]);
if (tab == NULL) { if (tab == NULL) {
tab = kcalloc(RTM_NR_MSGTYPES, sizeof(*tab), GFP_KERNEL); tab = kcalloc(RTM_NR_MSGTYPES, sizeof(*tab), GFP_KERNEL);
if (tab == NULL) if (tab == NULL)
...@@ -262,7 +262,7 @@ void rtnl_unregister_all(int protocol) ...@@ -262,7 +262,7 @@ void rtnl_unregister_all(int protocol)
synchronize_net(); synchronize_net();
while (refcount_read(&rtnl_msg_handlers_ref[protocol]) > 0) while (refcount_read(&rtnl_msg_handlers_ref[protocol]) > 1)
schedule(); schedule();
kfree(handlers); kfree(handlers);
} }
...@@ -402,16 +402,24 @@ static size_t rtnl_link_get_slave_info_data_size(const struct net_device *dev) ...@@ -402,16 +402,24 @@ static size_t rtnl_link_get_slave_info_data_size(const struct net_device *dev)
{ {
struct net_device *master_dev; struct net_device *master_dev;
const struct rtnl_link_ops *ops; const struct rtnl_link_ops *ops;
size_t size = 0;
master_dev = netdev_master_upper_dev_get((struct net_device *) dev); rcu_read_lock();
master_dev = netdev_master_upper_dev_get_rcu((struct net_device *)dev);
if (!master_dev) if (!master_dev)
return 0; goto out;
ops = master_dev->rtnl_link_ops; ops = master_dev->rtnl_link_ops;
if (!ops || !ops->get_slave_size) if (!ops || !ops->get_slave_size)
return 0; goto out;
/* IFLA_INFO_SLAVE_DATA + nested data */ /* IFLA_INFO_SLAVE_DATA + nested data */
return nla_total_size(sizeof(struct nlattr)) + size = nla_total_size(sizeof(struct nlattr)) +
ops->get_slave_size(master_dev, dev); ops->get_slave_size(master_dev, dev);
out:
rcu_read_unlock();
return size;
} }
static size_t rtnl_link_get_size(const struct net_device *dev) static size_t rtnl_link_get_size(const struct net_device *dev)
...@@ -4167,7 +4175,7 @@ static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -4167,7 +4175,7 @@ static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
if (kind != 2 && !netlink_net_capable(skb, CAP_NET_ADMIN)) if (kind != 2 && !netlink_net_capable(skb, CAP_NET_ADMIN))
return -EPERM; return -EPERM;
if (family > ARRAY_SIZE(rtnl_msg_handlers)) if (family >= ARRAY_SIZE(rtnl_msg_handlers))
family = PF_UNSPEC; family = PF_UNSPEC;
rcu_read_lock(); rcu_read_lock();
...@@ -4196,7 +4204,7 @@ static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -4196,7 +4204,7 @@ static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
refcount_inc(&rtnl_msg_handlers_ref[family]); refcount_inc(&rtnl_msg_handlers_ref[family]);
if (type == RTM_GETLINK) if (type == RTM_GETLINK - RTM_BASE)
min_dump_alloc = rtnl_calcit(skb, nlh); min_dump_alloc = rtnl_calcit(skb, nlh);
rcu_read_unlock(); rcu_read_unlock();
...@@ -4213,6 +4221,12 @@ static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -4213,6 +4221,12 @@ static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
return err; return err;
} }
doit = READ_ONCE(handlers[type].doit);
if (!doit) {
family = PF_UNSPEC;
handlers = rcu_dereference(rtnl_msg_handlers[family]);
}
flags = READ_ONCE(handlers[type].flags); flags = READ_ONCE(handlers[type].flags);
if (flags & RTNL_FLAG_DOIT_UNLOCKED) { if (flags & RTNL_FLAG_DOIT_UNLOCKED) {
refcount_inc(&rtnl_msg_handlers_ref[family]); refcount_inc(&rtnl_msg_handlers_ref[family]);
...@@ -4316,6 +4330,11 @@ static struct pernet_operations rtnetlink_net_ops = { ...@@ -4316,6 +4330,11 @@ static struct pernet_operations rtnetlink_net_ops = {
void __init rtnetlink_init(void) void __init rtnetlink_init(void)
{ {
int i;
for (i = 0; i < ARRAY_SIZE(rtnl_msg_handlers_ref); i++)
refcount_set(&rtnl_msg_handlers_ref[i], 1);
if (register_pernet_subsys(&rtnetlink_net_ops)) if (register_pernet_subsys(&rtnetlink_net_ops))
panic("rtnetlink_init: cannot initialize rtnetlink\n"); panic("rtnetlink_init: cannot initialize rtnetlink\n");
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
CFLAGS = -Wall -Wl,--no-as-needed -O2 -g CFLAGS = -Wall -Wl,--no-as-needed -O2 -g
CFLAGS += -I../../../../usr/include/ CFLAGS += -I../../../../usr/include/
TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
TEST_GEN_FILES = socket TEST_GEN_FILES = socket
TEST_GEN_FILES += psock_fanout psock_tpacket TEST_GEN_FILES += psock_fanout psock_tpacket
TEST_GEN_FILES += reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa TEST_GEN_FILES += reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
......
#!/bin/sh
#
# This test is for checking rtnetlink callpaths, and get as much coverage as possible.
#
# set -e
devdummy="test-dummy0"
ret=0
# set global exit status, but never reset nonzero one.
check_err()
{
if [ $ret -eq 0 ]; then
ret=$1
fi
}
kci_add_dummy()
{
ip link add name "$devdummy" type dummy
check_err $?
ip link set "$devdummy" up
check_err $?
}
kci_del_dummy()
{
ip link del dev "$devdummy"
check_err $?
}
# add a bridge with vlans on top
kci_test_bridge()
{
devbr="test-br0"
vlandev="testbr-vlan1"
ret=0
ip link add name "$devbr" type bridge
check_err $?
ip link set dev "$devdummy" master "$devbr"
check_err $?
ip link set "$devbr" up
check_err $?
ip link add link "$devbr" name "$vlandev" type vlan id 1
check_err $?
ip addr add dev "$vlandev" 10.200.7.23/30
check_err $?
ip -6 addr add dev "$vlandev" dead:42::1234/64
check_err $?
ip -d link > /dev/null
check_err $?
ip r s t all > /dev/null
check_err $?
ip -6 addr del dev "$vlandev" dead:42::1234/64
check_err $?
ip link del dev "$vlandev"
check_err $?
ip link del dev "$devbr"
check_err $?
if [ $ret -ne 0 ];then
echo "FAIL: bridge setup"
return 1
fi
echo "PASS: bridge setup"
}
kci_test_gre()
{
gredev=neta
rem=10.42.42.1
loc=10.0.0.1
ret=0
ip tunnel add $gredev mode gre remote $rem local $loc ttl 1
check_err $?
ip link set $gredev up
check_err $?
ip addr add 10.23.7.10 dev $gredev
check_err $?
ip route add 10.23.8.0/30 dev $gredev
check_err $?
ip addr add dev "$devdummy" 10.23.7.11/24
check_err $?
ip link > /dev/null
check_err $?
ip addr > /dev/null
check_err $?
ip addr del dev "$devdummy" 10.23.7.11/24
check_err $?
ip link del $gredev
check_err $?
if [ $ret -ne 0 ];then
echo "FAIL: gre tunnel endpoint"
return 1
fi
echo "PASS: gre tunnel endpoint"
}
# tc uses rtnetlink too, for full tc testing
# please see tools/testing/selftests/tc-testing.
kci_test_tc()
{
dev=lo
ret=0
tc qdisc add dev "$dev" root handle 1: htb
check_err $?
tc class add dev "$dev" parent 1: classid 1:10 htb rate 1mbit
check_err $?
tc filter add dev "$dev" parent 1:0 prio 5 handle ffe: protocol ip u32 divisor 256
check_err $?
tc filter add dev "$dev" parent 1:0 prio 5 handle ffd: protocol ip u32 divisor 256
check_err $?
tc filter add dev "$dev" parent 1:0 prio 5 handle ffc: protocol ip u32 divisor 256
check_err $?
tc filter add dev "$dev" protocol ip parent 1: prio 5 handle ffe:2:3 u32 ht ffe:2: match ip src 10.0.0.3 flowid 1:10
check_err $?
tc filter add dev "$dev" protocol ip parent 1: prio 5 handle ffe:2:2 u32 ht ffe:2: match ip src 10.0.0.2 flowid 1:10
check_err $?
tc filter show dev "$dev" parent 1:0 > /dev/null
check_err $?
tc filter del dev "$dev" protocol ip parent 1: prio 5 handle ffe:2:3 u32
check_err $?
tc filter show dev "$dev" parent 1:0 > /dev/null
check_err $?
tc qdisc del dev "$dev" root handle 1: htb
check_err $?
if [ $ret -ne 0 ];then
echo "FAIL: tc htb hierarchy"
return 1
fi
echo "PASS: tc htb hierarchy"
}
kci_test_polrouting()
{
ret=0
ip rule add fwmark 1 lookup 100
check_err $?
ip route add local 0.0.0.0/0 dev lo table 100
check_err $?
ip r s t all > /dev/null
check_err $?
ip rule del fwmark 1 lookup 100
check_err $?
ip route del local 0.0.0.0/0 dev lo table 100
check_err $?
if [ $ret -ne 0 ];then
echo "FAIL: policy route test"
return 1
fi
echo "PASS: policy routing"
}
kci_test_rtnl()
{
kci_add_dummy
if [ $ret -ne 0 ];then
echo "FAIL: cannot add dummy interface"
return 1
fi
kci_test_polrouting
kci_test_tc
kci_test_gre
kci_test_bridge
kci_del_dummy
}
#check for needed privileges
if [ "$(id -u)" -ne 0 ];then
echo "SKIP: Need root privileges"
exit 0
fi
for x in ip tc;do
$x -Version 2>/dev/null >/dev/null
if [ $? -ne 0 ];then
echo "SKIP: Could not run test without the $x tool"
exit 0
fi
done
kci_test_rtnl
exit $ret
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment