--- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -149,4 +149,37 @@ br_port_flag_is_set(const struct net_dev } #endif +extern struct net_device *br_port_dev_get(struct net_device *dev, + unsigned char *addr, + struct sk_buff *skb, + unsigned int cookie); +extern void br_refresh_fdb_entry(struct net_device *dev, const char *addr); +extern struct net_bridge_fdb_entry *br_fdb_has_entry(struct net_device *dev, + const char *addr, + __u16 vid); +extern void br_fdb_update_register_notify(struct notifier_block *nb); +extern void br_fdb_update_unregister_notify(struct notifier_block *nb); + +typedef struct net_bridge_port *br_port_dev_get_hook_t(struct net_device *dev, + struct sk_buff *skb, + unsigned char *addr, + unsigned int cookie); +extern br_port_dev_get_hook_t __rcu *br_port_dev_get_hook; + +#define BR_FDB_EVENT_ADD 0x01 +#define BR_FDB_EVENT_DEL 0x02 + +struct br_fdb_event { + struct net_device *dev; + unsigned char addr[6]; + unsigned char is_local; +}; +extern void br_fdb_register_notify(struct notifier_block *nb); +extern void br_fdb_unregister_notify(struct notifier_block *nb); + +typedef struct net_bridge_port *br_get_dst_hook_t( + const struct net_bridge_port *src, + struct sk_buff **skb); +extern br_get_dst_hook_t __rcu *br_get_dst_hook; + #endif --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2596,6 +2596,8 @@ enum netdev_cmd { NETDEV_CVLAN_FILTER_DROP_INFO, NETDEV_SVLAN_FILTER_PUSH_INFO, NETDEV_SVLAN_FILTER_DROP_INFO, + NETDEV_BR_JOIN, + NETDEV_BR_LEAVE, }; const char *netdev_cmd_to_name(enum netdev_cmd cmd); --- a/include/linux/ppp_channel.h +++ b/include/linux/ppp_channel.h @@ -32,6 +32,17 @@ struct ppp_channel_ops { #if IS_ENABLED(CONFIG_NF_FLOW_TABLE) int (*flow_offload_check)(struct ppp_channel *, struct flow_offload_hw_path *); #endif + + /* Get channel protocol type, one of PX_PROTO_XYZ or specific to + * the channel subtype + */ + int (*get_channel_protocol)(struct ppp_channel *); + /* Get channel protocol version */ + int (*get_channel_protocol_ver)(struct ppp_channel *); + /* Hold the channel from being destroyed */ + void (*hold)(struct ppp_channel *); + /* Release hold on the channel */ + void (*release)(struct ppp_channel *); }; struct ppp_channel { @@ -84,5 +95,53 @@ extern char *ppp_dev_name(struct ppp_cha * that ppp_unregister_channel returns. */ +/* Call this to obtain the underlying protocol of the PPP channel, + * e.g. PX_PROTO_OE + */ +extern int ppp_channel_get_protocol(struct ppp_channel *); + +/* Call this get protocol version */ +extern int ppp_channel_get_proto_version(struct ppp_channel *); + +/* Call this to hold a channel */ +extern bool ppp_channel_hold(struct ppp_channel *); + +/* Call this to release a hold you have upon a channel */ +extern void ppp_channel_release(struct ppp_channel *); + +/* Release hold on PPP channels */ +extern void ppp_release_channels(struct ppp_channel *channels[], + unsigned int chan_sz); + +/* Hold PPP channels for the PPP device */ +extern int ppp_hold_channels(struct net_device *dev, + struct ppp_channel *channels[], + unsigned int chan_sz); +/* Test if ppp xmit lock is locked */ +extern bool ppp_is_xmit_locked(struct net_device *dev); + +/* Hold PPP channels for the PPP device */ +extern int __ppp_hold_channels(struct net_device *dev, + struct ppp_channel *channels[], + unsigned int chan_sz); + +/* Test if the ppp device is a multi-link ppp device */ +extern int ppp_is_multilink(struct net_device *dev); + +/* Test if the ppp device is a multi-link ppp device */ +extern int __ppp_is_multilink(struct net_device *dev); + +/* Update statistics of the PPP net_device by incrementing related + * statistics field value with corresponding parameter + */ +extern void ppp_update_stats(struct net_device *dev, unsigned long rx_packets, + unsigned long rx_bytes, unsigned long tx_packets, + unsigned long tx_bytes, unsigned long rx_errors, + unsigned long tx_errors, unsigned long rx_dropped, + unsigned long tx_dropped); + +/* Get the device index associated with a channel, or 0, if none */ +extern int ppp_dev_index(struct ppp_channel *); + #endif /* __KERNEL__ */ #endif --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -728,6 +728,7 @@ void phy_stop_machine(struct phy_device *phydev) phydev->state = PHY_UP; mutex_unlock(&phydev->lock); } +EXPORT_SYMBOL_GPL(phy_stop_machine); /** * phy_error - enter HALTED state for this PHY device --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -3357,6 +3357,318 @@ static void *unit_find(struct idr *p, int n) return idr_find(p, n); } +/* Return the PPP net device index */ +int ppp_dev_index(struct ppp_channel *chan) +{ + struct channel *pch = chan->ppp; + int ifindex = 0; + + if (pch) { + read_lock_bh(&pch->upl); + if (pch->ppp && pch->ppp->dev) + ifindex = pch->ppp->dev->ifindex; + read_unlock_bh(&pch->upl); + } + return ifindex; +} +EXPORT_SYMBOL(ppp_dev_index); + +/* Updates the PPP interface statistics. */ +void ppp_update_stats(struct net_device *dev, unsigned long rx_packets, + unsigned long rx_bytes, unsigned long tx_packets, + unsigned long tx_bytes, unsigned long rx_errors, + unsigned long tx_errors, unsigned long rx_dropped, + unsigned long tx_dropped) +{ + struct ppp *ppp; + + if (!dev) + return; + + if (dev->type != ARPHRD_PPP) + return; + + ppp = netdev_priv(dev); + + ppp_xmit_lock(ppp); + ppp->stats64.tx_packets += tx_packets; + ppp->stats64.tx_bytes += tx_bytes; + ppp->dev->stats.tx_errors += tx_errors; + ppp->dev->stats.tx_dropped += tx_dropped; + if (tx_packets) + ppp->last_xmit = jiffies; + ppp_xmit_unlock(ppp); + + ppp_recv_lock(ppp); + ppp->stats64.rx_packets += rx_packets; + ppp->stats64.rx_bytes += rx_bytes; + ppp->dev->stats.rx_errors += rx_errors; + ppp->dev->stats.rx_dropped += rx_dropped; + if (rx_packets) + ppp->last_recv = jiffies; + ppp_recv_unlock(ppp); +} +EXPORT_SYMBOL(ppp_update_stats); + +/* Returns >0 if the device is a multilink PPP netdevice, 0 if not or < 0 if + * the device is not PPP. + */ +int ppp_is_multilink(struct net_device *dev) +{ + struct ppp *ppp; + unsigned int flags; + + if (!dev) + return -1; + + if (dev->type != ARPHRD_PPP) + return -1; + + ppp = netdev_priv(dev); + ppp_lock(ppp); + flags = ppp->flags; + ppp_unlock(ppp); + + if (flags & SC_MULTILINK) + return 1; + + return 0; +} +EXPORT_SYMBOL(ppp_is_multilink); + +/* __ppp_is_multilink() + * Returns >0 if the device is a multilink PPP netdevice, 0 if not or < 0 + * if the device is not PPP. Caller should acquire ppp_lock before calling + * this function + */ +int __ppp_is_multilink(struct net_device *dev) +{ + struct ppp *ppp; + unsigned int flags; + + if (!dev) + return -1; + + if (dev->type != ARPHRD_PPP) + return -1; + + ppp = netdev_priv(dev); + flags = ppp->flags; + + if (flags & SC_MULTILINK) + return 1; + + return 0; +} +EXPORT_SYMBOL(__ppp_is_multilink); + +/* ppp_channel_get_protocol() + * Call this to obtain the underlying protocol of the PPP channel, + * e.g. PX_PROTO_OE + * + * NOTE: Some channels do not use PX sockets so the protocol value may be very + * different for them. + * NOTE: -1 indicates failure. + * NOTE: Once you know the channel protocol you may then either cast 'chan' to + * its sub-class or use the channel protocol specific API's as provided by that + * channel sub type. + */ +int ppp_channel_get_protocol(struct ppp_channel *chan) +{ + if (!chan->ops->get_channel_protocol) + return -1; + + return chan->ops->get_channel_protocol(chan); +} +EXPORT_SYMBOL(ppp_channel_get_protocol); + +/* ppp_channel_get_proto_version() + * Call this to get channel protocol version + */ +int ppp_channel_get_proto_version(struct ppp_channel *chan) +{ + if (!chan->ops->get_channel_protocol_ver) + return -1; + + return chan->ops->get_channel_protocol_ver(chan); +} +EXPORT_SYMBOL(ppp_channel_get_proto_version); + +/* ppp_channel_hold() + * Call this to hold a channel. + * + * Returns true on success or false if the hold could not happen. + * + * NOTE: chan must be protected against destruction during this call - + * either by correct locking etc. or because you already have an implicit + * or explicit hold to the channel already and this is an additional hold. + */ +bool ppp_channel_hold(struct ppp_channel *chan) +{ + if (!chan->ops->hold) + return false; + + chan->ops->hold(chan); + return true; +} +EXPORT_SYMBOL(ppp_channel_hold); + +/* ppp_channel_release() + * Call this to release a hold you have upon a channel + */ +void ppp_channel_release(struct ppp_channel *chan) +{ + chan->ops->release(chan); +} +EXPORT_SYMBOL(ppp_channel_release); + +/* ppp_hold_channels() + * Returns the PPP channels of the PPP device, storing each one into + * channels[]. + * + * channels[] has chan_sz elements. + * This function returns the number of channels stored, up to chan_sz. + * It will return < 0 if the device is not PPP. + * + * You MUST release the channels using ppp_release_channels(). + */ +int ppp_hold_channels(struct net_device *dev, struct ppp_channel *channels[], + unsigned int chan_sz) +{ + struct ppp *ppp; + int c; + struct channel *pch; + + if (!dev) + return -1; + + if (dev->type != ARPHRD_PPP) + return -1; + + ppp = netdev_priv(dev); + + c = 0; + ppp_lock(ppp); + list_for_each_entry(pch, &ppp->channels, clist) { + struct ppp_channel *chan; + + if (!pch->chan) { + /* Channel is going / gone away */ + continue; + } + + if (c == chan_sz) { + /* No space to record channel */ + ppp_unlock(ppp); + return c; + } + + /* Hold the channel, if supported */ + chan = pch->chan; + if (!chan->ops->hold) + continue; + + chan->ops->hold(chan); + + /* Record the channel */ + channels[c++] = chan; + } + ppp_unlock(ppp); + return c; +} +EXPORT_SYMBOL(ppp_hold_channels); + +/* __ppp_hold_channels() + * Returns the PPP channels of the PPP device, storing each one + * into channels[]. + * + * channels[] has chan_sz elements. + * This function returns the number of channels stored, up to chan_sz. + * It will return < 0 if the device is not PPP. + * + * You MUST acquire ppp_lock and release the channels using + * ppp_release_channels(). + */ +int __ppp_hold_channels(struct net_device *dev, struct ppp_channel *channels[], + unsigned int chan_sz) +{ + struct ppp *ppp; + int c; + struct channel *pch; + + if (!dev) + return -1; + + if (dev->type != ARPHRD_PPP) + return -1; + + ppp = netdev_priv(dev); + + c = 0; + list_for_each_entry(pch, &ppp->channels, clist) { + struct ppp_channel *chan; + + if (!pch->chan) { + /* Channel is going / gone away*/ + continue; + } + if (c == chan_sz) { + /* No space to record channel */ + return c; + } + + /* Hold the channel, if supported */ + chan = pch->chan; + if (!chan->ops->hold) + continue; + + chan->ops->hold(chan); + + /* Record the channel */ + channels[c++] = chan; + } + return c; +} +EXPORT_SYMBOL(__ppp_hold_channels); + +/* ppp_release_channels() + * Releases channels + */ +void ppp_release_channels(struct ppp_channel *channels[], unsigned int chan_sz) +{ + unsigned int c; + + for (c = 0; c < chan_sz; ++c) { + struct ppp_channel *chan; + + chan = channels[c]; + chan->ops->release(chan); + } +} +EXPORT_SYMBOL(ppp_release_channels); + +/* Check if ppp xmit lock is on hold */ +bool ppp_is_xmit_locked(struct net_device *dev) +{ + struct ppp *ppp; + + if (!dev) + return false; + + if (dev->type != ARPHRD_PPP) + return false; + + ppp = netdev_priv(dev); + if (!ppp) + return false; + + if (spin_is_locked(&(ppp)->wlock)) + return true; + + return false; +} +EXPORT_SYMBOL(ppp_is_xmit_locked); + /* Module/initialization stuff */ module_init(ppp_init); --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c @@ -26,6 +26,10 @@ #include "br_private.h" +/* Hook for external forwarding logic */ +br_port_dev_get_hook_t __rcu *br_port_dev_get_hook __read_mostly; +EXPORT_SYMBOL_GPL(br_port_dev_get_hook); + /* * Determine initial path cost based on speed. * using recommendations from 802.1d standard @@ -695,6 +699,8 @@ int br_add_if(struct net_bridge *br, struct net_device *dev, kobject_uevent(&p->kobj, KOBJ_ADD); + call_netdevice_notifiers(NETDEV_BR_JOIN, dev); + return 0; err7: @@ -731,6 +737,8 @@ int br_del_if(struct net_bridge *br, struct net_device *dev) if (!p || p->br != br) return -EINVAL; + call_netdevice_notifiers(NETDEV_BR_LEAVE, dev); + /* Since more than one interface can be attached to a bridge, * there still maybe an alternate path for netconsole to use; * therefore there is no reason for a NETDEV_RELEASE event. @@ -785,6 +793,65 @@ void br_dev_update_stats(struct net_device *dev, } EXPORT_SYMBOL_GPL(br_dev_update_stats); +/* br_port_dev_get() + * If a skb is provided, and the br_port_dev_get_hook_t hook exists, + * use that to try and determine the egress port for that skb. + * If not, or no egress port could be determined, use the given addr + * to identify the port to which it is reachable, + * returing a reference to the net device associated with that port. + * + * NOTE: Return NULL if given dev is not a bridge or the mac has no + * associated port. + */ +struct net_device *br_port_dev_get(struct net_device *dev, unsigned char *addr, + struct sk_buff *skb, + unsigned int cookie) +{ + struct net_bridge_fdb_entry *fdbe; + struct net_bridge *br; + struct net_device *netdev = NULL; + + /* Is this a bridge? */ + if (!(dev->priv_flags & IFF_EBRIDGE)) + return NULL; + + rcu_read_lock(); + + /* If the hook exists and the skb isn't NULL, try and get the port */ + if (skb) { + br_port_dev_get_hook_t *port_dev_get_hook; + + port_dev_get_hook = rcu_dereference(br_port_dev_get_hook); + if (port_dev_get_hook) { + struct net_bridge_port *pdst = + __br_get(port_dev_get_hook, NULL, dev, skb, + addr, cookie); + if (pdst) { + dev_hold(pdst->dev); + netdev = pdst->dev; + goto out; + } + } + } + + /* Either there is no hook, or can't + * determine the port to use - fall back to using FDB + */ + + br = netdev_priv(dev); + + /* Lookup the fdb entry and get reference to the port dev */ + fdbe = br_fdb_find_rcu(br, addr, 0); + if (fdbe && fdbe->dst) { + netdev = fdbe->dst->dev; /* port device */ + dev_hold(netdev); + } +out: + rcu_read_unlock(); + return netdev; +} +EXPORT_SYMBOL_GPL(br_port_dev_get); + bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag) { struct net_bridge_port *p; --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -37,6 +37,33 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source, static void fdb_notify(struct net_bridge *br, const struct net_bridge_fdb_entry *, int, bool); +ATOMIC_NOTIFIER_HEAD(br_fdb_notifier_list); +ATOMIC_NOTIFIER_HEAD(br_fdb_update_notifier_list); + +void br_fdb_register_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_register(&br_fdb_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_register_notify); + +void br_fdb_unregister_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&br_fdb_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_unregister_notify); + +void br_fdb_update_register_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_register(&br_fdb_update_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_update_register_notify); + +void br_fdb_update_unregister_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&br_fdb_update_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_update_unregister_notify); + int __init br_fdb_init(void) { br_fdb_cache = kmem_cache_create("bridge_fdb_cache", @@ -337,6 +364,7 @@ void br_fdb_cleanup(struct work_struct *work) unsigned long delay = hold_time(br); unsigned long work_delay = delay; unsigned long now = jiffies; + u8 mac_addr[6]; /* this part is tricky, in order to avoid blocking learning and * consequently forwarding, we rely on rcu to delete objects with @@ -353,8 +381,11 @@ void br_fdb_cleanup(struct work_struct *work) work_delay = min(work_delay, this_timer - now); } else { spin_lock_bh(&br->hash_lock); - if (!hlist_unhashed(&f->fdb_node)) + if (!hlist_unhashed(&f->fdb_node)) { fdb_delete(br, f, true); + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, 0, (void *)mac_addr); + } spin_unlock_bh(&br->hash_lock); } } @@ -587,6 +618,8 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, /* Take over HW learned entry */ if (unlikely(fdb->added_by_external_learn)) fdb->added_by_external_learn = 0; + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, 0, (void *)addr); } if (now != fdb->updated) fdb->updated = now; @@ -696,6 +729,23 @@ static void fdb_notify(struct net_bridge *br, struct sk_buff *skb; int err = -ENOBUFS; + if (fdb->dst) { + int event; + struct br_fdb_event fdb_event; + + if (type == RTM_NEWNEIGH) + event = BR_FDB_EVENT_ADD; + else + event = BR_FDB_EVENT_DEL; + + fdb_event.dev = fdb->dst->dev; + ether_addr_copy(fdb_event.addr, fdb->key.addr.addr); + fdb_event.is_local = fdb->is_local; + atomic_notifier_call_chain(&br_fdb_notifier_list, + event, + (void *)&fdb_event); + } + if (swdev_notify) br_switchdev_fdb_notify(br, fdb, type); @@ -1212,3 +1262,41 @@ void br_fdb_clear_offload(const struct net_device *dev, u16 vid) spin_unlock_bh(&p->br->hash_lock); } EXPORT_SYMBOL_GPL(br_fdb_clear_offload); + +/* Refresh FDB entries for bridge packets being forwarded by offload engines */ +void br_refresh_fdb_entry(struct net_device *dev, const char *addr) +{ + struct net_bridge_port *p = br_port_get_rcu(dev); + + if (!p || p->state == BR_STATE_DISABLED) + return; + + if (!is_valid_ether_addr(addr)) { + pr_info("bridge: Attempt to refresh with invalid ether address %pM\n", + addr); + return; + } + + rcu_read_lock(); + br_fdb_update(p->br, p, addr, 0, true); + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(br_refresh_fdb_entry); + +/* Look up the MAC address in the device's bridge fdb table */ +struct net_bridge_fdb_entry *br_fdb_has_entry(struct net_device *dev, + const char *addr, __u16 vid) +{ + struct net_bridge_port *p = br_port_get_rcu(dev); + struct net_bridge_fdb_entry *fdb; + + if (!p || p->state == BR_STATE_DISABLED) + return NULL; + + rcu_read_lock(); + fdb = fdb_find_rcu(&p->br->fdb_hash_tbl, addr, vid); + rcu_read_unlock(); + + return fdb; +} +EXPORT_SYMBOL_GPL(br_fdb_has_entry); --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -1269,4 +1269,7 @@ void br_do_proxy_suppress_arp(struct sk_ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p, struct nd_msg *msg); struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb, struct nd_msg *m); + +#define __br_get(__hook, __default, __args ...) \ + (__hook ? (__hook(__args)) : (__default)) #endif