Skip to contentSkip to conversation

ipvlan: Initial check-in of the IPVLAN driver.

PATCH In review Mahesh Bandewar 6 participants 15 messages Nov 11, 2014 – Nov 13, 2014

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 ipvlan: Initial check-in of the IPVLAN driver.

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

This driver was developed keeping the namespace use-case in mind. Hence most of the examples given here take that as the base setup where main-device belongs to the default-ns and virtual devices are assigned to the additional namespaces.

The device operates in two different modes and the difference in these two modes in primarily in the TX side.

(a) L2 mode : In this mode, the device behaves as a L2 device. TX processing upto L2 happens on the stack of the virtual device associated with (namespace). Packets are switched after that into the main device (default-ns) and queued for xmit.

RX processing is simple and all multicast, broadcast (if applicable), and unicast belonging to the address(es) are delivered to the virtual devices.

(b) L3 mode : In this mode, the device behaves like a L3 device. TX processing upto L3 happens on the stack of the virtual device associated with (namespace). Packets are switched to the main-device (default-ns) for the L2 processing. Hence the routing table of the default-ns will be used in this mode.

RX processins is somewhat similar to the L2 mode except that in this mode only Unicast packets are delivered to the virtual device while main-dev will handle all other packets.

The devices can be added using the "ip" command from the iproute2 package -

ip link add link <master> <virtual> type ipvlan mode [ l2 | l3 ]

Cc Eric Dumazet <[email protected]>
Cc Maciej Żenczykowski <[email protected]>
Cc Laurent Chavey <[email protected]>
Cc Tim Hockin <[email protected]>
Cc Brandon Philips <[email protected]>
Cc Pavel Emelianov <[email protected]>
---
 Documentation/networking/ipvlan.txt | 100 +++++
 drivers/net/Kconfig                 |  18 +
 drivers/net/Makefile                |   1 +
 drivers/net/ipvlan/Makefile         |   7 +
 drivers/net/ipvlan/ipvlan.h         | 164 +++++++
 drivers/net/ipvlan/ipvlan_core.c    | 634 +++++++++++++++++++++++++++
 drivers/net/ipvlan/ipvlan_main.c    | 828 ++++++++++++++++++++++++++++++++++++
 drivers/net/ipvlan/ipvlan_sysfs.c   | 119 ++++++
 include/linux/netdevice.h           |   4 +
 include/uapi/linux/if_link.h        |  15 +
 10 files changed, 1890 insertions(+)
 create mode 100644 Documentation/networking/ipvlan.txt
 create mode 100644 drivers/net/ipvlan/Makefile
 create mode 100644 drivers/net/ipvlan/ipvlan.h
 create mode 100644 drivers/net/ipvlan/ipvlan_core.c
 create mode 100644 drivers/net/ipvlan/ipvlan_main.c
 create mode 100644 drivers/net/ipvlan/ipvlan_sysfs.c
Documentation/networking/ipvlan.txt new file +100 -0
new file mode 100644
index 000000000000..7eeb90eb8b96
@@ -0,0 +1,100 @@
+
+                            IPVLAN Driver HOWTO
+
+Initial Release:
+	Mahesh Bandewar <maheshb AT google.com>
+
+1. Introduction:
+	This is conceptually very similar to the macvlan driver with one major
+exception of using L3 for mux-ing /demux-ing among slaves. This property makes
+the master device share the L2 with it's slave devices. I have developed this
+driver in conjuntion with network namespaces and not sure if there is use case
+outside of it.
+
+
+2. Building and Installation:
+	In order to build the driver, please select the config item CONFIG_IPVLAN.
+The driver can be built into the kernel (CONFIG_IPVLAN=y) or as a module
+(CONFIG_IPVLAN=m).
+
+
+3. Configuration:
+	There are no module parameters for this driver and it can be configured
+using IProute2/ip utility.
+
+	ip link add link <master-dev> <slave-dev> type ipvlan mode { l2 | L3 }
+
+	e.g. ip link add link ipvl0 eth0 type ipvlan mode l2
+
+
+4. Operating modes:
+	IPvlan has two modes of operation - L2 and L3. For a given master device,
+you can select one of these two modes and all slaves on that master will
+operate in the same (slected) mode. The RX mode is almost identical except 
+that in L3 mode the slaves wont receive any multicast / broadcast traffic.
+
+4.1 L2 mode:
+	In this mode TX processing happens on the stack instance attached to the
+slave device and packets are switched and queued to the master device to send
+out. In this mode the slaves will RX/TX multicast and broadcast (if applicable)
+as well.
+
+4.2 L3 mode:
+	In this mode TX processing upto L3 happens on the stack instance attached
+to the slave device and packets are switched to the stack instance of the 
+master device for the L2 processing and routing from that instance will be
+used before packets are queued on the outbound device. In this mode the slaves
+will not receive nor can send multicast / broadcast traffic.
+
+
+5. Sysfs interface:
+	Currently the mode of operation is available at -
+		 /sys/class/net/<master>/ipvlan/mode
+The value can be 0 or 1; where 0 :=> L2, 1 := L3 mode 
+
+
+6. Example configuration:
+
+  +=============================================================+
+  |  Host: host1                                                |
+  |                                                             |
+  |   +----------------------+      +----------------------+    |
+  |   |   NS:ns0             |      |  NS:ns1              |    |
+  |   |                      |      |                      |    |
+  |   |                      |      |                      |    |
+  |   |        ipvl0         |      |         ipvl1        |    |
+  |   +----------#-----------+      +-----------#----------+    |
+  |              #                              #               |
+  |              ################################               |
+  |                              # eth0                         |
+  +==============================#==============================+
+
+
+	(a) Create two network namespaces - ns0, ns1
+		ip netns add ns0
+		ip netns add ns1
+
+	(b) Create two ipvlan slaves on eth0 (master device)
+		ip link add link eth0 ipvl0 type ipvlan mode l2
+		ip link add link eth0 ipvl1 type ipvlan mode l2
+
+	(c) Assign slaves to the respective network namespaces
+		ip link set dev ipvl0 netns ns0
+		ip link set dev ipvl1 netns ns1
+
+	(d) Now switch to the namespace (ns0 or ns1) to configure the slave devices
+		- For ns0
+			(1) ip netns exec ns0 bash
+			(2) ip link set dev ipvl0 up
+			(3) ip link set dev lo up
+			(4) ip -4 addr add 127.0.0.1 dev lo
+			(5) ip -4 addr add $IPADDR dev ipvl0
+			(6) ip -4 route add default via $ROUTER dev ipvl0
+		- For ns1
+			(1) ip netns exec ns1 bash
+			(2) ip link set dev ipvl1 up
+			(3) ip link set dev lo up
+			(4) ip -4 addr add 127.0.0.1 dev lo
+			(5) ip -4 addr add $IPADDR dev ipvl1
+			(6) ip -4 route add default via $ROUTER dev ipvl1
+		
drivers/net/Kconfig +18 -0
index f9009be3f307..b6d64f546574 100644
@@ -145,6 +145,24 @@ config MACVTAP
 	  To compile this driver as a module, choose M here: the module
 	  will be called macvtap.
 
+
+config IPVLAN
+    tristate "IP-VLAN support"
+    ---help---
+      This allows one to create virtual devices off of a main interface
+      and packets will be delivered based on the dest L3 (IPv6/IPv4 addr)
+      on packets. All interfaces (including the main interface) share L2
+      making it transparent to the connected L2 switch.
+
+      Ipvlan devices can be added using the "ip" command from the
+      iproute2 package starting with the iproute2-X.Y.ZZ release:
+
+      "ip link add link <main-dev> [ NAME ] type ipvlan"
+
+      To compile this driver as a module, choose M here: the module
+      will be called ipvlan.
+
+
 config VXLAN
        tristate "Virtual eXtensible Local Area Network (VXLAN)"
        depends on INET
drivers/net/Makefile +1 -0
index 61aefdd1e173..e25fdd7d905e 100644
@@ -6,6 +6,7 @@
 # Networking Core Drivers
 #
 obj-$(CONFIG_BONDING) += bonding/
+obj-$(CONFIG_IPVLAN) += ipvlan/
 obj-$(CONFIG_DUMMY) += dummy.o
 obj-$(CONFIG_EQUALIZER) += eql.o
 obj-$(CONFIG_IFB) += ifb.o
drivers/net/ipvlan/Makefile new file +7 -0
new file mode 100644
index 000000000000..2efff4e9bb40
@@ -0,0 +1,7 @@
+#
+# Makefile for the Ethernet Ipvlan driver
+#
+
+obj-$(CONFIG_IPVLAN) += ipvlan.o
+
+ipvlan-objs := ipvlan_core.o ipvlan_main.o ipvlan_sysfs.o
drivers/net/ipvlan/ipvlan.h new file +164 -0

164 lines not on this page · raw message

drivers/net/ipvlan/ipvlan_core.c new file +634 -0

634 lines not on this page · raw message

drivers/net/ipvlan/ipvlan_main.c new file +828 -0

828 lines not on this page · raw message

drivers/net/ipvlan/ipvlan_sysfs.c new file +119 -0

119 lines not on this page · raw message

include/linux/netdevice.h +4 -0
index 888d5513fa4a..0b290c04a469 100644
@@ -1228,6 +1228,8 @@ enum netdev_priv_flags {
 	IFF_LIVE_ADDR_CHANGE		= 1<<20,
 	IFF_MACVLAN			= 1<<21,
 	IFF_XMIT_DST_RELEASE_PERM	= 1<<22,
+	IFF_IPVLAN_MASTER		= 1<<23,
+	IFF_IPVLAN_SLAVE		= 1<<24,
 };
 
 #define IFF_802_1Q_VLAN			IFF_802_1Q_VLAN
@@ -1253,6 +1255,8 @@ enum netdev_priv_flags {
 #define IFF_LIVE_ADDR_CHANGE		IFF_LIVE_ADDR_CHANGE
 #define IFF_MACVLAN			IFF_MACVLAN
 #define IFF_XMIT_DST_RELEASE_PERM	IFF_XMIT_DST_RELEASE_PERM
+#define IFF_IPVLAN_MASTER		IFF_IPVLAN_MASTER
+#define IFF_IPVLAN_SLAVE		IFF_IPVLAN_SLAVE
 
 /**
  *	struct net_device - The DEVICE structure.
include/uapi/linux/if_link.h +15 -0
index 7072d8325016..36bddc233633 100644
@@ -330,6 +330,21 @@ enum macvlan_macaddr_mode {
 
 #define MACVLAN_FLAG_NOPROMISC	1
 
+/* IPVLAN section */
+enum {
+	IFLA_IPVLAN_UNSPEC,
+	IFLA_IPVLAN_MODE,
+	__IFLA_IPVLAN_MAX
+};
+
+#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1)
+
+enum ipvlan_mode {
+	IPVLAN_MODE_L2 = 0,
+	IPVLAN_MODE_L3,
+	IPVLAN_MODE_MAX
+};
+
 /* VXLAN section */
 enum {
 	IFLA_VXLAN_UNSPEC,
Signature · 1 line
2.1.0.rc2.206.gedb03e5
#1 lore raw
Quoted 6 lines · Mahesh Bandewar

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

Why do we need this from the beginning?
IOW, what problem does this solve while macvlan doesn't?

Quoted 6 lines

This driver was developed keeping the namespace use-case in mind. Hence most of the examples given here take that as the base setup where main-device belongs to the default-ns and virtual devices are assigned to the additional namespaces.

Which virtual device is still not aware of netns now? I'd be surprised.

I _guess_ you mean the if_link of macvlan, that is unfortunately not just macvlan, but because of netns isolates from L2, it is purely a display problem, macvlan should work well, just its netlink dump is confusing.

maintainer
#2 lore raw

From: Cong Wang <[email protected]>
Date: Tue, 11 Nov 2014 15:12:27 -0800

Quoted 10 lines · Mahesh Bandewar
Quoted 6 lines

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

Why do we need this from the beginning?
IOW, what problem does this solve while macvlan doesn't?

macvlan has several built-in limitations, which IP VLAN absolutely does not have.

Eric Dumazet spoke about this at the networking track at the kernel summit in Chicago, maybe he or another person working on this can chime in.

#3 lore raw
Quoted 14 lines · David Miller

From: Cong Wang <[email protected]>
Date: Tue, 11 Nov 2014 15:12:27 -0800

Quoted 10 lines · Mahesh Bandewar
Quoted 6 lines

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

Why do we need this from the beginning?
IOW, what problem does this solve while macvlan doesn't?

Quoted 6 lines

macvlan has several built-in limitations, which IP VLAN absolutely does not have.

Eric Dumazet spoke about this at the networking track at the kernel summit in Chicago, maybe he or another person working on this can chime in.

Either you need to publish it or document it in this changelog, otherwise too much information is missing.

#4 lore raw
In reply to Cong Wang
Quoted 10 lines · Cong Wang
Quoted 6 lines · Mahesh Bandewar

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

Why do we need this from the beginning?
IOW, what problem does this solve while macvlan doesn't?

I think it is good to reduce the number of mac addresses before a NIC switches into promisc mode.

Bye,
Hannes

#5 lore raw
Quoted 9 lines · Hannes Frederic Sowa
Quoted 8 lines · Cong Wang
Quoted 6 lines · Mahesh Bandewar

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

Quoted 6 lines
Quoted 2 lines

Why do we need this from the beginning?
IOW, what problem does this solve while macvlan doesn't?

I think it is good to reduce the number of mac addresses before a NIC switches into promisc mode.

Sounds like over-kill to have a new device just for not worrying about mac. Or you mean our neigh table doesn't scale?

#6 lore raw
Quoted 2 lines · Cong Wang

Sounds like over-kill to have a new device just for not worrying about mac. Or you mean our neigh table doesn't scale?

Some environments simply do not allow having multiple MAC, it is not a linux problem with neigh table.

Linux hosts can be attached to switches with a very strict security policy : One (or few) mac address per port.

http://en.wikipedia.org/wiki/CAM_Table

#7 lore raw
In reply to Cong Wang
Quoted 13 lines · Cong Wang
Quoted 12 lines · Hannes Frederic Sowa
Quoted 10 lines · Cong Wang
Quoted 6 lines · Mahesh Bandewar

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

Why do we need this from the beginning?
IOW, what problem does this solve while macvlan doesn't?

Quoted 6 lines
Quoted 3 lines

I think it is good to reduce the number of mac addresses before a NIC switches into promisc mode.

Sounds like over-kill to have a new device just for not worrying about mac. Or you mean our neigh table doesn't scale?

I do not think this is a neigh-table scaling issue and certainly wont feel it's a over kill. It's addressing the need that macvlan does not address. Having said that, this certainly does not mean that this is the replacement of macvlan driver.

(Linux) Hosts could be connected to devices with stricter security policies allowing only a mac per port it's connected to barring the use of devices like macvlan. This would mean that you need something like ipvlan type of device to address that need. Also as Hannes has mentioned, burning more macs per port and putting the NIC in promisc mode is taxing for performance. That could be avoided by using ipvlan.

At LPC there was another talk / discussion about using pure L2 device in docker container was a security concern
(http://www.linuxplumbersconf.net/2014/ocw//system/presentations/1959/original/lxc-security-issues.pdf)

However, point taken, I will summarize all this macvlan to ipvlan comparison and add into the document that is part of this patch.

Thanks,
--mahesh..

#8 lore raw
In reply to Mahesh Bandewar

On Tue, 2014-11-11 at 14:29 -0800, Mahesh Bandewar wrote:

...

Quoted 29 lines
drivers/net/ipvlan/ipvlan_core.c new file +29 -0
+static void *ipvlan_get_L3_hdr(struct sk_buff *skb, int *type)
+{
+	void *lyr3h = NULL;
+
+	switch (skb->protocol) {
+	case htons(ETH_P_ARP): {
+		struct arphdr *arph;
+
+		if (unlikely(!pskb_may_pull(skb, sizeof(struct arphdr))))
+			return NULL;
+
+		arph = arp_hdr(skb);
+		*type = IPVL_ARP;
+		lyr3h = arph;
+		break;
+	}
+
+	case htons(ETH_P_IP): {
+		u32 pktlen;
+		struct iphdr *ip4h;
+
+		if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))
+			return NULL;
+
+		ip4h = ip_hdr(skb);
+		pktlen = ntohs(ip4h->tot_len);
+		if (ip4h->ihl < 5 || ip4h->version != 4)
+			return NULL;
+		if (skb->len < pktlen || pktlen < (ip4h->ihl * 4))

drivers/net/ipvlan/ipvlan_core.c line 149

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+			return NULL;
+
+		*type = IPVL_IPV4;
+		lyr3h = ip4h;
+		break;
+	}
+	case htons(ETH_P_IPV6): {
+		struct ipv6hdr *ip6h;
+
+		if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))

sizeof(struct ipv6hdr) or sizeof(*ip6h)

Quoted 23 lines
drivers/net/ipvlan/ipvlan_core.c new file +23 -0
+			return NULL;
+
+		ip6h = ipv6_hdr(skb);
+		if (ip6h->version != 6)
+			return NULL;
+
+		*type = IPVL_IPV6;
+		lyr3h = ip6h;
+		/* Only Neighbour Solicitation pkts need different treatment */
+		if (ipv6_addr_any(&ip6h->saddr) &&
+		    ip6h->nexthdr == NEXTHDR_ICMP) {
+			/* Get to the ICMPv6 header */
+			*type = IPVL_ICMPV6;
+			lyr3h = ip6h + 1;
+		}
+		break;
+	}
+	default:
+		return NULL;
+	}
+
+	return lyr3h;
+}

...

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+static int ipvlan_process_v6_outbound(struct sk_buff *skb)
+{
+	const struct ipv6hdr *ip6h = ipv6_hdr(skb);
+	struct net_device *dev = skb->dev;
+	struct dst_entry *dst;
+	int err, ret = NET_XMIT_DROP;
+	struct flowi6 fl6 = {
+		.flowi6_iif = skb->dev->ifindex,
+		.daddr = ip6h->daddr,
+		.saddr = ip6h->saddr,

drivers/net/ipvlan/ipvlan_core.c line 385

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+		.flowi6_flags = FLOWI_FLAG_ANYSRC,
+		.flowlabel = ip6_flowinfo(ip6h),
+		.flowi6_mark = skb->mark,
+		.flowi6_proto = ip6h->nexthdr,
+	};
+
+	dst = ip6_route_output(dev_net(dev), NULL, &fl6);
+	if (IS_ERR(dst)) {
+		err = PTR_ERR(dst);
+		dst = NULL;

dst = NULL; seems not needed.

Quoted 16 lines
drivers/net/ipvlan/ipvlan_core.c new file +16 -0
+		goto err;
+	}
+	skb_dst_drop(skb);
+	skb_dst_set(skb, dst);
+	err = ip6_local_out(skb);
+	if (unlikely(net_xmit_eval(err)))
+		dev->stats.tx_errors++;
+	else
+		ret = NET_XMIT_SUCCESS;
+	goto out;
+err:
+	dev->stats.tx_errors++;
+	kfree_skb(skb);
+out:
+	return ret;
+}

...

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+static rx_handler_result_t ipvlan_handle_mode_l2(struct sk_buff **pskb,
+						 struct ipvl_port *port)
+{
+	struct sk_buff *skb = *pskb;
+	struct ethhdr *eth = eth_hdr(skb);
+	rx_handler_result_t ret = RX_HANDLER_PASS;
+	void *lyr3h;
+	int addr_type;
+
+	/* First Handle multi-cast frames */

drivers/net/ipvlan/ipvlan_core.c line 598

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+	if (is_multicast_ether_addr(eth->h_dest)) {
+		/* Pass to virtual devs only if they haven't seen the frame. */
+		if (ipvlan_external_frame(skb, port)) {
+			ipvlan_dbg(4, "%s[%d]L2:Mcast Recv:[%s], PROT=[%x]\n",
+				   __func__, __LINE__, port->dev->name,
+				   ntohs(skb->protocol));
+			ipvlan_multicast_frame(port, skb, NULL, false);
+		}
+	} else if ((lyr3h = ipvlan_get_L3_hdr(skb, &addr_type)) != NULL) {
+		struct ipvl_addr *addr = NULL;

= NULL; not needed.

Quoted 12 lines
drivers/net/ipvlan/ipvlan_core.c new file +12 -0
+
+		addr = ipvlan_addr_lookup(port, lyr3h, addr_type, true);
+		if (addr) {
+			ipvlan_dbg(4, "%s[%d]L2:Ucast Recv:[%s], PROT=[%x]\n",
+				   __func__, __LINE__, addr->master->dev->name,
+				   ntohs(skb->protocol));
+			ret = ipvlan_rcv_frame(addr, skb, false);
+		}
+	}
+
+	return ret;
+}

drivers/net/ipvlan/ipvlan_core.c line 620

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+
+rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb)
+{
+	struct sk_buff *skb = *pskb;
+	struct ipvl_port *port = ipvlan_port_get_rcu(skb->dev);
+
+	if (!port)
+		goto out;
+
+	if (unlikely(!pskb_may_pull(skb, sizeof(struct ethhdr))))

This looks strange.

Here we are sure ethernet header was already pulled by eth_type_trans()

Quoted 30 lines
drivers/net/ipvlan/ipvlan_core.c new file +14 -0
+		goto out;
+
+	switch (port->mode) {
+	case IPVLAN_MODE_L2:
+		return ipvlan_handle_mode_l2(pskb, port);
+	case IPVLAN_MODE_L3:
+		return ipvlan_handle_mode_l3(pskb, port);
+	}
+
+	/* Should not reach here */
+	BUG();
+out:
+	return RX_HANDLER_PASS;
+}
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
new file mode 100644
index 000000000000..e87b6eb01060
@@ -0,0 +1,828 @@
+/* Copyright (c) 2014 Mahesh Bandewar <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ */
+
+

...

drivers/net/ipvlan/ipvlan_main.c line 632

Quoted 12 lines
drivers/net/ipvlan/ipvlan_main.c new file +12 -0
+static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+{
+	struct ipvl_addr *addr = NULL;
+
+	if (ipvlan_addr_busy(ipvlan, ip6_addr, true)) {
+		pr_warn("%s[%d]: Failed IPv6=%x:%x:%x:%x address for %s intf\n",
+			__func__, __LINE__, ip6_addr->s6_addr32[0],
+			ip6_addr->s6_addr32[1], ip6_addr->s6_addr32[2],
+			ip6_addr->s6_addr32[3], ipvlan->dev->name);
+		return -EINVAL;
+	}
+	if ((addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC)) == NULL)

Why is GFP_ATOMIC used here ?

Quoted 69 lines
drivers/net/ipvlan/ipvlan_main.c new file +69 -0
+		return -ENOMEM;
+
+	ipvlan_dbg(1, "%s[%d]: Adding IPv6=%x:%x:%x:%x address for %s intf\n",
+		   __func__, __LINE__, ip6_addr->s6_addr32[0],
+		   ip6_addr->s6_addr32[1], ip6_addr->s6_addr32[2],
+		   ip6_addr->s6_addr32[3], ipvlan->dev->name);
+	addr->master = ipvlan;
+	memcpy(&addr->ip6addr, ip6_addr, sizeof(struct in6_addr));
+	addr->atype = IPVL_IPV6;
+	list_add_tail_rcu(&addr->anode, &ipvlan->addrs);
+	ipvlan->ipv6cnt++;
+	ipvlan_ht_addr_add(ipvlan, addr);
+
+	return 0;
+}
+
+static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+{
+	struct ipvl_addr *addr = NULL;
+
+	if ((addr = ipvlan_ht_addr_lookup(ipvlan->port, ip6_addr, true)) ==NULL)
+		return;
+
+	ipvlan_dbg(1,
+		   "%s[%d]: Deleting IPv6=%x:%x:%x:%x address for %s intf.\n",
+		   __func__, __LINE__, ip6_addr->s6_addr32[0],
+		   ip6_addr->s6_addr32[1], ip6_addr->s6_addr32[2],
+		   ip6_addr->s6_addr32[3], ipvlan->dev->name);
+	/* Delete from the hash-table */
+	ipvlan_ht_addr_del(addr, true);
+	/* Delete from the logical's addr list */
+	list_del_rcu(&addr->anode);
+	ipvlan->ipv6cnt--;
+	WARN_ON(ipvlan->ipv6cnt < 0);
+	kfree_rcu(addr, rcu);
+
+	return;
+}
+
+static int ipvlan_addr6_event(struct notifier_block *unused,
+			      unsigned long event, void *ptr)
+{
+	struct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr;
+	struct net_device *dev = (struct net_device *)if6->idev->dev;
+	struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+	ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+	if (!ipvlan_dev_slave(dev))
+		return NOTIFY_DONE;
+
+	if (!ipvlan || !ipvlan->port)
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_UP:
+		if (ipvlan_add_addr6(ipvlan, &if6->addr))
+			return NOTIFY_BAD;
+		break;
+
+	case NETDEV_DOWN:
+		ipvlan_del_addr6(ipvlan, &if6->addr);
+		break;
+	}
+
+	ipvlan_dbg(3, "%s[%d]: Leaving...\n", __func__, __LINE__);
+	return NOTIFY_OK;
+}
+
+static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)

drivers/net/ipvlan/ipvlan_main.c line 711

Quoted 10 lines
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
+{
+	struct ipvl_addr *addr = NULL;
+
+	if (ipvlan_addr_busy(ipvlan, ip4_addr, false)) {
+		pr_warn("%s[%d]: Failed to add IPv4=%x on %s intf.\n",
+			__func__, __LINE__, ntohl(ip4_addr->s_addr),
+			   ipvlan->dev->name);
+		return -EINVAL;
+	}
+	if ((addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC)) == NULL)

Same issue here ? GFP_KERNEL should be OK.

#9 lore raw
Quoted 195 lines · Eric Dumazet

On Tue, 2014-11-11 at 14:29 -0800, Mahesh Bandewar wrote:

...

Quoted 29 lines
drivers/net/ipvlan/ipvlan_core.c new file +29 -0
+static void *ipvlan_get_L3_hdr(struct sk_buff *skb, int *type)
+{
+     void *lyr3h = NULL;
+
+     switch (skb->protocol) {
+     case htons(ETH_P_ARP): {
+             struct arphdr *arph;
+
+             if (unlikely(!pskb_may_pull(skb, sizeof(struct arphdr))))
+                     return NULL;
+
+             arph = arp_hdr(skb);
+             *type = IPVL_ARP;
+             lyr3h = arph;
+             break;
+     }
+
+     case htons(ETH_P_IP): {
+             u32 pktlen;
+             struct iphdr *ip4h;
+
+             if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))
+                     return NULL;
+
+             ip4h = ip_hdr(skb);
+             pktlen = ntohs(ip4h->tot_len);
+             if (ip4h->ihl < 5 || ip4h->version != 4)
+                     return NULL;
+             if (skb->len < pktlen || pktlen < (ip4h->ihl * 4))

drivers/net/ipvlan/ipvlan_core.c line 149

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+                     return NULL;
+
+             *type = IPVL_IPV4;
+             lyr3h = ip4h;
+             break;
+     }
+     case htons(ETH_P_IPV6): {
+             struct ipv6hdr *ip6h;
+
+             if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))

sizeof(struct ipv6hdr) or sizeof(*ip6h)

Quoted 23 lines
drivers/net/ipvlan/ipvlan_core.c new file +23 -0
+                     return NULL;
+
+             ip6h = ipv6_hdr(skb);
+             if (ip6h->version != 6)
+                     return NULL;
+
+             *type = IPVL_IPV6;
+             lyr3h = ip6h;
+             /* Only Neighbour Solicitation pkts need different treatment */
+             if (ipv6_addr_any(&ip6h->saddr) &&
+                 ip6h->nexthdr == NEXTHDR_ICMP) {
+                     /* Get to the ICMPv6 header */
+                     *type = IPVL_ICMPV6;
+                     lyr3h = ip6h + 1;
+             }
+             break;
+     }
+     default:
+             return NULL;
+     }
+
+     return lyr3h;
+}

...

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+static int ipvlan_process_v6_outbound(struct sk_buff *skb)
+{
+     const struct ipv6hdr *ip6h = ipv6_hdr(skb);
+     struct net_device *dev = skb->dev;
+     struct dst_entry *dst;
+     int err, ret = NET_XMIT_DROP;
+     struct flowi6 fl6 = {
+             .flowi6_iif = skb->dev->ifindex,
+             .daddr = ip6h->daddr,
+             .saddr = ip6h->saddr,

drivers/net/ipvlan/ipvlan_core.c line 385

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+             .flowi6_flags = FLOWI_FLAG_ANYSRC,
+             .flowlabel = ip6_flowinfo(ip6h),
+             .flowi6_mark = skb->mark,
+             .flowi6_proto = ip6h->nexthdr,
+     };
+
+     dst = ip6_route_output(dev_net(dev), NULL, &fl6);
+     if (IS_ERR(dst)) {
+             err = PTR_ERR(dst);
+             dst = NULL;

dst = NULL; seems not needed.

Quoted 16 lines
drivers/net/ipvlan/ipvlan_core.c new file +16 -0
+             goto err;
+     }
+     skb_dst_drop(skb);
+     skb_dst_set(skb, dst);
+     err = ip6_local_out(skb);
+     if (unlikely(net_xmit_eval(err)))
+             dev->stats.tx_errors++;
+     else
+             ret = NET_XMIT_SUCCESS;
+     goto out;
+err:
+     dev->stats.tx_errors++;
+     kfree_skb(skb);
+out:
+     return ret;
+}

...

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+static rx_handler_result_t ipvlan_handle_mode_l2(struct sk_buff **pskb,
+                                              struct ipvl_port *port)
+{
+     struct sk_buff *skb = *pskb;
+     struct ethhdr *eth = eth_hdr(skb);
+     rx_handler_result_t ret = RX_HANDLER_PASS;
+     void *lyr3h;
+     int addr_type;
+
+     /* First Handle multi-cast frames */

drivers/net/ipvlan/ipvlan_core.c line 598

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+     if (is_multicast_ether_addr(eth->h_dest)) {
+             /* Pass to virtual devs only if they haven't seen the frame. */
+             if (ipvlan_external_frame(skb, port)) {
+                     ipvlan_dbg(4, "%s[%d]L2:Mcast Recv:[%s], PROT=[%x]\n",
+                                __func__, __LINE__, port->dev->name,
+                                ntohs(skb->protocol));
+                     ipvlan_multicast_frame(port, skb, NULL, false);
+             }
+     } else if ((lyr3h = ipvlan_get_L3_hdr(skb, &addr_type)) != NULL) {
+             struct ipvl_addr *addr = NULL;

= NULL; not needed.

Quoted 12 lines
drivers/net/ipvlan/ipvlan_core.c new file +12 -0
+
+             addr = ipvlan_addr_lookup(port, lyr3h, addr_type, true);
+             if (addr) {
+                     ipvlan_dbg(4, "%s[%d]L2:Ucast Recv:[%s], PROT=[%x]\n",
+                                __func__, __LINE__, addr->master->dev->name,
+                                ntohs(skb->protocol));
+                     ret = ipvlan_rcv_frame(addr, skb, false);
+             }
+     }
+
+     return ret;
+}

drivers/net/ipvlan/ipvlan_core.c line 620

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+
+rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb)
+{
+     struct sk_buff *skb = *pskb;
+     struct ipvl_port *port = ipvlan_port_get_rcu(skb->dev);
+
+     if (!port)
+             goto out;
+
+     if (unlikely(!pskb_may_pull(skb, sizeof(struct ethhdr))))

This looks strange.

Here we are sure ethernet header was already pulled by eth_type_trans()

Quoted 30 lines
drivers/net/ipvlan/ipvlan_core.c new file +14 -0
+             goto out;
+
+     switch (port->mode) {
+     case IPVLAN_MODE_L2:
+             return ipvlan_handle_mode_l2(pskb, port);
+     case IPVLAN_MODE_L3:
+             return ipvlan_handle_mode_l3(pskb, port);
+     }
+
+     /* Should not reach here */
+     BUG();
+out:
+     return RX_HANDLER_PASS;
+}
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
new file mode 100644
index 000000000000..e87b6eb01060
@@ -0,0 +1,828 @@
+/* Copyright (c) 2014 Mahesh Bandewar <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ */
+
+

...

Quoted 15 lines

drivers/net/ipvlan/ipvlan_main.c line 632

Quoted 12 lines
drivers/net/ipvlan/ipvlan_main.c new file +12 -0
+static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+{
+     struct ipvl_addr *addr = NULL;
+
+     if (ipvlan_addr_busy(ipvlan, ip6_addr, true)) {
+             pr_warn("%s[%d]: Failed IPv6=%x:%x:%x:%x address for %s intf\n",
+                     __func__, __LINE__, ip6_addr->s6_addr32[0],
+                     ip6_addr->s6_addr32[1], ip6_addr->s6_addr32[2],
+                     ip6_addr->s6_addr32[3], ipvlan->dev->name);
+             return -EINVAL;
+     }
+     if ((addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC)) == NULL)

Why is GFP_ATOMIC used here ?

That is correct. I was using some locking during the development and these are remaining bits from those. I would correct it.

Quoted 84 lines
Quoted 69 lines
drivers/net/ipvlan/ipvlan_main.c new file +69 -0
+             return -ENOMEM;
+
+     ipvlan_dbg(1, "%s[%d]: Adding IPv6=%x:%x:%x:%x address for %s intf\n",
+                __func__, __LINE__, ip6_addr->s6_addr32[0],
+                ip6_addr->s6_addr32[1], ip6_addr->s6_addr32[2],
+                ip6_addr->s6_addr32[3], ipvlan->dev->name);
+     addr->master = ipvlan;
+     memcpy(&addr->ip6addr, ip6_addr, sizeof(struct in6_addr));
+     addr->atype = IPVL_IPV6;
+     list_add_tail_rcu(&addr->anode, &ipvlan->addrs);
+     ipvlan->ipv6cnt++;
+     ipvlan_ht_addr_add(ipvlan, addr);
+
+     return 0;
+}
+
+static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+{
+     struct ipvl_addr *addr = NULL;
+
+     if ((addr = ipvlan_ht_addr_lookup(ipvlan->port, ip6_addr, true)) ==NULL)
+             return;
+
+     ipvlan_dbg(1,
+                "%s[%d]: Deleting IPv6=%x:%x:%x:%x address for %s intf.\n",
+                __func__, __LINE__, ip6_addr->s6_addr32[0],
+                ip6_addr->s6_addr32[1], ip6_addr->s6_addr32[2],
+                ip6_addr->s6_addr32[3], ipvlan->dev->name);
+     /* Delete from the hash-table */
+     ipvlan_ht_addr_del(addr, true);
+     /* Delete from the logical's addr list */
+     list_del_rcu(&addr->anode);
+     ipvlan->ipv6cnt--;
+     WARN_ON(ipvlan->ipv6cnt < 0);
+     kfree_rcu(addr, rcu);
+
+     return;
+}
+
+static int ipvlan_addr6_event(struct notifier_block *unused,
+                           unsigned long event, void *ptr)
+{
+     struct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr;
+     struct net_device *dev = (struct net_device *)if6->idev->dev;
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!ipvlan_dev_slave(dev))
+             return NOTIFY_DONE;
+
+     if (!ipvlan || !ipvlan->port)
+             return NOTIFY_DONE;
+
+     switch (event) {
+     case NETDEV_UP:
+             if (ipvlan_add_addr6(ipvlan, &if6->addr))
+                     return NOTIFY_BAD;
+             break;
+
+     case NETDEV_DOWN:
+             ipvlan_del_addr6(ipvlan, &if6->addr);
+             break;
+     }
+
+     ipvlan_dbg(3, "%s[%d]: Leaving...\n", __func__, __LINE__);
+     return NOTIFY_OK;
+}
+
+static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)

drivers/net/ipvlan/ipvlan_main.c line 711

Quoted 10 lines
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
+{
+     struct ipvl_addr *addr = NULL;
+
+     if (ipvlan_addr_busy(ipvlan, ip4_addr, false)) {
+             pr_warn("%s[%d]: Failed to add IPv4=%x on %s intf.\n",
+                     __func__, __LINE__, ntohl(ip4_addr->s_addr),
+                        ipvlan->dev->name);
+             return -EINVAL;
+     }
+     if ((addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC)) == NULL)

Same issue here ? GFP_KERNEL should be OK.

#10 lore raw
In reply to Mahesh Bandewar
Quoted 39 lines · Mahesh Bandewar

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

This driver was developed keeping the namespace use-case in mind. Hence most of the examples given here take that as the base setup where main-device belongs to the default-ns and virtual devices are assigned to the additional namespaces.

The device operates in two different modes and the difference in these two modes in primarily in the TX side.

(a) L2 mode : In this mode, the device behaves as a L2 device. TX processing upto L2 happens on the stack of the virtual device associated with (namespace). Packets are switched after that into the main device (default-ns) and queued for xmit.

RX processing is simple and all multicast, broadcast (if applicable), and unicast belonging to the address(es) are delivered to the virtual devices.

(b) L3 mode : In this mode, the device behaves like a L3 device. TX processing upto L3 happens on the stack of the virtual device associated with (namespace). Packets are switched to the main-device (default-ns) for the L2 processing. Hence the routing table of the default-ns will be used in this mode.

RX processins is somewhat similar to the L2 mode except that in this mode only Unicast packets are delivered to the virtual device while main-dev will handle all other packets.

The devices can be added using the "ip" command from the iproute2 package -

ip link add link <master> <virtual> type ipvlan mode [ l2 | l3 ]

Quoted 7 lines
Cc Eric Dumazet <[email protected]>
Cc Maciej Żenczykowski <[email protected]>
Cc Laurent Chavey <[email protected]>
Cc Tim Hockin <[email protected]>
Cc Brandon Philips <[email protected]>
Cc Pavel Emelianov <[email protected]>

Acked-by: /me on the general idea. We use this device of type in Parallels heavily for several reasons -- not to generate too many MAC-s from one host and to "enforce" the IP address for a container. I have a comment about the latter below.

Quoted 29 lines
drivers/net/ipvlan/ipvlan_core.c new file +29 -0
+static void *ipvlan_get_L3_hdr(struct sk_buff *skb, int *type)
+{
+	void *lyr3h = NULL;
+
+	switch (skb->protocol) {
+	case htons(ETH_P_ARP): {
+		struct arphdr *arph;
+
+		if (unlikely(!pskb_may_pull(skb, sizeof(struct arphdr))))
+			return NULL;
+
+		arph = arp_hdr(skb);
+		*type = IPVL_ARP;
+		lyr3h = arph;
+		break;
+	}
+
+	case htons(ETH_P_IP): {
+		u32 pktlen;
+		struct iphdr *ip4h;
+
+		if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))
+			return NULL;
+
+		ip4h = ip_hdr(skb);
+		pktlen = ntohs(ip4h->tot_len);
+		if (ip4h->ihl < 5 || ip4h->version != 4)
+			return NULL;
+		if (skb->len < pktlen || pktlen < (ip4h->ihl * 4))

drivers/net/ipvlan/ipvlan_core.c line 149

Quoted 10 lines
drivers/net/ipvlan/ipvlan_core.c new file +10 -0
+			return NULL;
+
+		*type = IPVL_IPV4;
+		lyr3h = ip4h;
+		break;
+	}
+	case htons(ETH_P_IPV6): {
+		struct ipv6hdr *ip6h;
+
+		if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))

Misprint -- should be sizeof(struct ipv6hdr)

Quoted 26 lines
drivers/net/ipvlan/ipvlan_main.c new file +26 -0
+static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
+			   struct nlattr *tb[], struct nlattr *data[])
+{
+	struct ipvl_dev *ipvlan = netdev_priv(dev);
+	struct ipvl_port *port;
+	struct net_device *phy_dev;
+	int err;
+
+	ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+	if (!tb[IFLA_LINK]) {
+		ipvlan_dbg(3, "%s[%d]: Returning -EINVAL...\n",
+			   __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	phy_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
+	if (phy_dev == NULL) {
+		ipvlan_dbg(3, "%s[%d]: Returning -ENODEV...\n",
+			   __func__, __LINE__);
+		return -ENODEV;
+	}
+
+	/* TODO will someone try creating ipvlan-dev on an ipvlan-virtual dev?*/
+	if (!ipvlan_dev_master(phy_dev)) {
+		err = ipvlan_port_create(phy_dev);
+		if (err < 0) {

drivers/net/ipvlan/ipvlan_main.c line 464

Quoted 10 lines
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
+			ipvlan_dbg(3, "%s[%d]: Returning error (%d)...\n",
+				   __func__, __LINE__, err);
+			return err;
+		}
+	}
+
+	port = ipvlan_port_get_rtnl(phy_dev);
+	/* Get the mode if specified. */
+	if (data && data[IFLA_IPVLAN_MODE])
+		port->mode = nla_get_u16(data[IFLA_IPVLAN_MODE]);

Should the invalid value be checked here? There are places where we BUG() in mode being "unknown".

Quoted 30 lines
drivers/net/ipvlan/ipvlan_main.c new file +28 -0
+
+	ipvlan->phy_dev = phy_dev;
+	ipvlan->dev = dev;
+	ipvlan->port = port;
+	ipvlan->sfeatures = IPVLAN_FEATURES;
+	INIT_LIST_HEAD(&ipvlan->addrs);
+	ipvlan->ipv4cnt = 0;
+	ipvlan->ipv6cnt = 0;
 
 
+static int ipvlan_device_event(struct notifier_block *unused,
+			       unsigned long event, void *ptr)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+	struct ipvl_dev *ipvlan, *next;
+	struct ipvl_port *port;
+	LIST_HEAD(lst_kill);
+
+	if (!ipvlan_dev_master(dev))
+		return NOTIFY_DONE;
+
+	port = ipvlan_port_get_rtnl(dev);
+
+	switch (event) {
+	case NETDEV_CHANGE:
+		list_for_each_entry(ipvlan, &port->ipvlans, pnode)
+			netif_stacked_transfer_operstate(ipvlan->phy_dev,
+							 ipvlan->dev);
+		break;
+

drivers/net/ipvlan/ipvlan_main.c line 597

Quoted 10 lines
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
+	case NETDEV_UNREGISTER:
+		if (dev->reg_state != NETREG_UNREGISTERING)
+			break;
+
+		list_for_each_entry_safe(ipvlan, next, &port->ipvlans,
+					 pnode)
+			ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev,
+							    &lst_kill);
+		unregister_netdevice_many(&lst_kill);
+		list_del(&lst_kill);

This list_del seems to be excessive.

Quoted 10 lines
drivers/net/ipvlan/ipvlan_main.c new file +9 -0
+		break;
+
 
+static int ipvlan_addr4_event(struct notifier_block *unused,
+			      unsigned long event, void *ptr)
+{
+	struct in_ifaddr *if4 = (struct in_ifaddr *)ptr;
+	struct net_device *dev = (struct net_device *)if4->ifa_dev->dev;
+	struct ipvl_dev *ipvlan = netdev_priv(dev);
+	struct in_addr ip4_addr;

drivers/net/ipvlan/ipvlan_main.c line 765

Quoted 10 lines
drivers/net/ipvlan/ipvlan_main.c new file +10 -0
+
+	ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+	if (!ipvlan_dev_slave(dev))
+		return NOTIFY_DONE;
+
+	if (!ipvlan || !ipvlan->port)
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_UP:

Can it be (in the future) somehow restricted so that net-namespace wouldn't be able to assign arbitrary IP address here? One of the reasons for using such devices is to enforce the container to use the IP address given from the host.

Quoted 14 lines
drivers/net/ipvlan/ipvlan_main.c new file +14 -0
+		ip4_addr.s_addr = if4->ifa_address;
+		if (ipvlan_add_addr4(ipvlan, &ip4_addr))
+			return NOTIFY_BAD;
+		break;
+
+	case NETDEV_DOWN:
+		ip4_addr.s_addr = if4->ifa_address;
+		ipvlan_del_addr4(ipvlan, &ip4_addr);
+		break;
+	}
+
+	ipvlan_dbg(3, "%s[%d]: Leaving...\n", __func__, __LINE__);
+	return NOTIFY_OK;
+}
#11 lore raw
Quoted 87 lines · Pavel Emelyanov
Quoted 39 lines · Mahesh Bandewar

This driver is very similar to the macvlan driver except that it uses L3 on the frame to determine the logical interface while functioning as packet dispatcher. It inherits L2 of the master device hence the packets on wire will have the same L2 for all the packets originating from all virtual devices off of the same master device.

This driver was developed keeping the namespace use-case in mind. Hence most of the examples given here take that as the base setup where main-device belongs to the default-ns and virtual devices are assigned to the additional namespaces.

The device operates in two different modes and the difference in these two modes in primarily in the TX side.

(a) L2 mode : In this mode, the device behaves as a L2 device. TX processing upto L2 happens on the stack of the virtual device associated with (namespace). Packets are switched after that into the main device (default-ns) and queued for xmit.

RX processing is simple and all multicast, broadcast (if applicable), and unicast belonging to the address(es) are delivered to the virtual devices.

(b) L3 mode : In this mode, the device behaves like a L3 device. TX processing upto L3 happens on the stack of the virtual device associated with (namespace). Packets are switched to the main-device (default-ns) for the L2 processing. Hence the routing table of the default-ns will be used in this mode.

RX processins is somewhat similar to the L2 mode except that in this mode only Unicast packets are delivered to the virtual device while main-dev will handle all other packets.

The devices can be added using the "ip" command from the iproute2 package -

ip link add link <master> <virtual> type ipvlan mode [ l2 | l3 ]

Quoted 7 lines
Cc Eric Dumazet <[email protected]>
Cc Maciej Żenczykowski <[email protected]>
Cc Laurent Chavey <[email protected]>
Cc Tim Hockin <[email protected]>
Cc Brandon Philips <[email protected]>
Cc Pavel Emelianov <[email protected]>

Acked-by: /me on the general idea. We use this device of type in Parallels heavily for several reasons -- not to generate too many MAC-s from one host and to "enforce" the IP address for a container. I have a comment about the latter below.

Quoted 33 lines
drivers/net/ipvlan/ipvlan_core.c new file +33 -0
+static void *ipvlan_get_L3_hdr(struct sk_buff *skb, int *type)
+{
+     void *lyr3h = NULL;
+
+     switch (skb->protocol) {
+     case htons(ETH_P_ARP): {
+             struct arphdr *arph;
+
+             if (unlikely(!pskb_may_pull(skb, sizeof(struct arphdr))))
+                     return NULL;
+
+             arph = arp_hdr(skb);
+             *type = IPVL_ARP;
+             lyr3h = arph;
+             break;
+     }
+
+     case htons(ETH_P_IP): {
+             u32 pktlen;
+             struct iphdr *ip4h;
+
+             if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))
+                     return NULL;
+
+             ip4h = ip_hdr(skb);
+             pktlen = ntohs(ip4h->tot_len);
+             if (ip4h->ihl < 5 || ip4h->version != 4)
+                     return NULL;
+             if (skb->len < pktlen || pktlen < (ip4h->ihl * 4))
+                     return NULL;
+
+             *type = IPVL_IPV4;
+             lyr3h = ip4h;
Quoted 9 lines

drivers/net/ipvlan/ipvlan_core.c line 149

Quoted 6 lines
drivers/net/ipvlan/ipvlan_core.c new file +6 -0
+             break;
+     }
+     case htons(ETH_P_IPV6): {
+             struct ipv6hdr *ip6h;
+
+             if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))

Misprint -- should be sizeof(struct ipv6hdr)

Good catch, will correct it!

Quoted 31 lines
Quoted 31 lines
drivers/net/ipvlan/ipvlan_main.c new file +31 -0
+static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
+                        struct nlattr *tb[], struct nlattr *data[])
+{
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+     struct ipvl_port *port;
+     struct net_device *phy_dev;
+     int err;
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!tb[IFLA_LINK]) {
+             ipvlan_dbg(3, "%s[%d]: Returning -EINVAL...\n",
+                        __func__, __LINE__);
+             return -EINVAL;
+     }
+
+     phy_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
+     if (phy_dev == NULL) {
+             ipvlan_dbg(3, "%s[%d]: Returning -ENODEV...\n",
+                        __func__, __LINE__);
+             return -ENODEV;
+     }
+
+     /* TODO will someone try creating ipvlan-dev on an ipvlan-virtual dev?*/
+     if (!ipvlan_dev_master(phy_dev)) {
+             err = ipvlan_port_create(phy_dev);
+             if (err < 0) {
+                     ipvlan_dbg(3, "%s[%d]: Returning error (%d)...\n",
+                                __func__, __LINE__, err);
+                     return err;
+             }
+     }
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 464

Quoted 5 lines
drivers/net/ipvlan/ipvlan_main.c new file +5 -0
+
+     port = ipvlan_port_get_rtnl(phy_dev);
+     /* Get the mode if specified. */
+     if (data && data[IFLA_IPVLAN_MODE])
+             port->mode = nla_get_u16(data[IFLA_IPVLAN_MODE]);

Should the invalid value be checked here? There are places where we BUG() in mode being "unknown".

Assuming the calls come over netlink, the ".validate" will be called before ".newlink", so that would be unnecessary, isn't it?

Quoted 34 lines
Quoted 34 lines
drivers/net/ipvlan/ipvlan_main.c new file +32 -0
+
+     ipvlan->phy_dev = phy_dev;
+     ipvlan->dev = dev;
+     ipvlan->port = port;
+     ipvlan->sfeatures = IPVLAN_FEATURES;
+     INIT_LIST_HEAD(&ipvlan->addrs);
+     ipvlan->ipv4cnt = 0;
+     ipvlan->ipv6cnt = 0;
 
 
+static int ipvlan_device_event(struct notifier_block *unused,
+                            unsigned long event, void *ptr)
+{
+     struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+     struct ipvl_dev *ipvlan, *next;
+     struct ipvl_port *port;
+     LIST_HEAD(lst_kill);
+
+     if (!ipvlan_dev_master(dev))
+             return NOTIFY_DONE;
+
+     port = ipvlan_port_get_rtnl(dev);
+
+     switch (event) {
+     case NETDEV_CHANGE:
+             list_for_each_entry(ipvlan, &port->ipvlans, pnode)
+                     netif_stacked_transfer_operstate(ipvlan->phy_dev,
+                                                      ipvlan->dev);
+             break;
+
+     case NETDEV_UNREGISTER:
+             if (dev->reg_state != NETREG_UNREGISTERING)
+                     break;
+
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 597

Quoted 6 lines
drivers/net/ipvlan/ipvlan_main.c new file +6 -0
+             list_for_each_entry_safe(ipvlan, next, &port->ipvlans,
+                                      pnode)
+                     ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev,
+                                                         &lst_kill);
+             unregister_netdevice_many(&lst_kill);
+             list_del(&lst_kill);

This list_del seems to be excessive.

That is correct. Looks like unregister_netdevice_many() does it now. I'll remove it.

Quoted 17 lines
Quoted 17 lines
drivers/net/ipvlan/ipvlan_main.c new file +16 -0
+             break;
+
 
+static int ipvlan_addr4_event(struct notifier_block *unused,
+                           unsigned long event, void *ptr)
+{
+     struct in_ifaddr *if4 = (struct in_ifaddr *)ptr;
+     struct net_device *dev = (struct net_device *)if4->ifa_dev->dev;
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+     struct in_addr ip4_addr;
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!ipvlan_dev_slave(dev))
+             return NOTIFY_DONE;
+
+     if (!ipvlan || !ipvlan->port)
+             return NOTIFY_DONE;
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 687

Quoted 3 lines
drivers/net/ipvlan/ipvlan_main.c new file +3 -0
+
+     switch (event) {
+     case NETDEV_UP:

Can it be (in the future) somehow restricted so that net-namespace wouldn't be able to assign arbitrary IP address here? One of the reasons for using such devices is to enforce the container to use the IP address given from the host.

Probably this could be a config (sysfs?) entry which would lockup the config coming from ns when set. So code could look like -

          case NETDEV_UP:
                         if (!restrict_ns_config) {
                            ...
                         }
                         break;
Quoted 15 lines
Quoted 14 lines
drivers/net/ipvlan/ipvlan_main.c new file +14 -0
+             ip4_addr.s_addr = if4->ifa_address;
+             if (ipvlan_add_addr4(ipvlan, &ip4_addr))
+                     return NOTIFY_BAD;
+             break;
+
+     case NETDEV_DOWN:
+             ip4_addr.s_addr = if4->ifa_address;
+             ipvlan_del_addr4(ipvlan, &ip4_addr);
+             break;
+     }
+
+     ipvlan_dbg(3, "%s[%d]: Leaving...\n", __func__, __LINE__);
+     return NOTIFY_OK;
+}
#12 lore raw
Quoted 40 lines
Quoted 31 lines
Quoted 31 lines
drivers/net/ipvlan/ipvlan_main.c new file +31 -0
+static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
+                        struct nlattr *tb[], struct nlattr *data[])
+{
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+     struct ipvl_port *port;
+     struct net_device *phy_dev;
+     int err;
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!tb[IFLA_LINK]) {
+             ipvlan_dbg(3, "%s[%d]: Returning -EINVAL...\n",
+                        __func__, __LINE__);
+             return -EINVAL;
+     }
+
+     phy_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
+     if (phy_dev == NULL) {
+             ipvlan_dbg(3, "%s[%d]: Returning -ENODEV...\n",
+                        __func__, __LINE__);
+             return -ENODEV;
+     }
+
+     /* TODO will someone try creating ipvlan-dev on an ipvlan-virtual dev?*/
+     if (!ipvlan_dev_master(phy_dev)) {
+             err = ipvlan_port_create(phy_dev);
+             if (err < 0) {
+                     ipvlan_dbg(3, "%s[%d]: Returning error (%d)...\n",
+                                __func__, __LINE__, err);
+                     return err;
+             }
+     }
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 464

Quoted 5 lines
drivers/net/ipvlan/ipvlan_main.c new file +5 -0
+
+     port = ipvlan_port_get_rtnl(phy_dev);
+     /* Get the mode if specified. */
+     if (data && data[IFLA_IPVLAN_MODE])
+             port->mode = nla_get_u16(data[IFLA_IPVLAN_MODE]);

Should the invalid value be checked here? There are places where we BUG() in mode being "unknown".

Quoted 2 lines

Assuming the calls come over netlink, the ".validate" will be called before ".newlink", so that would be unnecessary, isn't it?

Yes, you're right. I've missed the validate callback.

Quoted 26 lines
Quoted 17 lines
Quoted 17 lines
drivers/net/ipvlan/ipvlan_main.c new file +16 -0
+             break;
+
 
+static int ipvlan_addr4_event(struct notifier_block *unused,
+                           unsigned long event, void *ptr)
+{
+     struct in_ifaddr *if4 = (struct in_ifaddr *)ptr;
+     struct net_device *dev = (struct net_device *)if4->ifa_dev->dev;
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+     struct in_addr ip4_addr;
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!ipvlan_dev_slave(dev))
+             return NOTIFY_DONE;
+
+     if (!ipvlan || !ipvlan->port)
+             return NOTIFY_DONE;
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 687

Quoted 3 lines
drivers/net/ipvlan/ipvlan_main.c new file +3 -0
+
+     switch (event) {
+     case NETDEV_UP:

Can it be (in the future) somehow restricted so that net-namespace wouldn't be able to assign arbitrary IP address here? One of the reasons for using such devices is to enforce the container to use the IP address given from the host.

Quoted 7 lines

Probably this could be a config (sysfs?) entry which would lockup the config coming from ns when set. So code could look like -

          case NETDEV_UP:
                         if (!restrict_ns_config) {
                            ...
                         }
                         break;

Maybe introduce some "lock" call for ipvlan device after which no new IP addresses can be assigned? And the configuration would look like

1. create ipvlan
2. move to proper net namespace
3. add addresses
4. lock

?

Thanks,
Pavel

#13 lore raw
Quoted 82 lines · Pavel Emelyanov
Quoted 40 lines
Quoted 31 lines
Quoted 31 lines
drivers/net/ipvlan/ipvlan_main.c new file +31 -0
+static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
+                        struct nlattr *tb[], struct nlattr *data[])
+{
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+     struct ipvl_port *port;
+     struct net_device *phy_dev;
+     int err;
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!tb[IFLA_LINK]) {
+             ipvlan_dbg(3, "%s[%d]: Returning -EINVAL...\n",
+                        __func__, __LINE__);
+             return -EINVAL;
+     }
+
+     phy_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
+     if (phy_dev == NULL) {
+             ipvlan_dbg(3, "%s[%d]: Returning -ENODEV...\n",
+                        __func__, __LINE__);
+             return -ENODEV;
+     }
+
+     /* TODO will someone try creating ipvlan-dev on an ipvlan-virtual dev?*/
+     if (!ipvlan_dev_master(phy_dev)) {
+             err = ipvlan_port_create(phy_dev);
+             if (err < 0) {
+                     ipvlan_dbg(3, "%s[%d]: Returning error (%d)...\n",
+                                __func__, __LINE__, err);
+                     return err;
+             }
+     }
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 464

Quoted 5 lines
drivers/net/ipvlan/ipvlan_main.c new file +5 -0
+
+     port = ipvlan_port_get_rtnl(phy_dev);
+     /* Get the mode if specified. */
+     if (data && data[IFLA_IPVLAN_MODE])
+             port->mode = nla_get_u16(data[IFLA_IPVLAN_MODE]);

Should the invalid value be checked here? There are places where we BUG() in mode being "unknown".

Quoted 2 lines

Assuming the calls come over netlink, the ".validate" will be called before ".newlink", so that would be unnecessary, isn't it?

Yes, you're right. I've missed the validate callback.

Quoted 26 lines
Quoted 17 lines
Quoted 17 lines
drivers/net/ipvlan/ipvlan_main.c new file +16 -0
+             break;
+
 
+static int ipvlan_addr4_event(struct notifier_block *unused,
+                           unsigned long event, void *ptr)
+{
+     struct in_ifaddr *if4 = (struct in_ifaddr *)ptr;
+     struct net_device *dev = (struct net_device *)if4->ifa_dev->dev;
+     struct ipvl_dev *ipvlan = netdev_priv(dev);
+     struct in_addr ip4_addr;
+
+     ipvlan_dbg(3, "%s[%d]: Entering...\n", __func__, __LINE__);
+     if (!ipvlan_dev_slave(dev))
+             return NOTIFY_DONE;
+
+     if (!ipvlan || !ipvlan->port)
+             return NOTIFY_DONE;
Quoted 9 lines

drivers/net/ipvlan/ipvlan_main.c line 687

Quoted 3 lines
drivers/net/ipvlan/ipvlan_main.c new file +3 -0
+
+     switch (event) {
+     case NETDEV_UP:

Can it be (in the future) somehow restricted so that net-namespace wouldn't be able to assign arbitrary IP address here? One of the reasons for using such devices is to enforce the container to use the IP address given from the host.

Quoted 7 lines

Probably this could be a config (sysfs?) entry which would lockup the config coming from ns when set. So code could look like -

          case NETDEV_UP:
                         if (!restrict_ns_config) {
                            ...
                         }
                         break;

Maybe introduce some "lock" call for ipvlan device after which no new IP addresses can be assigned? And the configuration would look like

Quoted 6 lines

1. create ipvlan
2. move to proper net namespace
3. add addresses
4. lock

?

Yes. Exporting this "locked" property on the master device so that it can be controlled from masters' net-ns. Only thing we have to ensure is that both possibilities are allowed i.e. trusted ns where config do not need to be locked as well as untrusted/hostile ns where one can lock it down. However this is a future enhancement and if your implementation idea is different than this concept; we can discuss it at the time of implementation.

Quoted 4 lines

Thanks,
Pavel

#14 lore raw
Quoted 9 lines
Quoted 9 lines

Maybe introduce some "lock" call for ipvlan device after which no new IP addresses can be assigned? And the configuration would look like

1. create ipvlan
2. move to proper net namespace
3. add addresses
4. lock

Quoted 8 lines
Quoted 1 line

?

Yes. Exporting this "locked" property on the master device so that it can be controlled from masters' net-ns. Only thing we have to ensure is that both possibilities are allowed i.e. trusted ns where config do not need to be locked as well as untrusted/hostile ns where one can lock it down. However this is a future enhancement and if your implementation idea is different than this concept; we can discuss it at the time of implementation.

Sure. It's not a wish for the very first version of the set, it can be added later.

Thanks,
Pavel