| From foo@baz Wed Aug 22 09:42:09 CEST 2018 |
| From: Hangbin Liu <liuhangbin@gmail.com> |
| Date: Sun, 1 Jul 2018 16:21:21 +0800 |
| Subject: ipvlan: call dev_change_flags when ipvlan mode is reset |
| |
| From: Hangbin Liu <liuhangbin@gmail.com> |
| |
| [ Upstream commit 5dc2d3996a8b221c20dd0900bdad45031a572530 ] |
| |
| After we change the ipvlan mode from l3 to l2, or vice versa, we only |
| reset IFF_NOARP flag, but don't flush the ARP table cache, which will |
| cause eth->h_dest to be equal to eth->h_source in ipvlan_xmit_mode_l2(). |
| Then the message will not come out of host. |
| |
| Here is the reproducer on local host: |
| |
| ip link set eth1 up |
| ip addr add 192.168.1.1/24 dev eth1 |
| ip link add link eth1 ipvlan1 type ipvlan mode l3 |
| |
| ip netns add net1 |
| ip link set ipvlan1 netns net1 |
| ip netns exec net1 ip link set ipvlan1 up |
| ip netns exec net1 ip addr add 192.168.2.1/24 dev ipvlan1 |
| |
| ip route add 192.168.2.0/24 via 192.168.1.2 |
| ping 192.168.2.2 -c 2 |
| |
| ip netns exec net1 ip link set ipvlan1 type ipvlan mode l2 |
| ping 192.168.2.2 -c 2 |
| |
| Add the same configuration on remote host. After we set the mode to l2, |
| we could find that the src/dst MAC addresses are the same on eth1: |
| |
| 21:26:06.648565 00:b7:13:ad:d3:05 > 00:b7:13:ad:d3:05, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 58356, offset 0, flags [DF], proto ICMP (1), length 84) |
| 192.168.2.1 > 192.168.2.2: ICMP echo request, id 22686, seq 1, length 64 |
| |
| Fix this by calling dev_change_flags(), which will call netdevice notifier |
| with flag change info. |
| |
| v2: |
| a) As pointed out by Wang Cong, check return value for dev_change_flags() when |
| change dev flags. |
| b) As suggested by Stefano and Sabrina, move flags setting before l3mdev_ops. |
| So we don't need to redo ipvlan_{, un}register_nf_hook() again in err path. |
| |
| Reported-by: Jianlin Shi <jishi@redhat.com> |
| Reviewed-by: Stefano Brivio <sbrivio@redhat.com> |
| Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> |
| Fixes: 2ad7bf3638411 ("ipvlan: Initial check-in of the IPVLAN driver.") |
| Signed-off-by: Hangbin Liu <liuhangbin@gmail.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Sasha Levin <alexander.levin@microsoft.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/net/ipvlan/ipvlan_main.c | 36 ++++++++++++++++++++++++++++-------- |
| 1 file changed, 28 insertions(+), 8 deletions(-) |
| |
| --- a/drivers/net/ipvlan/ipvlan_main.c |
| +++ b/drivers/net/ipvlan/ipvlan_main.c |
| @@ -63,10 +63,23 @@ static int ipvlan_set_port_mode(struct i |
| { |
| struct ipvl_dev *ipvlan; |
| struct net_device *mdev = port->dev; |
| - int err = 0; |
| + unsigned int flags; |
| + int err; |
| |
| ASSERT_RTNL(); |
| if (port->mode != nval) { |
| + list_for_each_entry(ipvlan, &port->ipvlans, pnode) { |
| + flags = ipvlan->dev->flags; |
| + if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) { |
| + err = dev_change_flags(ipvlan->dev, |
| + flags | IFF_NOARP); |
| + } else { |
| + err = dev_change_flags(ipvlan->dev, |
| + flags & ~IFF_NOARP); |
| + } |
| + if (unlikely(err)) |
| + goto fail; |
| + } |
| if (nval == IPVLAN_MODE_L3S) { |
| /* New mode is L3S */ |
| err = ipvlan_register_nf_hook(); |
| @@ -74,21 +87,28 @@ static int ipvlan_set_port_mode(struct i |
| mdev->l3mdev_ops = &ipvl_l3mdev_ops; |
| mdev->priv_flags |= IFF_L3MDEV_MASTER; |
| } else |
| - return err; |
| + goto fail; |
| } else if (port->mode == IPVLAN_MODE_L3S) { |
| /* Old mode was L3S */ |
| mdev->priv_flags &= ~IFF_L3MDEV_MASTER; |
| ipvlan_unregister_nf_hook(); |
| mdev->l3mdev_ops = NULL; |
| } |
| - list_for_each_entry(ipvlan, &port->ipvlans, pnode) { |
| - if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) |
| - ipvlan->dev->flags |= IFF_NOARP; |
| - else |
| - ipvlan->dev->flags &= ~IFF_NOARP; |
| - } |
| port->mode = nval; |
| } |
| + return 0; |
| + |
| +fail: |
| + /* Undo the flags changes that have been done so far. */ |
| + list_for_each_entry_continue_reverse(ipvlan, &port->ipvlans, pnode) { |
| + flags = ipvlan->dev->flags; |
| + if (port->mode == IPVLAN_MODE_L3 || |
| + port->mode == IPVLAN_MODE_L3S) |
| + dev_change_flags(ipvlan->dev, flags | IFF_NOARP); |
| + else |
| + dev_change_flags(ipvlan->dev, flags & ~IFF_NOARP); |
| + } |
| + |
| return err; |
| } |
| |