|
@@ -84,11 +84,99 @@
|
|
|
#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);
|
|
|
|
|
@@ -1902,6 +1990,62 @@ int fib_table_flush(struct net *net, struct fib_table *tb)
|
|
|
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);
|