diff options
| author | Michael Braun <michael-dev@fami-braun.de> | 2014-09-25 10:31:08 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2014-09-29 15:37:01 -0400 |
| commit | 79cf79abce71eb7dbc40e2f3121048ca5405cb47 (patch) | |
| tree | 81e5b392884d9b0c2f7e4bbd925f4c7c55229834 | |
| parent | 852248449c73b5ffe109a33d65485c71d3d398a7 (diff) | |
macvlan: add source mode
This patch adds a new mode of operation to macvlan, called "source".
It allows one to set a list of allowed mac address, which is used
to match against source mac address from received frames on underlying
interface.
This enables creating mac based VLAN associations, instead of standard
port or tag based. The feature is useful to deploy 802.1x mac based
behavior, where drivers of underlying interfaces doesn't allows that.
Configuration is done through the netlink interface using e.g.:
ip link add link eth0 name macvlan0 type macvlan mode source
ip link add link eth0 name macvlan1 type macvlan mode source
ip link set link dev macvlan0 type macvlan macaddr add 00:11:11:11:11:11
ip link set link dev macvlan0 type macvlan macaddr add 00:22:22:22:22:22
ip link set link dev macvlan0 type macvlan macaddr add 00:33:33:33:33:33
ip link set link dev macvlan1 type macvlan macaddr add 00:33:33:33:33:33
ip link set link dev macvlan1 type macvlan macaddr add 00:44:44:44:44:44
This allows clients with MAC addresses 00:11:11:11:11:11,
00:22:22:22:22:22 to be part of only VLAN associated with macvlan0
interface. Clients with MAC addresses 00:44:44:44:44:44 with only VLAN
associated with macvlan1 interface. And client with MAC address
00:33:33:33:33:33 to be associated with both VLANs.
Based on work of Stefan Gula <steweg@gmail.com>
v8: last version of Stefan Gula for Kernel 3.2.1
v9: rework onto linux-next 2014-03-12 by Michael Braun
add MACADDR_SET command, enable to configure mac for source mode
while creating interface
v10:
- reduce indention level
- rename source_list to source_entry
- use aligned 64bit ether address
- use hash_64 instead of addr[5]
v11:
- rebase for 3.14 / linux-next 20.04.2014
v12
- rebase for linux-next 2014-09-25
Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | drivers/net/macvlan.c | 304 | ||||
| -rw-r--r-- | include/linux/if_macvlan.h | 1 | ||||
| -rw-r--r-- | include/uapi/linux/if_link.h | 12 |
3 files changed, 314 insertions, 3 deletions
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 726edabff26b..e8a453f1b458 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c | |||
| @@ -35,7 +35,8 @@ | |||
| 35 | #include <net/xfrm.h> | 35 | #include <net/xfrm.h> |
| 36 | #include <linux/netpoll.h> | 36 | #include <linux/netpoll.h> |
| 37 | 37 | ||
| 38 | #define MACVLAN_HASH_SIZE (1 << BITS_PER_BYTE) | 38 | #define MACVLAN_HASH_BITS 8 |
| 39 | #define MACVLAN_HASH_SIZE (1<<MACVLAN_HASH_BITS) | ||
| 39 | #define MACVLAN_BC_QUEUE_LEN 1000 | 40 | #define MACVLAN_BC_QUEUE_LEN 1000 |
| 40 | 41 | ||
| 41 | struct macvlan_port { | 42 | struct macvlan_port { |
| @@ -47,6 +48,14 @@ struct macvlan_port { | |||
| 47 | struct work_struct bc_work; | 48 | struct work_struct bc_work; |
| 48 | bool passthru; | 49 | bool passthru; |
| 49 | int count; | 50 | int count; |
| 51 | struct hlist_head vlan_source_hash[MACVLAN_HASH_SIZE]; | ||
| 52 | }; | ||
| 53 | |||
| 54 | struct macvlan_source_entry { | ||
| 55 | struct hlist_node hlist; | ||
| 56 | struct macvlan_dev *vlan; | ||
| 57 | unsigned char addr[6+2] __aligned(sizeof(u16)); | ||
| 58 | struct rcu_head rcu; | ||
| 50 | }; | 59 | }; |
| 51 | 60 | ||
| 52 | struct macvlan_skb_cb { | 61 | struct macvlan_skb_cb { |
| @@ -57,6 +66,20 @@ struct macvlan_skb_cb { | |||
| 57 | 66 | ||
| 58 | static void macvlan_port_destroy(struct net_device *dev); | 67 | static void macvlan_port_destroy(struct net_device *dev); |
| 59 | 68 | ||
| 69 | /* Hash Ethernet address */ | ||
| 70 | static u32 macvlan_eth_hash(const unsigned char *addr) | ||
| 71 | { | ||
| 72 | u64 value = get_unaligned((u64 *)addr); | ||
| 73 | |||
| 74 | /* only want 6 bytes */ | ||
| 75 | #ifdef __BIG_ENDIAN | ||
| 76 | value >>= 16; | ||
| 77 | #else | ||
| 78 | value <<= 16; | ||
| 79 | #endif | ||
| 80 | return hash_64(value, MACVLAN_HASH_BITS); | ||
| 81 | } | ||
| 82 | |||
| 60 | static struct macvlan_port *macvlan_port_get_rcu(const struct net_device *dev) | 83 | static struct macvlan_port *macvlan_port_get_rcu(const struct net_device *dev) |
| 61 | { | 84 | { |
| 62 | return rcu_dereference(dev->rx_handler_data); | 85 | return rcu_dereference(dev->rx_handler_data); |
| @@ -73,20 +96,68 @@ static struct macvlan_dev *macvlan_hash_lookup(const struct macvlan_port *port, | |||
| 73 | const unsigned char *addr) | 96 | const unsigned char *addr) |
| 74 | { | 97 | { |
| 75 | struct macvlan_dev *vlan; | 98 | struct macvlan_dev *vlan; |
| 99 | u32 idx = macvlan_eth_hash(addr); | ||
| 76 | 100 | ||
| 77 | hlist_for_each_entry_rcu(vlan, &port->vlan_hash[addr[5]], hlist) { | 101 | hlist_for_each_entry_rcu(vlan, &port->vlan_hash[idx], hlist) { |
| 78 | if (ether_addr_equal_64bits(vlan->dev->dev_addr, addr)) | 102 | if (ether_addr_equal_64bits(vlan->dev->dev_addr, addr)) |
| 79 | return vlan; | 103 | return vlan; |
| 80 | } | 104 | } |
| 81 | return NULL; | 105 | return NULL; |
| 82 | } | 106 | } |
| 83 | 107 | ||
| 108 | static struct macvlan_source_entry *macvlan_hash_lookup_source( | ||
| 109 | const struct macvlan_dev *vlan, | ||
| 110 | const unsigned char *addr) | ||
| 111 | { | ||
| 112 | struct macvlan_source_entry *entry; | ||
| 113 | u32 idx = macvlan_eth_hash(addr); | ||
| 114 | struct hlist_head *h = &vlan->port->vlan_source_hash[idx]; | ||
| 115 | |||
| 116 | hlist_for_each_entry_rcu(entry, h, hlist) { | ||
| 117 | if (ether_addr_equal_64bits(entry->addr, addr) && | ||
| 118 | entry->vlan == vlan) | ||
| 119 | return entry; | ||
| 120 | } | ||
| 121 | return NULL; | ||
| 122 | } | ||
| 123 | |||
| 124 | static int macvlan_hash_add_source(struct macvlan_dev *vlan, | ||
| 125 | const unsigned char *addr) | ||
| 126 | { | ||
| 127 | struct macvlan_port *port = vlan->port; | ||
| 128 | struct macvlan_source_entry *entry; | ||
| 129 | struct hlist_head *h; | ||
| 130 | |||
| 131 | entry = macvlan_hash_lookup_source(vlan, addr); | ||
| 132 | if (entry) | ||
| 133 | return 0; | ||
| 134 | |||
| 135 | entry = kmalloc(sizeof(*entry), GFP_KERNEL); | ||
| 136 | if (!entry) | ||
| 137 | return -ENOMEM; | ||
| 138 | |||
| 139 | ether_addr_copy(entry->addr, addr); | ||
| 140 | entry->vlan = vlan; | ||
| 141 | h = &port->vlan_source_hash[macvlan_eth_hash(addr)]; | ||
| 142 | hlist_add_head_rcu(&entry->hlist, h); | ||
| 143 | vlan->macaddr_count++; | ||
| 144 | |||
| 145 | return 0; | ||
| 146 | } | ||
| 147 | |||
| 84 | static void macvlan_hash_add(struct macvlan_dev *vlan) | 148 | static void macvlan_hash_add(struct macvlan_dev *vlan) |
| 85 | { | 149 | { |
| 86 | struct macvlan_port *port = vlan->port; | 150 | struct macvlan_port *port = vlan->port; |
| 87 | const unsigned char *addr = vlan->dev->dev_addr; | 151 | const unsigned char *addr = vlan->dev->dev_addr; |
| 152 | u32 idx = macvlan_eth_hash(addr); | ||
| 88 | 153 | ||
| 89 | hlist_add_head_rcu(&vlan->hlist, &port->vlan_hash[addr[5]]); | 154 | hlist_add_head_rcu(&vlan->hlist, &port->vlan_hash[idx]); |
| 155 | } | ||
| 156 | |||
| 157 | static void macvlan_hash_del_source(struct macvlan_source_entry *entry) | ||
| 158 | { | ||
| 159 | hlist_del_rcu(&entry->hlist); | ||
| 160 | kfree_rcu(entry, rcu); | ||
| 90 | } | 161 | } |
| 91 | 162 | ||
| 92 | static void macvlan_hash_del(struct macvlan_dev *vlan, bool sync) | 163 | static void macvlan_hash_del(struct macvlan_dev *vlan, bool sync) |
| @@ -267,6 +338,65 @@ err: | |||
| 267 | atomic_long_inc(&skb->dev->rx_dropped); | 338 | atomic_long_inc(&skb->dev->rx_dropped); |
| 268 | } | 339 | } |
| 269 | 340 | ||
| 341 | static void macvlan_flush_sources(struct macvlan_port *port, | ||
| 342 | struct macvlan_dev *vlan) | ||
| 343 | { | ||
| 344 | int i; | ||
| 345 | |||
| 346 | for (i = 0; i < MACVLAN_HASH_SIZE; i++) { | ||
| 347 | struct hlist_node *h, *n; | ||
| 348 | |||
| 349 | hlist_for_each_safe(h, n, &port->vlan_source_hash[i]) { | ||
| 350 | struct macvlan_source_entry *entry; | ||
| 351 | |||
| 352 | entry = hlist_entry(h, struct macvlan_source_entry, | ||
| 353 | hlist); | ||
| 354 | if (entry->vlan == vlan) | ||
| 355 | macvlan_hash_del_source(entry); | ||
| 356 | } | ||
| 357 | } | ||
| 358 | vlan->macaddr_count = 0; | ||
| 359 | } | ||
| 360 | |||
| 361 | static void macvlan_forward_source_one(struct sk_buff *skb, | ||
| 362 | struct macvlan_dev *vlan) | ||
| 363 | { | ||
| 364 | struct sk_buff *nskb; | ||
| 365 | struct net_device *dev; | ||
| 366 | int len; | ||
| 367 | int ret; | ||
| 368 | |||
| 369 | dev = vlan->dev; | ||
| 370 | if (unlikely(!(dev->flags & IFF_UP))) | ||
| 371 | return; | ||
| 372 | |||
| 373 | nskb = skb_clone(skb, GFP_ATOMIC); | ||
| 374 | if (!nskb) | ||
| 375 | return; | ||
| 376 | |||
| 377 | len = nskb->len + ETH_HLEN; | ||
| 378 | nskb->dev = dev; | ||
| 379 | nskb->pkt_type = PACKET_HOST; | ||
| 380 | |||
| 381 | ret = netif_rx(nskb); | ||
| 382 | macvlan_count_rx(vlan, len, ret == NET_RX_SUCCESS, 0); | ||
| 383 | } | ||
| 384 | |||
| 385 | static void macvlan_forward_source(struct sk_buff *skb, | ||
| 386 | struct macvlan_port *port, | ||
| 387 | const unsigned char *addr) | ||
| 388 | { | ||
| 389 | struct macvlan_source_entry *entry; | ||
| 390 | u32 idx = macvlan_eth_hash(addr); | ||
| 391 | struct hlist_head *h = &port->vlan_source_hash[idx]; | ||
| 392 | |||
| 393 | hlist_for_each_entry_rcu(entry, h, hlist) { | ||
| 394 | if (ether_addr_equal_64bits(entry->addr, addr)) | ||
| 395 | if (entry->vlan->dev->flags & IFF_UP) | ||
