From 5f118ef95a0a61dc83d9f56e2c5f41f346cdbb21 Mon Sep 17 00:00:00 2001 From: ACwifidude Date: Sun, 6 Feb 2022 10:51:30 -0600 Subject: [PATCH] ipq806x: NSS Hardware Offloading ECM patch --- .../999-01a-qca-nss-ecm-support-CORE.patch | 742 ++++++++++++++++++ 1 file changed, 742 insertions(+) create mode 100644 target/linux/ipq806x/patches-5.10/999-01a-qca-nss-ecm-support-CORE.patch diff --git a/target/linux/ipq806x/patches-5.10/999-01a-qca-nss-ecm-support-CORE.patch b/target/linux/ipq806x/patches-5.10/999-01a-qca-nss-ecm-support-CORE.patch new file mode 100644 index 0000000000..9bb69feab1 --- /dev/null +++ b/target/linux/ipq806x/patches-5.10/999-01a-qca-nss-ecm-support-CORE.patch @@ -0,0 +1,742 @@ +--- a/net/netfilter/nf_conntrack_proto_tcp.c ++++ b/net/netfilter/nf_conntrack_proto_tcp.c +@@ -32,12 +32,14 @@ + #include + + /* Do not check the TCP window for incoming packets */ +-static int nf_ct_tcp_no_window_check __read_mostly = 1; ++int nf_ct_tcp_no_window_check __read_mostly = 1; ++EXPORT_SYMBOL_GPL(nf_ct_tcp_no_window_check); + + /* "Be conservative in what you do, + be liberal in what you accept from others." + If it's non-zero, we mark only out of window RST segments as INVALID. */ +-static int nf_ct_tcp_be_liberal __read_mostly = 0; ++int nf_ct_tcp_be_liberal __read_mostly = 0; ++EXPORT_SYMBOL_GPL(nf_ct_tcp_be_liberal); + + /* If it is set to zero, we disable picking up already established + connections. */ +--- a/include/linux/if_bridge.h ++++ b/include/linux/if_bridge.h +@@ -60,6 +60,9 @@ struct br_ip_list { + #define BR_DEFAULT_AGEING_TIME (300 * HZ) + + extern void brioctl_set(int (*ioctl_hook)(struct net *, unsigned int, void __user *)); ++extern void br_dev_update_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *nlstats); ++extern bool br_is_hairpin_enabled(struct net_device *dev); + + #if IS_ENABLED(CONFIG_BRIDGE) && IS_ENABLED(CONFIG_BRIDGE_IGMP_SNOOPING) + int br_multicast_list_adjacent(struct net_device *dev, +@@ -155,4 +158,41 @@ br_port_flag_is_set(const struct net_dev + } + #endif + ++/* QCA NSS ECM support - Start */ ++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; ++ struct net_bridge *br; ++ struct net_device *orig_dev; ++}; ++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; ++/* QCA NSS ECM support - End */ ++ + #endif +--- a/include/linux/if_vlan.h ++++ b/include/linux/if_vlan.h +@@ -222,7 +222,28 @@ extern void vlan_vids_del_by_dev(struct + + extern bool vlan_uses_dev(const struct net_device *dev); + ++/* QCA NSS ECM support - Start */ ++extern void __vlan_dev_update_accel_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *stats); ++extern u16 vlan_dev_get_egress_prio(struct net_device *dev, u32 skb_prio); ++extern struct net_device *vlan_dev_next_dev(const struct net_device *dev); ++/* QCA NSS ECM support - End */ ++ + #else ++/* QCA NSS ECM support - Start */ ++static inline void __vlan_dev_update_accel_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *stats) ++{ ++ ++} ++ ++static inline u16 vlan_dev_get_egress_prio(struct net_device *dev, ++ u32 skb_prio) ++{ ++ return 0; ++} ++/* QCA NSS ECM support - End */ ++ + static inline struct net_device * + __vlan_find_dev_deep_rcu(struct net_device *real_dev, + __be16 vlan_proto, u16 vlan_id) +--- a/include/linux/netdevice.h ++++ b/include/linux/netdevice.h +@@ -2745,6 +2745,10 @@ enum netdev_cmd { + NETDEV_CVLAN_FILTER_DROP_INFO, + NETDEV_SVLAN_FILTER_PUSH_INFO, + NETDEV_SVLAN_FILTER_DROP_INFO, ++ /* QCA NSS ECM Support - Start */ ++ NETDEV_BR_JOIN, ++ NETDEV_BR_LEAVE, ++ /* QCA NSS ECM Support - End */ + }; + const char *netdev_cmd_to_name(enum netdev_cmd cmd); + +--- a/include/net/ip6_route.h ++++ b/include/net/ip6_route.h +@@ -210,6 +210,11 @@ void rt6_multipath_rebalance(struct fib6 + void rt6_uncached_list_add(struct rt6_info *rt); + void rt6_uncached_list_del(struct rt6_info *rt); + ++/* QCA NSS ECM support - Start */ ++int rt6_register_notifier(struct notifier_block *nb); ++int rt6_unregister_notifier(struct notifier_block *nb); ++/* QCA NSS ECM support - End */ ++ + static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb) + { + const struct dst_entry *dst = skb_dst(skb); +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -568,4 +568,15 @@ static inline void neigh_update_is_route + *notify = 1; + } + } ++ ++/* QCA NSS ECM support - Start */ ++struct neigh_mac_update { ++ unsigned char old_mac[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))]; ++ unsigned char update_mac[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))]; ++}; ++ ++extern void neigh_mac_update_register_notify(struct notifier_block *nb); ++extern void neigh_mac_update_unregister_notify(struct notifier_block *nb); ++/* QCA NSS ECM support - End */ ++ + #endif +--- a/include/net/route.h ++++ b/include/net/route.h +@@ -234,6 +234,11 @@ struct rtable *rt_dst_alloc(struct net_d + bool nopolicy, bool noxfrm); + struct rtable *rt_dst_clone(struct net_device *dev, struct rtable *rt); + ++/* QCA NSS ECM support - Start */ ++int ip_rt_register_notifier(struct notifier_block *nb); ++int ip_rt_unregister_notifier(struct notifier_block *nb); ++/* QCA NSS ECM support - End */ ++ + struct in_ifaddr; + void fib_add_ifaddr(struct in_ifaddr *); + void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *); +--- a/net/8021q/vlan_core.c ++++ b/net/8021q/vlan_core.c +@@ -550,4 +550,52 @@ static int __init vlan_offload_init(void + return 0; + } + ++/* QCA NSS ECM support - Start */ ++/* Update the VLAN device with statistics from network offload engines */ ++void __vlan_dev_update_accel_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *nlstats) ++{ ++ struct vlan_pcpu_stats *stats; ++ ++ if (!is_vlan_dev(dev)) ++ return; ++ ++ stats = per_cpu_ptr(vlan_dev_priv(dev)->vlan_pcpu_stats, 0); ++ ++ u64_stats_update_begin(&stats->syncp); ++ stats->rx_packets += nlstats->rx_packets; ++ stats->rx_bytes += nlstats->rx_bytes; ++ stats->tx_packets += nlstats->tx_packets; ++ stats->tx_bytes += nlstats->tx_bytes; ++ u64_stats_update_end(&stats->syncp); ++} ++EXPORT_SYMBOL(__vlan_dev_update_accel_stats); ++ ++/* Lookup the 802.1p egress_map table and return the 802.1p value */ ++u16 vlan_dev_get_egress_prio(struct net_device *dev, u32 skb_prio) ++{ ++ struct vlan_priority_tci_mapping *mp; ++ ++ mp = vlan_dev_priv(dev)->egress_priority_map[(skb_prio & 0xf)]; ++ while (mp) { ++ if (mp->priority == skb_prio) { ++ /* This should already be shifted ++ * to mask correctly with the ++ * VLAN's TCI ++ */ ++ return mp->vlan_qos; ++ } ++ mp = mp->next; ++ } ++ return 0; ++} ++EXPORT_SYMBOL(vlan_dev_get_egress_prio); ++ ++struct net_device *vlan_dev_next_dev(const struct net_device *dev) ++{ ++ return vlan_dev_priv(dev)->real_dev; ++} ++EXPORT_SYMBOL(vlan_dev_next_dev); ++/* QCA NSS ECM support - End */ ++ + fs_initcall(vlan_offload_init); +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -37,6 +37,35 @@ static int fdb_insert(struct net_bridge + static void fdb_notify(struct net_bridge *br, + const struct net_bridge_fdb_entry *, int, bool); + ++/* QCA NSS ECM support - Start */ ++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); ++/* QCA NSS ECM support - End */ ++ + int __init br_fdb_init(void) + { + br_fdb_cache = kmem_cache_create("bridge_fdb_cache", +@@ -342,6 +371,7 @@ void br_fdb_cleanup(struct work_struct * + unsigned long delay = hold_time(br); + unsigned long work_delay = delay; + unsigned long now = jiffies; ++ u8 mac_addr[6]; /* QCA NSS ECM support */ + + /* this part is tricky, in order to avoid blocking learning and + * consequently forwarding, we rely on rcu to delete objects with +@@ -368,8 +398,15 @@ void br_fdb_cleanup(struct work_struct * + 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)) { ++ ether_addr_copy(mac_addr, f->key.addr.addr); + fdb_delete(br, f, true); ++ /* QCA NSS ECM support - Start */ ++ atomic_notifier_call_chain( ++ &br_fdb_update_notifier_list, 0, ++ (void *)mac_addr); ++ /* QCA NSS ECM support - End */ ++ } + spin_unlock_bh(&br->hash_lock); + } + } +@@ -610,6 +647,12 @@ void br_fdb_update(struct net_bridge *br + &fdb->flags))) + clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, + &fdb->flags); ++ ++ /* QCA NSS ECM support - Start */ ++ atomic_notifier_call_chain( ++ &br_fdb_update_notifier_list, ++ 0, (void *)addr); ++ /* QCA NSS ECM support - End */ + } + + if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) +@@ -734,6 +777,25 @@ static void fdb_notify(struct net_bridge + struct sk_buff *skb; + int err = -ENOBUFS; + ++ /* QCA NSS ECM support - Start */ ++ 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 = test_bit(BR_FDB_LOCAL, &fdb->dst->flags); ++ atomic_notifier_call_chain(&br_fdb_notifier_list, ++ event, ++ (void *)&fdb_event); ++ } ++ /* QCA NSS ECM support - End */ ++ + if (swdev_notify) + br_switchdev_fdb_notify(br, fdb, type); + +@@ -1302,3 +1364,44 @@ void br_fdb_clear_offload(const struct n + spin_unlock_bh(&p->br->hash_lock); + } + EXPORT_SYMBOL_GPL(br_fdb_clear_offload); ++ ++/* QCA NSS ECM support - Start */ ++/* 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); ++/* QCA NSS ECM support - End */ ++ +--- a/net/bridge/br_if.c ++++ b/net/bridge/br_if.c +@@ -26,6 +26,12 @@ + + #include "br_private.h" + ++/* QCA NSS ECM support - Start */ ++/* 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); ++/* QCA NSS ECM support - End */ ++ + /* + * Determine initial path cost based on speed. + * using recommendations from 802.1d standard +@@ -697,6 +703,8 @@ int br_add_if(struct net_bridge *br, str + + kobject_uevent(&p->kobj, KOBJ_ADD); + ++ call_netdevice_notifiers(NETDEV_BR_JOIN, dev); /* QCA NSS ECM support */ ++ + return 0; + + err7: +@@ -730,6 +738,8 @@ int br_del_if(struct net_bridge *br, str + if (!p || p->br != br) + return -EINVAL; + ++ call_netdevice_notifiers(NETDEV_BR_LEAVE, dev); /* QCA NSS ECM support */ ++ + /* 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. +@@ -773,3 +783,98 @@ bool br_port_flag_is_set(const struct ne + return p->flags & flag; + } + EXPORT_SYMBOL_GPL(br_port_flag_is_set); ++ ++/* QCA NSS ECM support - Start */ ++/* Update bridge statistics for bridge packets processed by offload engines */ ++void br_dev_update_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *nlstats) ++{ ++ struct net_bridge *br; ++ struct pcpu_sw_netstats *stats; ++ ++ /* Is this a bridge? */ ++ if (!(dev->priv_flags & IFF_EBRIDGE)) ++ return; ++ ++ br = netdev_priv(dev); ++ stats = this_cpu_ptr(br->stats); ++ ++ u64_stats_update_begin(&stats->syncp); ++ stats->rx_packets += nlstats->rx_packets; ++ stats->rx_bytes += nlstats->rx_bytes; ++ stats->tx_packets += nlstats->tx_packets; ++ stats->tx_bytes += nlstats->tx_bytes; ++ u64_stats_update_end(&stats->syncp); ++} ++EXPORT_SYMBOL_GPL(br_dev_update_stats); ++ ++/* API to know if hairpin feature is enabled/disabled on this bridge port */ ++bool br_is_hairpin_enabled(struct net_device *dev) ++{ ++ struct net_bridge_port *port = br_port_get_check_rcu(dev); ++ ++ if (likely(port)) ++ return port->flags & BR_HAIRPIN_MODE; ++ return false; ++} ++EXPORT_SYMBOL_GPL(br_is_hairpin_enabled); ++ ++/* 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); ++/* QCA NSS ECM support - End */ +--- a/net/bridge/br_private.h ++++ b/net/bridge/br_private.h +@@ -1593,4 +1593,9 @@ 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); ++ ++/* QCA NSS ECM support - Start */ ++#define __br_get(__hook, __default, __args ...) \ ++ (__hook ? (__hook(__args)) : (__default)) ++/* QCA NSS ECM support - End */ + #endif +--- a/net/core/neighbour.c ++++ b/net/core/neighbour.c +@@ -1205,7 +1205,21 @@ static void neigh_update_hhs(struct neig + } + } + ++/* QCA NSS ECM support - start */ ++ATOMIC_NOTIFIER_HEAD(neigh_mac_update_notifier_list); ++ ++void neigh_mac_update_register_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_register(&neigh_mac_update_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(neigh_mac_update_register_notify); + ++void neigh_mac_update_unregister_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_unregister(&neigh_mac_update_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(neigh_mac_update_unregister_notify); ++/* QCA NSS ECM support - End */ + + /* Generic update routine. + -- lladdr is new lladdr or NULL, if it is not supplied. +@@ -1236,6 +1250,7 @@ static int __neigh_update(struct neighbo + int notify = 0; + struct net_device *dev; + int update_isrouter = 0; ++ struct neigh_mac_update nmu; /* QCA NSS ECM support */ + + trace_neigh_update(neigh, lladdr, new, flags, nlmsg_pid); + +@@ -1250,6 +1265,8 @@ static int __neigh_update(struct neighbo + new = old; + goto out; + } ++ memset(&nmu, 0, sizeof(struct neigh_mac_update)); /* QCA NSS ECM support */ ++ + if (!(flags & NEIGH_UPDATE_F_ADMIN) && + (old & (NUD_NOARP | NUD_PERMANENT))) + goto out; +@@ -1281,6 +1298,11 @@ static int __neigh_update(struct neighbo + - compare new & old + - if they are different, check override flag + */ ++ /* QCA NSS ECM update - Start */ ++ memcpy(nmu.old_mac, neigh->ha, dev->addr_len); ++ memcpy(nmu.update_mac, lladdr, dev->addr_len); ++ /* QCA NSS ECM update - End */ ++ + if ((old & NUD_VALID) && + !memcmp(lladdr, neigh->ha, dev->addr_len)) + lladdr = neigh->ha; +@@ -1403,8 +1425,11 @@ out: + if (((new ^ old) & NUD_PERMANENT) || ext_learn_change) + neigh_update_gc_list(neigh); + +- if (notify) ++ if (notify) { + neigh_update_notify(neigh, nlmsg_pid); ++ atomic_notifier_call_chain(&neigh_mac_update_notifier_list, 0, ++ (struct neigh_mac_update *)&nmu); /* QCA NSS ECM support */ ++ } + + trace_neigh_update_done(neigh, err); + +--- a/net/ipv4/fib_trie.c ++++ b/net/ipv4/fib_trie.c +@@ -1164,6 +1164,9 @@ static bool fib_valid_key_len(u32 key, u + static void fib_remove_alias(struct trie *t, struct key_vector *tp, + struct key_vector *l, struct fib_alias *old); + ++/* Define route change notification chain. */ ++static BLOCKING_NOTIFIER_HEAD(iproute_chain); /* QCA NSS ECM support */ ++ + /* Caller must hold RTNL. */ + int fib_table_insert(struct net *net, struct fib_table *tb, + struct fib_config *cfg, struct netlink_ext_ack *extack) +@@ -1352,6 +1355,9 @@ int fib_table_insert(struct net *net, st + rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, new_fa->tb_id, + &cfg->fc_nlinfo, nlflags); + succeeded: ++ blocking_notifier_call_chain(&iproute_chain, ++ RTM_NEWROUTE, fi); ++ + return 0; + + out_remove_new_fa: +@@ -1722,6 +1728,9 @@ int fib_table_delete(struct net *net, st + if (fa_to_delete->fa_state & FA_S_ACCESSED) + rt_cache_flush(cfg->fc_nlinfo.nl_net); + ++ blocking_notifier_call_chain(&iproute_chain, ++ RTM_DELROUTE, fa_to_delete->fa_info); ++ + fib_release_info(fa_to_delete->fa_info); + alias_free_mem_rcu(fa_to_delete); + return 0; +@@ -2358,6 +2367,20 @@ void __init fib_trie_init(void) + 0, SLAB_PANIC, NULL); + } + ++/* QCA NSS ECM support - Start */ ++int ip_rt_register_notifier(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_register(&iproute_chain, nb); ++} ++EXPORT_SYMBOL(ip_rt_register_notifier); ++ ++int ip_rt_unregister_notifier(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_unregister(&iproute_chain, nb); ++} ++EXPORT_SYMBOL(ip_rt_unregister_notifier); ++/* QCA NSS ECM support - End */ ++ + struct fib_table *fib_trie_table(u32 id, struct fib_table *alias) + { + struct fib_table *tb; +--- a/include/net/addrconf.h ++++ b/include/net/addrconf.h +@@ -501,4 +501,9 @@ int if6_proc_init(void); + void if6_proc_exit(void); + #endif + ++/* QCA NSS ECM support - Start */ ++struct net_device *ipv6_dev_find_and_hold(struct net *net, struct in6_addr *addr, ++ int strict); ++/* QCA NSS ECM support - End */ ++ + #endif +--- a/net/ipv6/addrconf.c ++++ b/net/ipv6/addrconf.c +@@ -7233,3 +7233,35 @@ void addrconf_cleanup(void) + + destroy_workqueue(addrconf_wq); + } ++ ++/* QCA NSS ECM support - Start */ ++/* ipv6_dev_find_and_hold() ++ * Find (and hold) net device that has the given address. ++ * Or NULL on failure. ++ */ ++struct net_device *ipv6_dev_find_and_hold(struct net *net, struct in6_addr *addr, ++ int strict) ++{ ++ struct inet6_ifaddr *ifp; ++ struct net_device *dev; ++ ++ ifp = ipv6_get_ifaddr(net, addr, NULL, strict); ++ if (!ifp) ++ return NULL; ++ ++ if (!ifp->idev) { ++ in6_ifa_put(ifp); ++ return NULL; ++ } ++ ++ dev = ifp->idev->dev; ++ if (dev) ++ dev_hold(dev); ++ ++ in6_ifa_put(ifp); ++ ++ return dev; ++} ++EXPORT_SYMBOL(ipv6_dev_find_and_hold); ++/* QCA NSS ECM support - End */ ++ +--- a/net/ipv6/ndisc.c ++++ b/net/ipv6/ndisc.c +@@ -649,6 +649,7 @@ void ndisc_send_ns(struct net_device *de + + ndisc_send_skb(skb, daddr, saddr); + } ++EXPORT_SYMBOL(ndisc_send_ns); + + void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr, + const struct in6_addr *daddr) +--- a/net/ipv6/route.c ++++ b/net/ipv6/route.c +@@ -3733,6 +3733,9 @@ out: + return ERR_PTR(err); + } + ++/* Define route change notification chain. */ ++ATOMIC_NOTIFIER_HEAD(ip6route_chain); /* QCA NSS ECM support */ ++ + int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, + struct netlink_ext_ack *extack) + { +@@ -3744,6 +3747,10 @@ int ip6_route_add(struct fib6_config *cf + return PTR_ERR(rt); + + err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack); ++ if (!err) ++ atomic_notifier_call_chain(&ip6route_chain, ++ RTM_NEWROUTE, rt); ++ + fib6_info_release(rt); + + return err; +@@ -3765,6 +3772,9 @@ static int __ip6_del_rt(struct fib6_info + err = fib6_del(rt, info); + spin_unlock_bh(&table->tb6_lock); + ++ if (!err) ++ atomic_notifier_call_chain(&ip6route_chain, ++ RTM_DELROUTE, rt); + out: + fib6_info_release(rt); + return err; +@@ -6116,6 +6126,20 @@ static int ip6_route_dev_notify(struct n + return NOTIFY_OK; + } + ++/* QCA NSS ECM support - Start */ ++int rt6_register_notifier(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_register(&ip6route_chain, nb); ++} ++EXPORT_SYMBOL(rt6_register_notifier); ++ ++int rt6_unregister_notifier(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_unregister(&ip6route_chain, nb); ++} ++EXPORT_SYMBOL(rt6_unregister_notifier); ++/* QCA NSS ECM support - End */ ++ + /* + * /proc + */