From e69ff734e15eb7f61621f8764ce0a2181823a737 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Fri, 8 Jun 2007 16:26:08 +1000 Subject: [CRYPTO] cipher: Remove obsolete fields from cipher_tfm This removes all the unused block cipher fields from cipher_tfm. Signed-off-by: Herbert Xu --- include/linux/crypto.h | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'include/linux') diff --git a/include/linux/crypto.h b/include/linux/crypto.h index 0de7e2ace82..357e8cfedc3 100644 --- a/include/linux/crypto.h +++ b/include/linux/crypto.h @@ -295,28 +295,8 @@ struct blkcipher_tfm { }; struct cipher_tfm { - void *cit_iv; - unsigned int cit_ivsize; - u32 cit_mode; int (*cit_setkey)(struct crypto_tfm *tfm, const u8 *key, unsigned int keylen); - int (*cit_encrypt)(struct crypto_tfm *tfm, - struct scatterlist *dst, - struct scatterlist *src, - unsigned int nbytes); - int (*cit_encrypt_iv)(struct crypto_tfm *tfm, - struct scatterlist *dst, - struct scatterlist *src, - unsigned int nbytes, u8 *iv); - int (*cit_decrypt)(struct crypto_tfm *tfm, - struct scatterlist *dst, - struct scatterlist *src, - unsigned int nbytes); - int (*cit_decrypt_iv)(struct crypto_tfm *tfm, - struct scatterlist *dst, - struct scatterlist *src, - unsigned int nbytes, u8 *iv); - void (*cit_xor_block)(u8 *dst, const u8 *src); void (*cit_encrypt_one)(struct crypto_tfm *tfm, u8 *dst, const u8 *src); void (*cit_decrypt_one)(struct crypto_tfm *tfm, u8 *dst, const u8 *src); }; -- cgit v1.2.3 From 5628221caf88e2a052782b042e12da7cd34111b0 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Tue, 10 Jul 2007 19:32:10 +0200 Subject: [PATCH] mac80211: ERP IE handling improvements The "protection needed" flag is currently parsed out of the ERP IE in beacons. This patch allows the ERP IE to be available at assocation time and causes the appropriate actions to be performed earlier. It is slightly complicated by the fact that most APs don't include the ERP IE in association responses. To work around this, we store ERP values in the ieee80211_sta_bss structure. Also added some WLAN_ERP defines for use by upcoming patches. Signed-off-by: Jiri Benc Signed-off-by: John W. Linville --- include/linux/ieee80211.h | 11 +++++++ net/mac80211/ieee80211_i.h | 6 ++++ net/mac80211/ieee80211_sta.c | 69 ++++++++++++++++++++++++++++++++------------ 3 files changed, 67 insertions(+), 19 deletions(-) (limited to 'include/linux') diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index ecd61e8438a..272f8c8c90d 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -227,6 +227,17 @@ struct ieee80211_cts { #define WLAN_CAPABILITY_SHORT_SLOT_TIME (1<<10) #define WLAN_CAPABILITY_DSSS_OFDM (1<<13) +/* 802.11g ERP information element */ +#define WLAN_ERP_NON_ERP_PRESENT (1<<0) +#define WLAN_ERP_USE_PROTECTION (1<<1) +#define WLAN_ERP_BARKER_PREAMBLE (1<<2) + +/* WLAN_ERP_BARKER_PREAMBLE values */ +enum { + WLAN_ERP_PREAMBLE_SHORT = 0, + WLAN_ERP_PREAMBLE_LONG = 1, +}; + /* Status codes */ enum ieee80211_statuscode { WLAN_STATUS_SUCCESS = 0, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index b222a9afd4e..99ff7c5e920 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -99,6 +99,12 @@ struct ieee80211_sta_bss { int probe_resp; unsigned long last_update; + /* during assocation, we save an ERP value from a probe response so + * that we can feed ERP info to the driver when handling the + * association completes. these fields probably won't be up-to-date + * otherwise, you probably don't want to use them. */ + int has_erp_value; + u8 erp_value; }; diff --git a/net/mac80211/ieee80211_sta.c b/net/mac80211/ieee80211_sta.c index f3ca83743b4..df6c410de16 100644 --- a/net/mac80211/ieee80211_sta.c +++ b/net/mac80211/ieee80211_sta.c @@ -314,6 +314,27 @@ static void ieee80211_sta_wmm_params(struct net_device *dev, } +static void ieee80211_handle_erp_ie(struct net_device *dev, u8 erp_value) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_if_sta *ifsta = &sdata->u.sta; + int use_protection = (erp_value & WLAN_ERP_USE_PROTECTION) != 0; + + if (use_protection != !!ifsta->use_protection) { + if (net_ratelimit()) { + printk(KERN_DEBUG "%s: CTS protection %s (BSSID=" + MAC_FMT ")\n", + dev->name, + use_protection ? "enabled" : "disabled", + MAC_ARG(ifsta->bssid)); + } + ifsta->use_protection = use_protection ? 1 : 0; + local->cts_protect_erp_frames = use_protection; + } +} + + static void ieee80211_sta_send_associnfo(struct net_device *dev, struct ieee80211_if_sta *ifsta) { @@ -377,9 +398,18 @@ static void ieee80211_set_associated(struct net_device *dev, if (assoc) { struct ieee80211_sub_if_data *sdata; + struct ieee80211_sta_bss *bss; sdata = IEEE80211_DEV_TO_SUB_IF(dev); if (sdata->type != IEEE80211_IF_TYPE_STA) return; + + bss = ieee80211_rx_bss_get(dev, ifsta->bssid); + if (bss) { + if (bss->has_erp_value) + ieee80211_handle_erp_ie(dev, bss->erp_value); + ieee80211_rx_bss_put(dev, bss); + } + netif_carrier_on(dev); ifsta->prev_bssid_set = 1; memcpy(ifsta->prev_bssid, sdata->u.sta.bssid, ETH_ALEN); @@ -1177,6 +1207,18 @@ static void ieee80211_rx_mgmt_assoc_resp(struct net_device *dev, return; } + /* it probably doesn't, but if the frame includes an ERP value then + * update our stored copy */ + if (elems.erp_info && elems.erp_info_len >= 1) { + struct ieee80211_sta_bss *bss + = ieee80211_rx_bss_get(dev, ifsta->bssid); + if (bss) { + bss->erp_value = elems.erp_info[0]; + bss->has_erp_value = 1; + ieee80211_rx_bss_put(dev, bss); + } + } + printk(KERN_DEBUG "%s: associated\n", dev->name); ifsta->aid = aid; ifsta->ap_capab = capab_info; @@ -1499,6 +1541,12 @@ static void ieee80211_rx_bss_info(struct net_device *dev, return; } + /* save the ERP value so that it is available at association time */ + if (elems.erp_info && elems.erp_info_len >= 1) { + bss->erp_value = elems.erp_info[0]; + bss->has_erp_value = 1; + } + bss->beacon_int = le16_to_cpu(mgmt->u.beacon.beacon_int); bss->capability = le16_to_cpu(mgmt->u.beacon.capab_info); if (elems.ssid && elems.ssid_len <= IEEE80211_MAX_SSID_LEN) { @@ -1614,10 +1662,8 @@ static void ieee80211_rx_mgmt_beacon(struct net_device *dev, size_t len, struct ieee80211_rx_status *rx_status) { - struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct ieee80211_sub_if_data *sdata; struct ieee80211_if_sta *ifsta; - int use_protection; size_t baselen; struct ieee802_11_elems elems; @@ -1641,23 +1687,8 @@ static void ieee80211_rx_mgmt_beacon(struct net_device *dev, &elems) == ParseFailed) return; - use_protection = 0; - if (elems.erp_info && elems.erp_info_len >= 1) { - use_protection = - (elems.erp_info[0] & ERP_INFO_USE_PROTECTION) != 0; - } - - if (use_protection != !!ifsta->use_protection) { - if (net_ratelimit()) { - printk(KERN_DEBUG "%s: CTS protection %s (BSSID=" - MAC_FMT ")\n", - dev->name, - use_protection ? "enabled" : "disabled", - MAC_ARG(ifsta->bssid)); - } - ifsta->use_protection = use_protection ? 1 : 0; - local->cts_protect_erp_frames = use_protection; - } + if (elems.erp_info && elems.erp_info_len >= 1) + ieee80211_handle_erp_ie(dev, elems.erp_info[0]); if (elems.wmm_param && ifsta->wmm_enabled) { ieee80211_sta_wmm_params(dev, ifsta, elems.wmm_param, -- cgit v1.2.3 From 24023451c8df726692e2f52288a20870d13b501f Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Sat, 14 Jul 2007 18:51:31 -0700 Subject: [NET]: Add net_device change_rx_mode callback Currently the set_multicast_list (and set_rx_mode) callbacks are responsible for configuring the device according to the IFF_PROMISC, IFF_MULTICAST and IFF_ALLMULTI flags and the mc_list (and uc_list in case of set_rx_mode). These callbacks can be invoked from BH context without the rtnl_mutex by dev_mc_add/dev_mc_delete, which makes reading the device flags and promiscous/allmulti count racy. For real hardware drivers that just commit all changes to the hardware this is not a real problem since the stack guarantees to call them for every change, so at least the final call will not race and commit the correct configuration to the hardware. For software devices that want to synchronize promiscous and multicast state to an underlying device however this can cause corruption of the underlying device's flags or promisc/allmulti counts. When the software device is concurrently put in promiscous or allmulti mode while set_multicast_list is invoked from bottem half context, the device might synchronize the change to the underlying device without holding the rtnl_mutex, which races with concurrent changes to the underlying device. Add a dev->change_rx_flags hook that is invoked when any of the flags that affect rx filtering change (under the rtnl_mutex), which allows drivers to perform synchronization immediately and only synchronize the address lists in set_multicast_list/set_rx_mode. Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- include/linux/netdevice.h | 3 +++ net/core/dev.c | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 79cc3dab4be..f193aba3038 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -516,6 +516,9 @@ struct net_device void *saddr, unsigned len); int (*rebuild_header)(struct sk_buff *skb); +#define HAVE_CHANGE_RX_FLAGS + void (*change_rx_flags)(struct net_device *dev, + int flags); #define HAVE_SET_RX_MODE void (*set_rx_mode)(struct net_device *dev); #define HAVE_MULTICAST diff --git a/net/core/dev.c b/net/core/dev.c index 96443055324..59ec811d2b5 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2521,6 +2521,8 @@ static void __dev_set_promiscuity(struct net_device *dev, int inc) { unsigned short old_flags = dev->flags; + ASSERT_RTNL(); + if ((dev->promiscuity += inc) == 0) dev->flags &= ~IFF_PROMISC; else @@ -2535,6 +2537,9 @@ static void __dev_set_promiscuity(struct net_device *dev, int inc) dev->name, (dev->flags & IFF_PROMISC), (old_flags & IFF_PROMISC), audit_get_loginuid(current->audit_context)); + + if (dev->change_rx_flags) + dev->change_rx_flags(dev, IFF_PROMISC); } } @@ -2573,11 +2578,16 @@ void dev_set_allmulti(struct net_device *dev, int inc) { unsigned short old_flags = dev->flags; + ASSERT_RTNL(); + dev->flags |= IFF_ALLMULTI; if ((dev->allmulti += inc) == 0) dev->flags &= ~IFF_ALLMULTI; - if (dev->flags ^ old_flags) + if (dev->flags ^ old_flags) { + if (dev->change_rx_flags) + dev->change_rx_flags(dev, IFF_ALLMULTI); dev_set_rx_mode(dev); + } } /* @@ -2778,6 +2788,8 @@ int dev_change_flags(struct net_device *dev, unsigned flags) int ret, changes; int old_flags = dev->flags; + ASSERT_RTNL(); + /* * Set the flags on our device. */ @@ -2792,6 +2804,9 @@ int dev_change_flags(struct net_device *dev, unsigned flags) * Load in the correct multicast list now the flags have changed. */ + if (dev->change_rx_flags && (dev->flags ^ flags) & IFF_MULTICAST) + dev->change_rx_flags(dev, IFF_MULTICAST); + dev_set_rx_mode(dev); /* -- cgit v1.2.3 From a0a400d79e3dd7843e7e81baa3ef2957bdc292d0 Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Sat, 14 Jul 2007 18:52:02 -0700 Subject: [NET]: dev_mcast: add multicast list synchronization helpers The method drivers currently use to synchronize multicast lists is not very pretty: - walk the multicast list - search each entry on a copy of the previous list - if new add to lower device - walk the copy of the previous list - search each entry on the current list - if removed delete from lower device - copy entire list This patch adds a new field to struct dev_addr_list to store the synchronization state and adds two helper functions for synchronization and cleanup. Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- include/linux/netdevice.h | 3 ++ net/core/dev_mcast.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) (limited to 'include/linux') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index f193aba3038..e5af458ab04 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -190,6 +190,7 @@ struct dev_addr_list struct dev_addr_list *next; u8 da_addr[MAX_ADDR_LEN]; u8 da_addrlen; + u8 da_synced; int da_users; int da_gusers; }; @@ -1103,6 +1104,8 @@ extern int dev_unicast_delete(struct net_device *dev, void *addr, int alen); extern int dev_unicast_add(struct net_device *dev, void *addr, int alen); extern int dev_mc_delete(struct net_device *dev, void *addr, int alen, int all); extern int dev_mc_add(struct net_device *dev, void *addr, int alen, int newonly); +extern int dev_mc_sync(struct net_device *to, struct net_device *from); +extern void dev_mc_unsync(struct net_device *to, struct net_device *from); extern void dev_mc_discard(struct net_device *dev); extern int __dev_addr_delete(struct dev_addr_list **list, int *count, void *addr, int alen, int all); extern int __dev_addr_add(struct dev_addr_list **list, int *count, void *addr, int alen, int newonly); diff --git a/net/core/dev_mcast.c b/net/core/dev_mcast.c index aa38100601f..235a2a8a0d0 100644 --- a/net/core/dev_mcast.c +++ b/net/core/dev_mcast.c @@ -102,6 +102,81 @@ int dev_mc_add(struct net_device *dev, void *addr, int alen, int glbl) return err; } +/** + * dev_mc_sync - Synchronize device's multicast list to another device + * @to: destination device + * @from: source device + * + * Add newly added addresses to the destination device and release + * addresses that have no users left. The source device must be + * locked by netif_tx_lock_bh. + * + * This function is intended to be called from the dev->set_multicast_list + * function of layered software devices. + */ +int dev_mc_sync(struct net_device *to, struct net_device *from) +{ + struct dev_addr_list *da; + int err = 0; + + netif_tx_lock_bh(to); + for (da = from->mc_list; da != NULL; da = da->next) { + if (!da->da_synced) { + err = __dev_addr_add(&to->mc_list, &to->mc_count, + da->da_addr, da->da_addrlen, 0); + if (err < 0) + break; + da->da_synced = 1; + da->da_users++; + } else if (da->da_users == 1) { + __dev_addr_delete(&to->mc_list, &to->mc_count, + da->da_addr, da->da_addrlen, 0); + __dev_addr_delete(&from->mc_list, &from->mc_count, + da->da_addr, da->da_addrlen, 0); + } + } + if (!err) + __dev_set_rx_mode(to); + netif_tx_unlock_bh(to); + + return err; +} +EXPORT_SYMBOL(dev_mc_sync); + + +/** + * dev_mc_unsync - Remove synchronized addresses from the destination + * device + * @to: destination device + * @from: source device + * + * Remove all addresses that were added to the destination device by + * dev_mc_sync(). This function is intended to be called from the + * dev->stop function of layered software devices. + */ +void dev_mc_unsync(struct net_device *to, struct net_device *from) +{ + struct dev_addr_list *da; + + netif_tx_lock_bh(from); + netif_tx_lock_bh(to); + + for (da = from->mc_list; da != NULL; da = da->next) { + if (!da->da_synced) + continue; + __dev_addr_delete(&to->mc_list, &to->mc_count, + da->da_addr, da->da_addrlen, 0); + da->da_synced = 0; + __dev_addr_delete(&from->mc_list, &from->mc_count, + da->da_addr, da->da_addrlen, 0); + } + __dev_set_rx_mode(to); + + netif_tx_unlock_bh(to); + netif_tx_unlock_bh(from); +} +EXPORT_SYMBOL(dev_mc_unsync); + /* * Discard multicast list when a device is downed */ -- cgit v1.2.3 From 6c78dcbd47a68a7d25d2bee7a6c74b9136cb5fde Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Sat, 14 Jul 2007 18:52:56 -0700 Subject: [VLAN]: Fix promiscous/allmulti synchronization races The set_multicast_list function may be called without holding the rtnl mutex, resulting in races when changing the underlying device's promiscous and allmulti state. Use the change_rx_mode hook, which is always invoked under the rtnl. Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- include/linux/if_vlan.h | 2 -- net/8021q/vlan.c | 1 + net/8021q/vlan.h | 1 + net/8021q/vlan_dev.c | 38 ++++++++++++++++++++------------------ 4 files changed, 22 insertions(+), 20 deletions(-) (limited to 'include/linux') diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h index 61a57dc2ac9..7f71df4c952 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h @@ -132,8 +132,6 @@ struct vlan_dev_info { * made, in order to feed the right changes down * to the real hardware... */ - int old_allmulti; /* similar to above. */ - int old_promiscuity; /* similar to above. */ struct net_device *real_dev; /* the underlying device/interface */ unsigned char real_dev_addr[ETH_ALEN]; struct proc_dir_entry *dent; /* Holds the proc data */ diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c index abb9900edb3..39bdcc25c15 100644 --- a/net/8021q/vlan.c +++ b/net/8021q/vlan.c @@ -373,6 +373,7 @@ void vlan_setup(struct net_device *new_dev) new_dev->open = vlan_dev_open; new_dev->stop = vlan_dev_stop; new_dev->set_multicast_list = vlan_dev_set_multicast_list; + new_dev->change_rx_flags = vlan_change_rx_flags; new_dev->destructor = free_netdev; new_dev->do_ioctl = vlan_dev_ioctl; diff --git a/net/8021q/vlan.h b/net/8021q/vlan.h index 62ce1c519aa..7df5b293557 100644 --- a/net/8021q/vlan.h +++ b/net/8021q/vlan.h @@ -69,6 +69,7 @@ int vlan_dev_set_vlan_flag(const struct net_device *dev, u32 flag, short flag_val); void vlan_dev_get_realdev_name(const struct net_device *dev, char *result); void vlan_dev_get_vid(const struct net_device *dev, unsigned short *result); +void vlan_change_rx_flags(struct net_device *dev, int change); void vlan_dev_set_multicast_list(struct net_device *vlan_dev); int vlan_check_real_dev(struct net_device *real_dev, unsigned short vlan_id); diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index d4a62d1b52b..dec7e62b2e1 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -712,6 +712,11 @@ int vlan_dev_open(struct net_device *dev) } memcpy(vlan->real_dev_addr, real_dev->dev_addr, ETH_ALEN); + if (dev->flags & IFF_ALLMULTI) + dev_set_allmulti(real_dev, 1); + if (dev->flags & IFF_PROMISC) + dev_set_promiscuity(real_dev, 1); + return 0; } @@ -721,6 +726,11 @@ int vlan_dev_stop(struct net_device *dev) vlan_flush_mc_list(dev); + if (dev->flags & IFF_ALLMULTI) + dev_set_allmulti(real_dev, -1); + if (dev->flags & IFF_PROMISC) + dev_set_promiscuity(real_dev, -1); + if (compare_ether_addr(dev->dev_addr, real_dev->dev_addr)) dev_unicast_delete(real_dev, dev->dev_addr, dev->addr_len); @@ -754,34 +764,26 @@ int vlan_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) return err; } +void vlan_change_rx_flags(struct net_device *dev, int change) +{ + struct net_device *real_dev = VLAN_DEV_INFO(dev)->real_dev; + + if (change & IFF_ALLMULTI) + dev_set_allmulti(real_dev, dev->flags & IFF_ALLMULTI ? 1 : -1); + if (change & IFF_PROMISC) + dev_set_promiscuity(real_dev, dev->flags & IFF_PROMISC ? 1 : -1); +} + /** Taken from Gleb + Lennert's VLAN code, and modified... */ void vlan_dev_set_multicast_list(struct net_device *vlan_dev) { struct dev_mc_list *dmi; struct net_device *real_dev; - int inc; if (vlan_dev && (vlan_dev->priv_flags & IFF_802_1Q_VLAN)) { /* Then it's a real vlan device, as far as we can tell.. */ real_dev = VLAN_DEV_INFO(vlan_dev)->real_dev; - /* compare the current promiscuity to the last promisc we had.. */ - inc = vlan_dev->promiscuity - VLAN_DEV_INFO(vlan_dev)->old_promiscuity; - if (inc) { - printk(KERN_INFO "%s: dev_set_promiscuity(master, %d)\n", - vlan_dev->name, inc); - dev_set_promiscuity(real_dev, inc); /* found in dev.c */ - VLAN_DEV_INFO(vlan_dev)->old_promiscuity = vlan_dev->promiscuity; - } - - inc = vlan_dev->allmulti - VLAN_DEV_INFO(vlan_dev)->old_allmulti; - if (inc) { - printk(KERN_INFO "%s: dev_set_allmulti(master, %d)\n", - vlan_dev->name, inc); - dev_set_allmulti(real_dev, inc); /* dev.c */ - VLAN_DEV_INFO(vlan_dev)->old_allmulti = vlan_dev->allmulti; - } - /* looking for addresses to add to master's list */ for (dmi = vlan_dev->mc_list; dmi != NULL; dmi = dmi->next) { if (vlan_should_add_mc(dmi, VLAN_DEV_INFO(vlan_dev)->old_mc_list)) { -- cgit v1.2.3 From 56addd6eeeb4e11f5a0af7093ca078e0f29140e0 Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Sat, 14 Jul 2007 18:53:28 -0700 Subject: [VLAN]: Use multicast list synchronization helpers Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- include/linux/if_vlan.h | 5 -- net/8021q/vlan_dev.c | 131 +----------------------------------------------- 2 files changed, 2 insertions(+), 134 deletions(-) (limited to 'include/linux') diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h index 7f71df4c952..f8443fdb124 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h @@ -127,11 +127,6 @@ struct vlan_dev_info { * like DHCP that use packet-filtering and don't understand * 802.1Q */ - struct dev_mc_list *old_mc_list; /* old multi-cast list for the VLAN interface.. - * we save this so we can tell what changes were - * made, in order to feed the right changes down - * to the real hardware... - */ struct net_device *real_dev; /* the underlying device/interface */ unsigned char real_dev_addr[ETH_ALEN]; struct proc_dir_entry *dent; /* Holds the proc data */ diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index dec7e62b2e1..4d2aa4dd42a 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -612,90 +612,6 @@ void vlan_dev_get_vid(const struct net_device *dev, unsigned short *result) *result = VLAN_DEV_INFO(dev)->vlan_id; } -static inline int vlan_dmi_equals(struct dev_mc_list *dmi1, - struct dev_mc_list *dmi2) -{ - return ((dmi1->dmi_addrlen == dmi2->dmi_addrlen) && - (memcmp(dmi1->dmi_addr, dmi2->dmi_addr, dmi1->dmi_addrlen) == 0)); -} - -/** dmi is a single entry into a dev_mc_list, a single node. mc_list is - * an entire list, and we'll iterate through it. - */ -static int vlan_should_add_mc(struct dev_mc_list *dmi, struct dev_mc_list *mc_list) -{ - struct dev_mc_list *idmi; - - for (idmi = mc_list; idmi != NULL; ) { - if (vlan_dmi_equals(dmi, idmi)) { - if (dmi->dmi_users > idmi->dmi_users) - return 1; - else - return 0; - } else { - idmi = idmi->next; - } - } - - return 1; -} - -static inline void vlan_destroy_mc_list(struct dev_mc_list *mc_list) -{ - struct dev_mc_list *dmi = mc_list; - struct dev_mc_list *next; - - while(dmi) { - next = dmi->next; - kfree(dmi); - dmi = next; - } -} - -static void vlan_copy_mc_list(struct dev_mc_list *mc_list, struct vlan_dev_info *vlan_info) -{ - struct dev_mc_list *dmi, *new_dmi; - - vlan_destroy_mc_list(vlan_info->old_mc_list); - vlan_info->old_mc_list = NULL; - - for (dmi = mc_list; dmi != NULL; dmi = dmi->next) { - new_dmi = kmalloc(sizeof(*new_dmi), GFP_ATOMIC); - if (new_dmi == NULL) { - printk(KERN_ERR "vlan: cannot allocate memory. " - "Multicast may not work properly from now.\n"); - return; - } - - /* Copy whole structure, then make new 'next' pointer */ - *new_dmi = *dmi; - new_dmi->next = vlan_info->old_mc_list; - vlan_info->old_mc_list = new_dmi; - } -} - -static void vlan_flush_mc_list(struct net_device *dev) -{ - struct dev_mc_list *dmi = dev->mc_list; - - while (dmi) { - printk(KERN_DEBUG "%s: del %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address from vlan interface\n", - dev->name, - dmi->dmi_addr[0], - dmi->dmi_addr[1], - dmi->dmi_addr[2], - dmi->dmi_addr[3], - dmi->dmi_addr[4], - dmi->dmi_addr[5]); - dev_mc_delete(dev, dmi->dmi_addr, dmi->dmi_addrlen, 0); - dmi = dev->mc_list; - } - - /* dev->mc_list is NULL by the time we get here. */ - vlan_destroy_mc_list(VLAN_DEV_INFO(dev)->old_mc_list); - VLAN_DEV_INFO(dev)->old_mc_list = NULL; -} - int vlan_dev_open(struct net_device *dev) { struct vlan_dev_info *vlan = VLAN_DEV_INFO(dev); @@ -724,8 +640,7 @@ int vlan_dev_stop(struct net_device *dev) { struct net_device *real_dev = VLAN_DEV_INFO(dev)->real_dev; - vlan_flush_mc_list(dev); - + dev_mc_unsync(real_dev, dev); if (dev->flags & IFF_ALLMULTI) dev_set_allmulti(real_dev, -1); if (dev->flags & IFF_PROMISC) @@ -777,47 +692,5 @@ void vlan_change_rx_flags(struct net_device *dev, int change) /** Taken from Gleb + Lennert's VLAN code, and modified... */ void vlan_dev_set_multicast_list(struct net_device *vlan_dev) { - struct dev_mc_list *dmi; - struct net_device *real_dev; - - if (vlan_dev && (vlan_dev->priv_flags & IFF_802_1Q_VLAN)) { - /* Then it's a real vlan device, as far as we can tell.. */ - real_dev = VLAN_DEV_INFO(vlan_dev)->real_dev; - - /* looking for addresses to add to master's list */ - for (dmi = vlan_dev->mc_list; dmi != NULL; dmi = dmi->next) { - if (vlan_should_add_mc(dmi, VLAN_DEV_INFO(vlan_dev)->old_mc_list)) { - dev_mc_add(real_dev, dmi->dmi_addr, dmi->dmi_addrlen, 0); - printk(KERN_DEBUG "%s: add %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address to master interface\n", - vlan_dev->name, - dmi->dmi_addr[0], - dmi->dmi_addr[1], - dmi->dmi_addr[2], - dmi->dmi_addr[3], - dmi->dmi_addr[4], - dmi->dmi_addr[5]); - } - } - - /* looking for addresses to delete from master's list */ - for (dmi = VLAN_DEV_INFO(vlan_dev)->old_mc_list; dmi != NULL; dmi = dmi->next) { - if (vlan_should_add_mc(dmi, vlan_dev->mc_list)) { - /* if we think we should add it to the new list, then we should really - * delete it from the real list on the underlying device. - */ - dev_mc_delete(real_dev, dmi->dmi_addr, dmi->dmi_addrlen, 0); - printk(KERN_DEBUG "%s: del %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address from master interface\n", - vlan_dev->name, - dmi->dmi_addr[0], - dmi->dmi_addr[1], - dmi->dmi_addr[2], - dmi->dmi_addr[3], - dmi->dmi_addr[4], - dmi->dmi_addr[5]); - } - } - - /* save multicast list */ - vlan_copy_mc_list(vlan_dev->mc_list, VLAN_DEV_INFO(vlan_dev)); - } + dev_mc_sync(VLAN_DEV_INFO(vlan_dev)->real_dev, vlan_dev); } -- cgit v1.2.3 From b863ceb7ddcea8c55fcf1d7b2ac591d50aa7ed53 Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Sat, 14 Jul 2007 18:55:06 -0700 Subject: [NET]: Add macvlan driver Add macvlan driver, which allows to create virtual ethernet devices based on MAC address. Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- MAINTAINERS | 6 + drivers/net/Kconfig | 10 + drivers/net/Makefile | 1 + drivers/net/macvlan.c | 496 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/if_macvlan.h | 9 + include/linux/netdevice.h | 2 + net/core/dev.c | 26 +++ 7 files changed, 550 insertions(+) create mode 100644 drivers/net/macvlan.c create mode 100644 include/linux/if_macvlan.h (limited to 'include/linux') diff --git a/MAINTAINERS b/MAINTAINERS index 845fbf4478b..360eb581953 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2330,6 +2330,12 @@ W: http://linuxwireless.org/ T: git kernel.org:/pub/scm/linux/kernel/git/jbenc/mac80211.git S: Maintained +MACVLAN DRIVER +P: Patrick McHardy +M: kaber@trash.net +L: netdev@vger.kernel.org +S: Maintained + MARVELL YUKON / SYSKONNECT DRIVER P: Mirko Lindner M: mlindner@syskonnect.de diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index ba314adf68b..d17d64eb706 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -82,6 +82,16 @@ config BONDING To compile this driver as a module, choose M here: the module will be called bonding. +config MACVLAN + tristate "MAC-VLAN support (EXPERIMENTAL)" + depends on EXPERIMENTAL + ---help--- + This allows one to create virtual interfaces that map packets to + or from specific MAC addresses to a particular interface. + + To compile this driver as a module, choose M here: the module + will be called macvlan. + config EQUALIZER tristate "EQL (serial line load balancing) support" ---help--- diff --git a/drivers/net/Makefile b/drivers/net/Makefile index a2241e6e145..c26b8674213 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_SLHC) += slhc.o obj-$(CONFIG_DUMMY) += dummy.o obj-$(CONFIG_IFB) += ifb.o +obj-$(CONFIG_MACVLAN) += macvlan.o obj-$(CONFIG_DE600) += de600.o obj-$(CONFIG_DE620) += de620.o obj-$(CONFIG_LANCE) += lance.o diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c new file mode 100644 index 00000000000..dc74d006e01 --- /dev/null +++ b/drivers/net/macvlan.c @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2007 Patrick McHardy + * + * 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. + * + * The code this is based on carried the following copyright notice: + * --- + * (C) Copyright 2001-2006 + * Alex Zeffertt, Cambridge Broadband Ltd, ajz@cambridgebroadband.com + * Re-worked by Ben Greear + * --- + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MACVLAN_HASH_SIZE (1 << BITS_PER_BYTE) + +struct macvlan_port { + struct net_device *dev; + struct hlist_head vlan_hash[MACVLAN_HASH_SIZE]; + struct list_head vlans; +}; + +struct macvlan_dev { + struct net_device *dev; + struct list_head list; + struct hlist_node hlist; + struct macvlan_port *port; + struct net_device *lowerdev; +}; + + +static struct macvlan_dev *macvlan_hash_lookup(const struct macvlan_port *port, + const unsigned char *addr) +{ + struct macvlan_dev *vlan; + struct hlist_node *n; + + hlist_for_each_entry_rcu(vlan, n, &port->vlan_hash[addr[5]], hlist) { + if (!compare_ether_addr(vlan->dev->dev_addr, addr)) + return vlan; + } + return NULL; +} + +static void macvlan_broadcast(struct sk_buff *skb, + const struct macvlan_port *port) +{ + const struct ethhdr *eth = eth_hdr(skb); + const struct macvlan_dev *vlan; + struct hlist_node *n; + struct net_device *dev; + struct sk_buff *nskb; + unsigned int i; + + for (i = 0; i < MACVLAN_HASH_SIZE; i++) { + hlist_for_each_entry_rcu(vlan, n, &port->vlan_hash[i], hlist) { + dev = vlan->dev; + if (unlikely(!(dev->flags & IFF_UP))) + continue; + + nskb = skb_clone(skb, GFP_ATOMIC); + if (nskb == NULL) { + dev->stats.rx_errors++; + dev->stats.rx_dropped++; + continue; + } + + dev->stats.rx_bytes += skb->len + ETH_HLEN; + dev->stats.rx_packets++; + dev->stats.multicast++; + dev->last_rx = jiffies; + + nskb->dev = dev; + if (!compare_ether_addr(eth->h_dest, dev->broadcast)) + nskb->pkt_type = PACKET_BROADCAST; + else + nskb->pkt_type = PACKET_MULTICAST; + + netif_rx(nskb); + } + } +} + +/* called under rcu_read_lock() from netif_receive_skb */ +static struct sk_buff *macvlan_handle_frame(struct sk_buff *skb) +{ + const struct ethhdr *eth = eth_hdr(skb); + const struct macvlan_port *port; + const struct macvlan_dev *vlan; + struct net_device *dev; + + port = rcu_dereference(skb->dev->macvlan_port); + if (port == NULL) + return skb; + + if (is_multicast_ether_addr(eth->h_dest)) { + macvlan_broadcast(skb, port); + return skb; + } + + vlan = macvlan_hash_lookup(port, eth->h_dest); + if (vlan == NULL) + return skb; + + dev = vlan->dev; + if (unlikely(!(dev->flags & IFF_UP))) { + kfree_skb(skb); + return NULL; + } + + skb = skb_share_check(skb, GFP_ATOMIC); + if (skb == NULL) { + dev->stats.rx_errors++; + dev->stats.rx_dropped++; + return NULL; + } + + dev->stats.rx_bytes += skb->len + ETH_HLEN; + dev->stats.rx_packets++; + dev->last_rx = jiffies; + + skb->dev = dev; + skb->pkt_type = PACKET_HOST; + + netif_rx(skb); + return NULL; +} + +static int macvlan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + const struct macvlan_dev *vlan = netdev_priv(dev); + unsigned int len = skb->len; + int ret; + + skb->dev = vlan->lowerdev; + ret = dev_queue_xmit(skb); + + if (likely(ret == NET_XMIT_SUCCESS)) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += len; + } else { + dev->stats.tx_errors++; + dev->stats.tx_aborted_errors++; + } + return NETDEV_TX_OK; +} + +static int macvlan_hard_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, void *daddr, void *saddr, + unsigned len) +{ + const struct macvlan_dev *vlan = netdev_priv(dev); + struct net_device *lowerdev = vlan->lowerdev; + + return lowerdev->hard_header(skb, lowerdev, type, daddr, + saddr ? : dev->dev_addr, len); +} + +static int macvlan_open(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + struct macvlan_port *port = vlan->port; + struct net_device *lowerdev = vlan->lowerdev; + int err; + + err = dev_unicast_add(lowerdev, dev->dev_addr, ETH_ALEN); + if (err < 0) + return err; + if (dev->flags & IFF_ALLMULTI) + dev_set_allmulti(lowerdev, 1); + + hlist_add_head_rcu(&vlan->hlist, &port->vlan_hash[dev->dev_addr[5]]); + return 0; +} + +static int macvlan_stop(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + struct net_device *lowerdev = vlan->lowerdev; + + dev_mc_unsync(lowerdev, dev); + if (dev->flags & IFF_ALLMULTI) + dev_set_allmulti(lowerdev, -1); + + dev_unicast_delete(lowerdev, dev->dev_addr, ETH_ALEN); + + hlist_del_rcu(&vlan->hlist); + synchronize_rcu(); + return 0; +} + +static void macvlan_change_rx_flags(struct net_device *dev, int change) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + struct net_device *lowerdev = vlan->lowerdev; + + if (change & IFF_ALLMULTI) + dev_set_allmulti(lowerdev, dev->flags & IFF_ALLMULTI ? 1 : -1); +} + +static void macvlan_set_multicast_list(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + + dev_mc_sync(vlan->lowerdev, dev); +} + +static int macvlan_change_mtu(struct net_device *dev, int new_mtu) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + + if (new_mtu < 68 || vlan->lowerdev->mtu < new_mtu) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + +/* + * macvlan network devices have devices nesting below it and are a special + * "super class" of normal network devices; split their locks off into a + * separate class since they always nest. + */ +static struct lock_class_key macvlan_netdev_xmit_lock_key; + +#define MACVLAN_FEATURES \ + (NETIF_F_SG | NETIF_F_ALL_CSUM | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST | \ + NETIF_F_GSO | NETIF_F_TSO | NETIF_F_UFO | NETIF_F_GSO_ROBUST | \ + NETIF_F_TSO_ECN | NETIF_F_TSO6) + +#define MACVLAN_STATE_MASK \ + ((1<<__LINK_STATE_NOCARRIER) | (1<<__LINK_STATE_DORMANT)) + +static int macvlan_init(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + const struct net_device *lowerdev = vlan->lowerdev; + + dev->state = (dev->state & ~MACVLAN_STATE_MASK) | + (lowerdev->state & MACVLAN_STATE_MASK); + dev->features = lowerdev->features & MACVLAN_FEATURES; + dev->iflink = lowerdev->ifindex; + + lockdep_set_class(&dev->_xmit_lock, &macvlan_netdev_xmit_lock_key); + return 0; +} + +static void macvlan_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *drvinfo) +{ + snprintf(drvinfo->driver, 32, "macvlan"); + snprintf(drvinfo->version, 32, "0.1"); +} + +static u32 macvlan_ethtool_get_rx_csum(struct net_device *dev) +{ + const struct macvlan_dev *vlan = netdev_priv(dev); + struct net_device *lowerdev = vlan->lowerdev; + + if (lowerdev->ethtool_ops->get_rx_csum == NULL) + return 0; + return lowerdev->ethtool_ops->get_rx_csum(lowerdev); +} + +static const struct ethtool_ops macvlan_ethtool_ops = { + .get_link = ethtool_op_get_link, + .get_rx_csum = macvlan_ethtool_get_rx_csum, + .get_tx_csum = ethtool_op_get_tx_csum, + .get_tso = ethtool_op_get_tso, + .get_ufo = ethtool_op_get_ufo, + .get_sg = ethtool_op_get_sg, + .get_drvinfo = macvlan_ethtool_get_drvinfo, +}; + +static void macvlan_setup(struct net_device *dev) +{ + ether_setup(dev); + + dev->init = macvlan_init; + dev->open = macvlan_open; + dev->stop = macvlan_stop; + dev->change_mtu = macvlan_change_mtu; + dev->change_rx_flags = macvlan_change_rx_flags; + dev->set_multicast_list = macvlan_set_multicast_list; + dev->hard_header = macvlan_hard_header; + dev->hard_start_xmit = macvlan_hard_start_xmit; + dev->destructor = free_netdev; + dev->ethtool_ops = &macvlan_ethtool_ops; + dev->tx_queue_len = 0; +} + +static int macvlan_port_create(struct net_device *dev) +{ + struct macvlan_port *port; + unsigned int i; + + if (dev->type != ARPHRD_ETHER || dev->flags & IFF_LOOPBACK) + return -EINVAL; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (port == NULL) + return -ENOMEM; + + port->dev = dev; + INIT_LIST_HEAD(&port->vlans); + for (i = 0; i < MACVLAN_HASH_SIZE; i++) + INIT_HLIST_HEAD(&port->vlan_hash[i]); + rcu_assign_pointer(dev->macvlan_port, port); + return 0; +} + +static void macvlan_port_destroy(struct net_device *dev) +{ + struct macvlan_port *port = dev->macvlan_port; + + rcu_assign_pointer(dev->macvlan_port, NULL); + synchronize_rcu(); + kfree(port); +} + +static void macvlan_transfer_operstate(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + const struct net_device *lowerdev = vlan->lowerdev; + + if (lowerdev->operstate == IF_OPER_DORMANT) + netif_dormant_on(dev); + else + netif_dormant_off(dev); + + if (netif_carrier_ok(lowerdev)) { + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + } else { + if (netif_carrier_ok(lowerdev)) + netif_carrier_off(dev); + } +} + +static int macvlan_validate(struct nlattr *tb[], struct nlattr *data[]) +{ + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } + return 0; +} + +static int macvlan_newlink(struct net_device *dev, + struct nlattr *tb[], struct nlattr *data[]) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + struct macvlan_port *port; + struct net_device *lowerdev; + int err; + + if (!tb[IFLA_LINK]) + return -EINVAL; + + lowerdev = __dev_get_by_index(nla_get_u32(tb[IFLA_LINK])); + if (lowerdev == NULL) + return -ENODEV; + + if (!tb[IFLA_MTU]) + dev->mtu = lowerdev->mtu; + else if (dev->mtu > lowerdev->mtu) + return -EINVAL; + + if (!tb[IFLA_ADDRESS]) + random_ether_addr(dev->dev_addr); + + if (lowerdev->macvlan_port == NULL) { + err = macvlan_port_create(lowerdev); + if (err < 0) + return err; + } + port = lowerdev->macvlan_port; + + vlan->lowerdev = lowerdev; + vlan->dev = dev; + vlan->port = port; + + err = register_netdevice(dev); + if (err < 0) + return err; + + list_add_tail(&vlan->list, &port->vlans); + macvlan_transfer_operstate(dev); + return 0; +} + +static void macvlan_dellink(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + struct macvlan_port *port = vlan->port; + + list_del(&vlan->list); + unregister_netdevice(dev); + + if (list_empty(&port->vlans)) + macvlan_port_destroy(dev); +} + +static struct rtnl_link_ops macvlan_link_ops __read_mostly = { + .kind = "macvlan", + .priv_size = sizeof(struct macvlan_dev), + .setup = macvlan_setup, + .validate = macvlan_validate, + .newlink = macvlan_newlink, + .dellink = macvlan_dellink, +}; + +static int macvlan_device_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + struct macvlan_dev *vlan, *next; + struct macvlan_port *port; + + port = dev->macvlan_port; + if (port == NULL) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_CHANGE: + list_for_each_entry(vlan, &port->vlans, list) + macvlan_transfer_operstate(vlan->dev); + break; + case NETDEV_FEAT_CHANGE: + list_for_each_entry(vlan, &port->vlans, list) { + vlan->dev->features = dev->features & MACVLAN_FEATURES; + netdev_features_change(vlan->dev); + } + break; + case NETDEV_UNREGISTER: + list_for_each_entry_safe(vlan, next, &port->vlans, list) + macvlan_dellink(vlan->dev); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block macvlan_notifier_block __read_mostly = { + .notifier_call = macvlan_device_event, +}; + +static int __init macvlan_init_module(void) +{ + int err; + + register_netdevice_notifier(&macvlan_notifier_block); + macvlan_handle_frame_hook = macvlan_handle_frame; + + err = rtnl_link_register(&macvlan_link_ops); + if (err < 0) + goto err1; + return 0; +err1: + macvlan_handle_frame_hook = macvlan_handle_frame; + unregister_netdevice_notifier(&macvlan_notifier_block); + return err; +} + +static void __exit macvlan_cleanup_module(void) +{ + rtnl_link_unregister(&macvlan_link_ops); + macvlan_handle_frame_hook = NULL; + unregister_netdevice_notifier(&macvlan_notifier_block); +} + +module_init(macvlan_init_module); +module_exit(macvlan_cleanup_module); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Patrick McHardy "); +MODULE_DESCRIPTION("Driver for MAC address based VLANs"); +MODULE_ALIAS_RTNL_LINK("macvlan"); diff --git a/include/linux/if_macvlan.h b/include/linux/if_macvlan.h new file mode 100644 index 00000000000..0d9d7ea2c1c --- /dev/null +++ b/include/linux/if_macvlan.h @@ -0,0 +1,9 @@ +#ifndef _LINUX_IF_MACVLAN_H +#define _LINUX_IF_MACVLAN_H + +#ifdef __KERNEL__ + +extern struct sk_buff *(*macvlan_handle_frame_hook)(struct sk_buff *); + +#endif /* __KERNEL__ */ +#endif /* _LINUX_IF_MACVLAN_H */ diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index e5af458ab04..322b5eae57d 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -564,6 +564,8 @@ struct net_device /* bridge stuff */ struct net_bridge_port *br_port; + /* macvlan */ + struct macvlan_port *macvlan_port; /* class/net/name entry */ struct device dev; diff --git a/net/core/dev.c b/net/core/dev.c index 59ec811d2b5..13a0d9f6da5 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -98,6 +98,7 @@ #include #include #include +#include #include #include #include @@ -1813,6 +1814,28 @@ static inline struct sk_buff *handle_bridge(struct sk_buff *skb, #define handle_bridge(skb, pt_prev, ret, orig_dev) (skb) #endif +#if defined(CONFIG_MACVLAN) || defined(CONFIG_MACVLAN_MODULE) +struct sk_buff *(*macvlan_handle_frame_hook)(struct sk_buff *skb) __read_mostly; +EXPORT_SYMBOL_GPL(macvlan_handle_frame_hook); + +static inline struct sk_buff *handle_macvlan(struct sk_buff *skb, + struct packet_type **pt_prev, + int *ret, + struct net_device *orig_dev) +{ + if (skb->dev->macvlan_port == NULL) + return skb; + + if (*pt_prev) { + *ret = deliver_skb(skb, *pt_prev, orig_dev); + *pt_prev = NULL; + } + return macvlan_handle_frame_hook(skb); +} +#else +#define handle_macvlan(skb, pt_prev, ret, orig_dev) (skb) +#endif + #ifdef CONFIG_NET_CLS_ACT /* TODO: Maybe we should just force sch_ingress to be compiled in * when CONFIG_NET_CLS_ACT is? otherwise some useless instructions @@ -1918,6 +1941,9 @@ ncls: #endif skb = handle_bridge(skb, &pt_prev, &ret, orig_dev); + if (!skb) + goto out; + skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; -- cgit v1.2.3 From 6460d948f3ebf7d5040328a60a0ab7221f69945b Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Sat, 14 Jul 2007 19:07:52 -0700 Subject: [NET]: Add ethtool support for NETIF_F_IPV6_CSUM devices. Add ethtool utility function to set or clear IPV6_CSUM feature flag. Modify tg3.c and bnx2.c to use this function when doing ethtool -K to change tx checksum. Signed-off-by: Michael Chan Signed-off-by: David S. Miller --- drivers/net/bnx2.c | 2 +- drivers/net/tg3.c | 2 +- include/linux/ethtool.h | 1 + net/core/ethtool.c | 12 ++++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/bnx2.c b/drivers/net/bnx2.c index 4e5e1cb2adc..d23861c8658 100644 --- a/drivers/net/bnx2.c +++ b/drivers/net/bnx2.c @@ -6218,7 +6218,7 @@ bnx2_set_tx_csum(struct net_device *dev, u32 data) struct bnx2 *bp = netdev_priv(dev); if (CHIP_NUM(bp) == CHIP_NUM_5709) - return (ethtool_op_set_tx_hw_csum(dev, data)); + return (ethtool_op_set_tx_ipv6_csum(dev, data)); else return (ethtool_op_set_tx_csum(dev, data)); } diff --git a/drivers/net/tg3.c b/drivers/net/tg3.c index 32e4037dcb5..5ee14764fd7 100644 --- a/drivers/net/tg3.c +++ b/drivers/net/tg3.c @@ -8318,7 +8318,7 @@ static int tg3_set_tx_csum(struct net_device *dev, u32 data) if (GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5755 || GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5787) - ethtool_op_set_tx_hw_csum(dev, data); + ethtool_op_set_tx_ipv6_csum(dev, data); else ethtool_op_set_tx_csum(dev, data); diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index f2d248f8cc9..3a632244f31 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -265,6 +265,7 @@ u32 ethtool_op_get_link(struct net_device *dev); u32 ethtool_op_get_tx_csum(struct net_device *dev); int ethtool_op_set_tx_csum(struct net_device *dev, u32 data); int ethtool_op_set_tx_hw_csum(struct net_device *dev, u32 data); +int ethtool_op_set_tx_ipv6_csum(struct net_device *dev, u32 data); u32 ethtool_op_get_sg(struct net_device *dev); int ethtool_op_set_sg(struct net_device *dev, u32 data); u32 ethtool_op_get_tso(struct net_device *dev); diff --git a/net/core/ethtool.c b/net/core/ethtool.c index 8d5e5a09b57..0b531e98ec3 100644 --- a/net/core/ethtool.c +++ b/net/core/ethtool.c @@ -52,6 +52,17 @@ int ethtool_op_set_tx_hw_csum(struct net_device *dev, u32 data) return 0; } + +int ethtool_op_set_tx_ipv6_csum(struct net_device *dev, u32 data) +{ + if (data) + dev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; + else + dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM); + + return 0; +} + u32 ethtool_op_get_sg(struct net_device *dev) { return (dev->features & NETIF_F_SG) != 0; @@ -980,5 +991,6 @@ EXPORT_SYMBOL(ethtool_op_set_sg); EXPORT_SYMBOL(ethtool_op_set_tso); EXPORT_SYMBOL(ethtool_op_set_tx_csum); EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum); +EXPORT_SYMBOL(ethtool_op_set_tx_ipv6_csum); EXPORT_SYMBOL(ethtool_op_set_ufo); EXPORT_SYMBOL(ethtool_op_get_ufo); -- cgit v1.2.3 From 370786f9cfd430cb424f00ce4110e75bb1b95a19 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sat, 14 Jul 2007 20:47:26 -0700 Subject: [NETFILTER]: x_tables: add connlimit match ipt_connlimit has been sitting in POM-NG for a long time. Here is a new shiny xt_connlimit with: * xtables'ified * will request the layer3 module (previously it hotdropped every packet when it was not loaded) * fixed: there was a deadlock in case of an OOM condition * support for any layer4 protocol (e.g. UDP/SCTP) * using jhash, as suggested by Eric Dumazet * ipv6 support Signed-off-by: Jan Engelhardt Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- include/linux/netfilter/xt_connlimit.h | 17 ++ net/netfilter/Kconfig | 7 + net/netfilter/Makefile | 1 + net/netfilter/xt_connlimit.c | 313 +++++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 include/linux/netfilter/xt_connlimit.h create mode 100644 net/netfilter/xt_connlimit.c (limited to 'include/linux') diff --git a/include/linux/netfilter/xt_connlimit.h b/include/linux/netfilter/xt_connlimit.h new file mode 100644 index 00000000000..90ae8b474cb --- /dev/null +++ b/include/linux/netfilter/xt_connlimit.h @@ -0,0 +1,17 @@ +#ifndef _XT_CONNLIMIT_H +#define _XT_CONNLIMIT_H + +struct xt_connlimit_data; + +struct xt_connlimit_info { + union { + u_int32_t v4_mask; + u_int32_t v6_mask[4]; + }; + unsigned int limit, inverse; + + /* this needs to be at the end */ + struct xt_connlimit_data *data __attribute__((aligned(8))); +}; + +#endif /* _XT_CONNLIMIT_H */ diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index df5e8dab871..9415b9a5dba 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -423,6 +423,13 @@ config NETFILTER_XT_MATCH_CONNBYTES If you want to compile it as a module, say M here and read . If unsure, say `N'. +config NETFILTER_XT_MATCH_CONNLIMIT + tristate '"connlimit" match support"' + depends on NETFILTER_XTABLES + ---help--- + This match allows you to match against the number of parallel + connections to a server per client IP address (or address block). + config NETFILTER_XT_MATCH_CONNMARK tristate '"connmark" connection mark match support' depends on NETFILTER_XTABLES diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 58b4245a172..3e4a16aeb04 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o # matches obj-$(CONFIG_NETFILTER_XT_MATCH_COMMENT) += xt_comment.o obj-$(CONFIG_NETFILTER_XT_MATCH_CONNBYTES) += xt_connbytes.o +obj-$(CONFIG_NETFILTER_XT_MATCH_CONNLIMIT) += xt_connlimit.o obj-$(CONFIG_NETFILTER_XT_MATCH_CONNMARK) += xt_connmark.o obj-$(CONFIG_NETFILTER_XT_MATCH_CONNTRACK) += xt_conntrack.o obj-$(CONFIG_NETFILTER_XT_MATCH_DCCP) += xt_dccp.o diff --git a/net/netfilter/xt_connlimit.c b/net/netfilter/xt_connlimit.c new file mode 100644 index 00000000000..3335dd5be96 --- /dev/null +++ b/net/netfilter/xt_connlimit.c @@ -0,0 +1,313 @@ +/* + * netfilter module to limit the number of parallel tcp + * connections per IP address. + * (c) 2000 Gerd Knorr + * Nov 2002: Martin Bene : + * only ignore TIME_WAIT or gone connections + * Copyright © Jan Engelhardt , 2007 + * + * based on ... + * + * Kernel module to match connection tracking information. + * GPL (C) 1999 Rusty Russell (rusty@rustcorp.com.au). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* we will save the tuples of all connections we care about */ +struct xt_connlimit_conn { + struct list_head list; + struct nf_conntrack_tuple tuple; +}; + +struct xt_connlimit_data { + struct list_head iphash[256]; + spinlock_t lock; +}; + +static u_int32_t connlimit_rnd; +static bool connlimit_rnd_inited; + +static inline unsigned int connlimit_iphash(u_int32_t addr) +{ + if (unlikely(!connlimit_rnd_inited)) { + get_random_bytes(&connlimit_rnd, sizeof(connlimit_rnd)); + connlimit_rnd_inited = true; + } + return jhash_1word(addr, connlimit_rnd) & 0xFF; +} + +static inline unsigned int +connlimit_iphash6(const union nf_conntrack_address *addr, + const union nf_conntrack_address *mask) +{ + union nf_conntrack_address res; + unsigned int i; + + if (unlikely(!connlimit_rnd_inited)) { + get_random_bytes(&connlimit_rnd, sizeof(connlimit_rnd)); + connlimit_rnd_inited = true; + } + + for (i = 0; i < ARRAY_SIZE(addr->ip6); ++i) + res.ip6[i] = addr->ip6[i] & mask->ip6[i]; + + return jhash2(res.ip6, ARRAY_SIZE(res.ip6), connlimit_rnd) & 0xFF; +} + +static inline bool already_closed(const struct nf_conn *conn) +{ + u_int16_t proto = conn->tuplehash[0].tuple.dst.protonum; + + if (proto == IPPROTO_TCP) + return conn->proto.tcp.state == TCP_CONNTRACK_TIME_WAIT; + else + return 0; +} + +static inline unsigned int +same_source_net(const union nf_conntrack_address *addr, + const union nf_conntrack_address *mask, + const union nf_conntrack_address *u3, unsigned int family) +{ + if (family == AF_INET) { + return (addr->ip & mask->ip) == (u3->ip & mask->ip); + } else { + union nf_conntrack_address lh, rh; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(addr->ip6); ++i) { + lh.ip6[i] = addr->ip6[i] & mask->ip6[i]; + rh.ip6[i] = u3->ip6[i] & mask->ip6[i]; + } + + return memcmp(&lh.ip6, &rh.ip6, sizeof(lh.ip6)) == 0; + } +} + +static int count_them(struct xt_connlimit_data *data, + const struct nf_conntrack_tuple *tuple, + const union nf_conntrack_address *addr, + const union nf_conntrack_address *mask, + const struct xt_match *match) +{ + struct nf_conntrack_tuple_hash *found; + struct xt_connlimit_conn *conn; + struct xt_connlimit_conn *tmp; + struct nf_conn *found_ct; + struct list_head *hash; + bool addit = true; + int matches = 0; + + + if (match->family == AF_INET6) + hash = &data->iphash[connlimit_iphash6(addr, mask)]; + else + hash = &data->iphash[connlimit_iphash(addr->ip & mask->ip)]; + + read_lock_bh(&nf_conntrack_lock); + + /* check the saved connections */ + list_for_each_entry_safe(conn, tmp, hash, list) { + found = __nf_conntrack_find(&conn->tuple, NULL); + found_ct = NULL; + + if (found != NULL) + found_ct = nf_ct_tuplehash_to_ctrack(found); + + if (found_ct != NULL && + nf_ct_tuple_equal(&conn->tuple, tuple) && + !already_closed(found_ct)) + /* + * Just to be sure we have it only once in the list. + * We should not see tuples twice unless someone hooks + * this into a table without "-p tcp --syn". + */ + addit = false; + + if (found == NULL) { + /* this one is gone */ + list_del(&conn->list); + kfree(conn); + continue; + } + + if (already_closed(found_ct)) { + /* + * we do not care about connections which are + * closed already -> ditch it + */ + list_del(&conn->list); + kfree(conn); + continue; + } + + if (same_source_net(addr, mask, &conn->tuple.src.u3, + match->family)) + /* same source network -> be counted! */ + ++matches; + } + + read_unlock_bh(&nf_conntrack_lock); + + if (addit) { + /* save the new connection in our list */ + conn = kzalloc(sizeof(*conn), GFP_ATOMIC); + if (conn == NULL) + return -ENOMEM; + conn->tuple = *tuple; + list_add(&conn->list, hash); + ++matches; + } + + return matches; +} + +static bool connlimit_match(const struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + const struct xt_match *match, + const void *matchinfo, int offset, + unsigned int protoff, bool *hotdrop) +{ + const struct xt_connlimit_info *info = matchinfo; + union nf_conntrack_address addr, mask; + struct nf_conntrack_tuple tuple; + const struct nf_conntrack_tuple *tuple_ptr = &tuple; + enum ip_conntrack_info ctinfo; + const struct nf_conn *ct; + int connections; + + ct = nf_ct_get(skb, &ctinfo); + if (ct != NULL) + tuple_ptr = &ct->tuplehash[0].tuple; + else if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb), + match->family, &tuple)) + goto hotdrop; + + if (match->family == AF_INET6) { + const struct ipv6hdr *iph = ipv6_hdr(skb); + memcpy(&addr.ip6, &iph->saddr, sizeof(iph->saddr)); + memcpy(&mask.ip6, info->v6_mask, sizeof(info->v6_mask)); + } else { + const struct iphdr *iph = ip_hdr(skb); + addr.ip = iph->saddr; + mask.ip = info->v4_mask; + } + + spin_lock_bh(&info->data->lock); + connections = count_them(info->data, tuple_ptr, &addr, &mask, match); + spin_unlock_bh(&info->data->lock); + + if (connections < 0) { + /* kmalloc failed, drop it entirely */ + *hotdrop = true; + return false; + } + + return (connections > info->limit) ^ info->inverse; + + hotdrop: + *hotdrop = true; + return false; +} + +static bool connlimit_check(const char *tablename, const void *ip, + const struct xt_match *match, void *matchinfo, + unsigned int hook_mask) +{ + struct xt_connlimit_info *info = matchinfo; + unsigned int i; + + if (nf_ct_l3proto_try_module_get(match->family) < 0) { + printk(KERN_WARNING "cannot load conntrack support for " + "address family %u\n", match->family); + return false; + } + + /* init private data */ + info->data = kmalloc(sizeof(struct xt_connlimit_data), GFP_KERNEL); + if (info->data == NULL) { + nf_ct_l3proto_module_put(match->family); + return false; + } + + spin_lock_init(&info->data->lock); + for (i = 0; i < ARRAY_SIZE(info->data->iphash); ++i) + INIT_LIST_HEAD(&info->data->iphash[i]); + + return true; +} + +static void connlimit_destroy(const struct xt_match *match, void *matchinfo) +{ + struct xt_connlimit_info *info = matchinfo; + struct xt_connlimit_conn *conn; + struct xt_connlimit_conn *tmp; + struct list_head *hash = info->data->iphash; + unsigned int i; + + nf_ct_l3proto_module_put(match->family); + + for (i = 0; i < ARRAY_SIZE(info->data->iphash); ++i) { + list_for_each_entry_safe(conn, tmp, &hash[i], list) { + list_del(&conn->list); + kfree(conn); + } + } + + kfree(info->data); +} + +static struct xt_match connlimit_reg[] __read_mostly = { + { + .name = "connlimit", + .family = AF_INET, + .checkentry = connlimit_check, + .match = connlimit_match, + .matchsize = sizeof(struct xt_connlimit_info), + .destroy = connlimit_destroy, + .me = THIS_MODULE, + }, + { + .name = "connlimit", + .family = AF_INET6, + .checkentry = connlimit_check, + .match = connlimit_match, + .matchsize = sizeof(struct xt_connlimit_info), + .destroy = connlimit_destroy, + .me = THIS_MODULE, + }, +}; + +static int __init xt_connlimit_init(void) +{ + return xt_register_matches(connlimit_reg, ARRAY_SIZE(connlimit_reg)); +} + +static void __exit xt_connlimit_exit(void) +{ + xt_unregister_matches(connlimit_reg, ARRAY_SIZE(connlimit_reg)); +} + +module_init(xt_connlimit_init); +module_exit(xt_connlimit_exit); +MODULE_AUTHOR("Jan Engelhardt "); +MODULE_DESCRIPTION("netfilter xt_connlimit match module"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_connlimit"); +MODULE_ALIAS("ip6t_connlimit"); -- cgit v1.2.3