#include <trace/events/fib.h>
 #include "fib_lookup.h"
 
+static unsigned int fib_seq_sum(void)
+{
+       unsigned int fib_seq = 0;
+       struct net *net;
+
+       rtnl_lock();
+       for_each_net(net)
+               fib_seq += net->ipv4.fib_seq;
+       rtnl_unlock();
+
+       return fib_seq;
+}
+
 static ATOMIC_NOTIFIER_HEAD(fib_chain);
 
-int register_fib_notifier(struct notifier_block *nb)
+static int call_fib_notifier(struct notifier_block *nb, struct net *net,
+                            enum fib_event_type event_type,
+                            struct fib_notifier_info *info)
 {
-       return atomic_notifier_chain_register(&fib_chain, nb);
+       info->net = net;
+       return nb->notifier_call(nb, event_type, info);
+}
+
+static void fib_rules_notify(struct net *net, struct notifier_block *nb,
+                            enum fib_event_type event_type)
+{
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+       struct fib_notifier_info info;
+
+       if (net->ipv4.fib_has_custom_rules)
+               call_fib_notifier(nb, net, event_type, &info);
+#endif
+}
+
+static void fib_notify(struct net *net, struct notifier_block *nb,
+                      enum fib_event_type event_type);
+
+static int call_fib_entry_notifier(struct notifier_block *nb, struct net *net,
+                                  enum fib_event_type event_type, u32 dst,
+                                  int dst_len, struct fib_info *fi,
+                                  u8 tos, u8 type, u32 tb_id, u32 nlflags)
+{
+       struct fib_entry_notifier_info info = {
+               .dst = dst,
+               .dst_len = dst_len,
+               .fi = fi,
+               .tos = tos,
+               .type = type,
+               .tb_id = tb_id,
+               .nlflags = nlflags,
+       };
+       return call_fib_notifier(nb, net, event_type, &info.info);
+}
+
+static bool fib_dump_is_consistent(struct notifier_block *nb,
+                                  void (*cb)(struct notifier_block *nb),
+                                  unsigned int fib_seq)
+{
+       atomic_notifier_chain_register(&fib_chain, nb);
+       if (fib_seq == fib_seq_sum())
+               return true;
+       atomic_notifier_chain_unregister(&fib_chain, nb);
+       if (cb)
+               cb(nb);
+       return false;
+}
+
+#define FIB_DUMP_MAX_RETRIES 5
+int register_fib_notifier(struct notifier_block *nb,
+                         void (*cb)(struct notifier_block *nb))
+{
+       int retries = 0;
+
+       do {
+               unsigned int fib_seq = fib_seq_sum();
+               struct net *net;
+
+               /* Mutex semantics guarantee that every change done to
+                * FIB tries before we read the change sequence counter
+                * is now visible to us.
+                */
+               rcu_read_lock();
+               for_each_net_rcu(net) {
+                       fib_rules_notify(net, nb, FIB_EVENT_RULE_ADD);
+                       fib_notify(net, nb, FIB_EVENT_ENTRY_ADD);
+               }
+               rcu_read_unlock();
+
+               if (fib_dump_is_consistent(nb, cb, fib_seq))
+                       return 0;
+       } while (++retries < FIB_DUMP_MAX_RETRIES);
+
+       return -EBUSY;
 }
 EXPORT_SYMBOL(register_fib_notifier);
 
        return found;
 }
 
+static void fib_leaf_notify(struct net *net, struct key_vector *l,
+                           struct fib_table *tb, struct notifier_block *nb,
+                           enum fib_event_type event_type)
+{
+       struct fib_alias *fa;
+
+       hlist_for_each_entry_rcu(fa, &l->leaf, fa_list) {
+               struct fib_info *fi = fa->fa_info;
+
+               if (!fi)
+                       continue;
+
+               /* local and main table can share the same trie,
+                * so don't notify twice for the same entry.
+                */
+               if (tb->tb_id != fa->tb_id)
+                       continue;
+
+               call_fib_entry_notifier(nb, net, event_type, l->key,
+                                       KEYLENGTH - fa->fa_slen, fi, fa->fa_tos,
+                                       fa->fa_type, fa->tb_id, 0);
+       }
+}
+
+static void fib_table_notify(struct net *net, struct fib_table *tb,
+                            struct notifier_block *nb,
+                            enum fib_event_type event_type)
+{
+       struct trie *t = (struct trie *)tb->tb_data;
+       struct key_vector *l, *tp = t->kv;
+       t_key key = 0;
+
+       while ((l = leaf_walk_rcu(&tp, key)) != NULL) {
+               fib_leaf_notify(net, l, tb, nb, event_type);
+
+               key = l->key + 1;
+               /* stop in case of wrap around */
+               if (key < l->key)
+                       break;
+       }
+}
+
+static void fib_notify(struct net *net, struct notifier_block *nb,
+                      enum fib_event_type event_type)
+{
+       unsigned int h;
+
+       for (h = 0; h < FIB_TABLE_HASHSZ; h++) {
+               struct hlist_head *head = &net->ipv4.fib_table_hash[h];
+               struct fib_table *tb;
+
+               hlist_for_each_entry_rcu(tb, head, tb_hlist)
+                       fib_table_notify(net, tb, nb, event_type);
+       }
+}
+
 static void __trie_free_rcu(struct rcu_head *head)
 {
        struct fib_table *tb = container_of(head, struct fib_table, rcu);