|
@@ -1529,25 +1529,6 @@ static void fib6_clean_tree(struct net *net, struct fib6_node *root,
|
|
|
fib6_walk(&c.w);
|
|
|
}
|
|
|
|
|
|
-void fib6_clean_all_ro(struct net *net, int (*func)(struct rt6_info *, void *arg),
|
|
|
- int prune, void *arg)
|
|
|
-{
|
|
|
- struct fib6_table *table;
|
|
|
- struct hlist_head *head;
|
|
|
- unsigned int h;
|
|
|
-
|
|
|
- rcu_read_lock();
|
|
|
- for (h = 0; h < FIB6_TABLE_HASHSZ; h++) {
|
|
|
- head = &net->ipv6.fib_table_hash[h];
|
|
|
- hlist_for_each_entry_rcu(table, head, tb6_hlist) {
|
|
|
- read_lock_bh(&table->tb6_lock);
|
|
|
- fib6_clean_tree(net, &table->tb6_root,
|
|
|
- func, prune, arg);
|
|
|
- read_unlock_bh(&table->tb6_lock);
|
|
|
- }
|
|
|
- }
|
|
|
- rcu_read_unlock();
|
|
|
-}
|
|
|
void fib6_clean_all(struct net *net, int (*func)(struct rt6_info *, void *arg),
|
|
|
int prune, void *arg)
|
|
|
{
|
|
@@ -1782,3 +1763,175 @@ void fib6_gc_cleanup(void)
|
|
|
unregister_pernet_subsys(&fib6_net_ops);
|
|
|
kmem_cache_destroy(fib6_node_kmem);
|
|
|
}
|
|
|
+
|
|
|
+#ifdef CONFIG_PROC_FS
|
|
|
+
|
|
|
+struct ipv6_route_iter {
|
|
|
+ struct seq_net_private p;
|
|
|
+ struct fib6_walker_t w;
|
|
|
+ loff_t skip;
|
|
|
+ struct fib6_table *tbl;
|
|
|
+};
|
|
|
+
|
|
|
+static int ipv6_route_seq_show(struct seq_file *seq, void *v)
|
|
|
+{
|
|
|
+ struct rt6_info *rt = v;
|
|
|
+ struct ipv6_route_iter *iter = seq->private;
|
|
|
+
|
|
|
+ seq_printf(seq, "%pi6 %02x ", &rt->rt6i_dst.addr, rt->rt6i_dst.plen);
|
|
|
+
|
|
|
+#ifdef CONFIG_IPV6_SUBTREES
|
|
|
+ seq_printf(seq, "%pi6 %02x ", &rt->rt6i_src.addr, rt->rt6i_src.plen);
|
|
|
+#else
|
|
|
+ seq_puts(seq, "00000000000000000000000000000000 00 ");
|
|
|
+#endif
|
|
|
+ if (rt->rt6i_flags & RTF_GATEWAY)
|
|
|
+ seq_printf(seq, "%pi6", &rt->rt6i_gateway);
|
|
|
+ else
|
|
|
+ seq_puts(seq, "00000000000000000000000000000000");
|
|
|
+
|
|
|
+ seq_printf(seq, " %08x %08x %08x %08x %8s\n",
|
|
|
+ rt->rt6i_metric, atomic_read(&rt->dst.__refcnt),
|
|
|
+ rt->dst.__use, rt->rt6i_flags,
|
|
|
+ rt->dst.dev ? rt->dst.dev->name : "");
|
|
|
+ iter->w.leaf = NULL;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ipv6_route_yield(struct fib6_walker_t *w)
|
|
|
+{
|
|
|
+ struct ipv6_route_iter *iter = w->args;
|
|
|
+
|
|
|
+ if (!iter->skip)
|
|
|
+ return 1;
|
|
|
+
|
|
|
+ do {
|
|
|
+ iter->w.leaf = iter->w.leaf->dst.rt6_next;
|
|
|
+ iter->skip--;
|
|
|
+ if (!iter->skip && iter->w.leaf)
|
|
|
+ return 1;
|
|
|
+ } while (iter->w.leaf);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
|
|
|
+{
|
|
|
+ memset(&iter->w, 0, sizeof(iter->w));
|
|
|
+ iter->w.func = ipv6_route_yield;
|
|
|
+ iter->w.root = &iter->tbl->tb6_root;
|
|
|
+ iter->w.state = FWS_INIT;
|
|
|
+ iter->w.node = iter->w.root;
|
|
|
+ iter->w.args = iter;
|
|
|
+ INIT_LIST_HEAD(&iter->w.lh);
|
|
|
+ fib6_walker_link(&iter->w);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fib6_table *ipv6_route_seq_next_table(struct fib6_table *tbl,
|
|
|
+ struct net *net)
|
|
|
+{
|
|
|
+ unsigned int h;
|
|
|
+ struct hlist_node *node;
|
|
|
+
|
|
|
+ if (tbl) {
|
|
|
+ h = (tbl->tb6_id & (FIB6_TABLE_HASHSZ - 1)) + 1;
|
|
|
+ node = rcu_dereference_bh(hlist_next_rcu(&tbl->tb6_hlist));
|
|
|
+ } else {
|
|
|
+ h = 0;
|
|
|
+ node = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (!node && h < FIB6_TABLE_HASHSZ) {
|
|
|
+ node = rcu_dereference_bh(
|
|
|
+ hlist_first_rcu(&net->ipv6.fib_table_hash[h++]));
|
|
|
+ }
|
|
|
+ return hlist_entry_safe(node, struct fib6_table, tb6_hlist);
|
|
|
+}
|
|
|
+
|
|
|
+static void *ipv6_route_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
|
+{
|
|
|
+ int r;
|
|
|
+ struct rt6_info *n;
|
|
|
+ struct net *net = seq_file_net(seq);
|
|
|
+ struct ipv6_route_iter *iter = seq->private;
|
|
|
+
|
|
|
+ if (!v)
|
|
|
+ goto iter_table;
|
|
|
+
|
|
|
+ n = ((struct rt6_info *)v)->dst.rt6_next;
|
|
|
+ if (n) {
|
|
|
+ ++*pos;
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+
|
|
|
+iter_table:
|
|
|
+ read_lock(&iter->tbl->tb6_lock);
|
|
|
+ r = fib6_walk_continue(&iter->w);
|
|
|
+ read_unlock(&iter->tbl->tb6_lock);
|
|
|
+ if (r > 0) {
|
|
|
+ if (v)
|
|
|
+ ++*pos;
|
|
|
+ return iter->w.leaf;
|
|
|
+ } else if (r < 0) {
|
|
|
+ fib6_walker_unlink(&iter->w);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ fib6_walker_unlink(&iter->w);
|
|
|
+
|
|
|
+ iter->tbl = ipv6_route_seq_next_table(iter->tbl, net);
|
|
|
+ if (!iter->tbl)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ ipv6_route_seq_setup_walk(iter);
|
|
|
+ goto iter_table;
|
|
|
+}
|
|
|
+
|
|
|
+static void *ipv6_route_seq_start(struct seq_file *seq, loff_t *pos)
|
|
|
+ __acquires(RCU_BH)
|
|
|
+{
|
|
|
+ struct net *net = seq_file_net(seq);
|
|
|
+ struct ipv6_route_iter *iter = seq->private;
|
|
|
+
|
|
|
+ rcu_read_lock_bh();
|
|
|
+ iter->tbl = ipv6_route_seq_next_table(NULL, net);
|
|
|
+ iter->skip = *pos;
|
|
|
+
|
|
|
+ if (iter->tbl) {
|
|
|
+ ipv6_route_seq_setup_walk(iter);
|
|
|
+ return ipv6_route_seq_next(seq, NULL, pos);
|
|
|
+ } else {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool ipv6_route_iter_active(struct ipv6_route_iter *iter)
|
|
|
+{
|
|
|
+ struct fib6_walker_t *w = &iter->w;
|
|
|
+ return w->node && !(w->state == FWS_U && w->node == w->root);
|
|
|
+}
|
|
|
+
|
|
|
+static void ipv6_route_seq_stop(struct seq_file *seq, void *v)
|
|
|
+ __releases(RCU_BH)
|
|
|
+{
|
|
|
+ struct ipv6_route_iter *iter = seq->private;
|
|
|
+
|
|
|
+ if (ipv6_route_iter_active(iter))
|
|
|
+ fib6_walker_unlink(&iter->w);
|
|
|
+
|
|
|
+ rcu_read_unlock_bh();
|
|
|
+}
|
|
|
+
|
|
|
+static const struct seq_operations ipv6_route_seq_ops = {
|
|
|
+ .start = ipv6_route_seq_start,
|
|
|
+ .next = ipv6_route_seq_next,
|
|
|
+ .stop = ipv6_route_seq_stop,
|
|
|
+ .show = ipv6_route_seq_show
|
|
|
+};
|
|
|
+
|
|
|
+int ipv6_route_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ return seq_open_net(inode, file, &ipv6_route_seq_ops,
|
|
|
+ sizeof(struct ipv6_route_iter));
|
|
|
+}
|
|
|
+
|
|
|
+#endif /* CONFIG_PROC_FS */
|