static void fib6_purge_rt(struct rt6_info *rt, struct fib6_node *fn, struct net *net) { if (atomic_read(&rt->rt6i_ref) != 1) { /* This route is used as dummy address holder in some split * nodes. It is not leaked, but it still holds other resources, * which must be released in time. So, scan ascendant nodes * and replace dummy references to this route with references * to still alive ones. */ while (fn) { if (!(fn->fn_flags & RTN_RTINFO) && fn->leaf == rt) { fn->leaf = fib6_find_prefix(net, fn); atomic_inc(&fn->leaf->rt6i_ref); rt6_release(rt); } fn = fn->parent; } /* No more references are possible at this point. */ BUG_ON(atomic_read(&rt->rt6i_ref) != 1); } }
static void fib6_del_route(struct fib6_node *fn, struct rt6_info **rtp) { struct fib6_walker_t *w; struct rt6_info *rt = *rtp; RT6_TRACE("fib6_del_route\n"); /* Unlink it */ *rtp = rt->u.next; rt->rt6i_node = NULL; rt6_stats.fib_rt_entries--; /* Adjust walkers */ FOR_WALKERS(w) { if (w->state == FWS_C && w->leaf == rt) { RT6_TRACE("walker %p adjusted by delroute\n", w); w->leaf = rt->u.next; if (w->leaf == NULL) w->state = FWS_U; } } rt->u.next = NULL; /* If it was last route, expunge its radix tree node */ if (fn->leaf == NULL) { fn->fn_flags &= ~RTN_RTINFO; rt6_stats.fib_route_nodes--; fib6_repair_tree(fn); } #ifdef CONFIG_RTNETLINK inet6_rt_notify(RTM_DELROUTE, rt); #endif rt6_release(rt); }
static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, struct nl_info *info) { struct rt6_info *iter = NULL; struct rt6_info **ins; int replace = (info->nlh && (info->nlh->nlmsg_flags & NLM_F_REPLACE)); int add = (!info->nlh || (info->nlh->nlmsg_flags & NLM_F_CREATE)); int found = 0; ins = &fn->leaf; for (iter = fn->leaf; iter; iter = iter->dst.rt6_next) { /* * Search for duplicates */ if (iter->rt6i_metric == rt->rt6i_metric) { /* * Same priority level */ if (info->nlh && (info->nlh->nlmsg_flags & NLM_F_EXCL)) return -EEXIST; if (replace) { found++; break; } if (iter->dst.dev == rt->dst.dev && iter->rt6i_idev == rt->rt6i_idev && ipv6_addr_equal(&iter->rt6i_gateway, &rt->rt6i_gateway)) { if (!(iter->rt6i_flags & RTF_EXPIRES)) return -EEXIST; if (!(rt->rt6i_flags & RTF_EXPIRES)) rt6_clean_expires(iter); else rt6_set_expires(iter, rt->dst.expires); return -EEXIST; } } if (iter->rt6i_metric > rt->rt6i_metric) break; ins = &iter->dst.rt6_next; } /* Reset round-robin state, if necessary */ if (ins == &fn->leaf) fn->rr_ptr = NULL; /* * insert node */ if (!replace) { if (!add) pr_warn("NLM_F_CREATE should be set when creating new route\n"); add: rt->dst.rt6_next = iter; *ins = rt; rt->rt6i_node = fn; atomic_inc(&rt->rt6i_ref); inet6_rt_notify(RTM_NEWROUTE, rt, info); info->nl_net->ipv6.rt6_stats->fib_rt_entries++; if (!(fn->fn_flags & RTN_RTINFO)) { info->nl_net->ipv6.rt6_stats->fib_route_nodes++; fn->fn_flags |= RTN_RTINFO; } } else { if (!found) { if (add) goto add; pr_warn("NLM_F_REPLACE set, but no existing node found!\n"); return -ENOENT; } *ins = rt; rt->rt6i_node = fn; rt->dst.rt6_next = iter->dst.rt6_next; atomic_inc(&rt->rt6i_ref); inet6_rt_notify(RTM_NEWROUTE, rt, info); rt6_release(iter); if (!(fn->fn_flags & RTN_RTINFO)) { info->nl_net->ipv6.rt6_stats->fib_route_nodes++; fn->fn_flags |= RTN_RTINFO; } } return 0; }
static struct fib6_node * fib6_add_1(struct fib6_node *root, void *addr, int addrlen, int plen, int offset, int allow_create, int replace_required) { struct fib6_node *fn, *in, *ln; struct fib6_node *pn = NULL; struct rt6key *key; int bit; __be32 dir = 0; __u32 sernum = fib6_new_sernum(); RT6_TRACE("fib6_add_1\n"); /* insert node in tree */ fn = root; do { key = (struct rt6key *)((u8 *)fn->leaf + offset); /* * Prefix match */ if (plen < fn->fn_bit || !ipv6_prefix_equal(&key->addr, addr, fn->fn_bit)) { if (!allow_create) { if (replace_required) { pr_warn("Can't replace route, no match found\n"); return ERR_PTR(-ENOENT); } pr_warn("NLM_F_CREATE should be set when creating new route\n"); } goto insert_above; } /* * Exact match ? */ if (plen == fn->fn_bit) { /* clean up an intermediate node */ if (!(fn->fn_flags & RTN_RTINFO)) { rt6_release(fn->leaf); fn->leaf = NULL; } fn->fn_sernum = sernum; return fn; } /* * We have more bits to go */ /* Try to walk down on tree. */ fn->fn_sernum = sernum; dir = addr_bit_set(addr, fn->fn_bit); pn = fn; fn = dir ? fn->right: fn->left; } while (fn); if (!allow_create) { /* We should not create new node because * NLM_F_REPLACE was specified without NLM_F_CREATE * I assume it is safe to require NLM_F_CREATE when * REPLACE flag is used! Later we may want to remove the * check for replace_required, because according * to netlink specification, NLM_F_CREATE * MUST be specified if new route is created. * That would keep IPv6 consistent with IPv4 */ if (replace_required) { pr_warn("Can't replace route, no match found\n"); return ERR_PTR(-ENOENT); } pr_warn("NLM_F_CREATE should be set when creating new route\n"); } /* * We walked to the bottom of tree. * Create new leaf node without children. */ ln = node_alloc(); if (!ln) return NULL; ln->fn_bit = plen; ln->parent = pn; ln->fn_sernum = sernum; if (dir) pn->right = ln; else pn->left = ln; return ln; insert_above: /* * split since we don't have a common prefix anymore or * we have a less significant route. * we've to insert an intermediate node on the list * this new node will point to the one we need to create * and the current */ pn = fn->parent; /* find 1st bit in difference between the 2 addrs. See comment in __ipv6_addr_diff: bit may be an invalid value, but if it is >= plen, the value is ignored in any case. */ bit = __ipv6_addr_diff(addr, &key->addr, addrlen); /* * (intermediate)[in] * / \ * (new leaf node)[ln] (old node)[fn] */ if (plen > bit) { in = node_alloc(); ln = node_alloc(); if (!in || !ln) { if (in) node_free(in); if (ln) node_free(ln); return NULL; } /* * new intermediate node. * RTN_RTINFO will * be off since that an address that chooses one of * the branches would not match less specific routes * in the other branch */ in->fn_bit = bit; in->parent = pn; in->leaf = fn->leaf; atomic_inc(&in->leaf->rt6i_ref); in->fn_sernum = sernum; /* update parent pointer */ if (dir) pn->right = in; else pn->left = in; ln->fn_bit = plen; ln->parent = in; fn->parent = in; ln->fn_sernum = sernum; if (addr_bit_set(addr, bit)) { in->right = ln; in->left = fn; } else { in->left = ln; in->right = fn; } } else { /* plen <= bit */ /* * (new leaf node)[ln] * / \ * (old node)[fn] NULL */ ln = node_alloc(); if (!ln) return NULL; ln->fn_bit = plen; ln->parent = pn; ln->fn_sernum = sernum; if (dir) pn->right = ln; else pn->left = ln; if (addr_bit_set(&key->addr, plen)) ln->right = fn; else ln->left = fn; fn->parent = ln; } return ln; }
static struct fib6_node * fib6_add_1(struct fib6_node *root, void *addr, int addrlen, int plen, int offset) { struct fib6_node *fn, *in, *ln; struct fib6_node *pn = NULL; struct rt6key *key; int bit; int dir = 0; __u32 sernum = fib6_new_sernum(); RT6_TRACE("fib6_add_1\n"); /* insert node in tree */ fn = root; do { key = (struct rt6key *)((u8 *)fn->leaf + offset); /* * Prefix match */ if (plen < fn->fn_bit || !ipv6_prefix_equal(&key->addr, addr, fn->fn_bit)) goto insert_above; /* * Exact match ? */ if (plen == fn->fn_bit) { /* clean up an intermediate node */ if ((fn->fn_flags & RTN_RTINFO) == 0) { rt6_release(fn->leaf); fn->leaf = NULL; } fn->fn_sernum = sernum; return fn; } /* * We have more bits to go */ /* Try to walk down on tree. */ fn->fn_sernum = sernum; dir = addr_bit_set(addr, fn->fn_bit); pn = fn; fn = dir ? fn->right: fn->left; } while (fn); /* * We walked to the bottom of tree. * Create new leaf node without children. */ ln = node_alloc(); if (ln == NULL) return NULL; ln->fn_bit = plen; ln->parent = pn; ln->fn_sernum = sernum; if (dir) pn->right = ln; else pn->left = ln; return ln; insert_above: /* * split since we don't have a common prefix anymore or * we have a less significant route. * we've to insert an intermediate node on the list * this new node will point to the one we need to create * and the current */ pn = fn->parent; /* find 1st bit in difference between the 2 addrs. See comment in __ipv6_addr_diff: bit may be an invalid value, but if it is >= plen, the value is ignored in any case. */ bit = __ipv6_addr_diff(addr, &key->addr, addrlen); /* * (intermediate)[in] * / \ * (new leaf node)[ln] (old node)[fn] */ if (plen > bit) { in = node_alloc(); ln = node_alloc(); if (in == NULL || ln == NULL) { if (in) node_free(in); if (ln) node_free(ln); return NULL; } /* * new intermediate node. * RTN_RTINFO will * be off since that an address that chooses one of * the branches would not match less specific routes * in the other branch */ in->fn_bit = bit; in->parent = pn; in->leaf = fn->leaf; atomic_inc(&in->leaf->rt6i_ref); in->fn_sernum = sernum; /* update parent pointer */ if (dir) pn->right = in; else pn->left = in; ln->fn_bit = plen; ln->parent = in; fn->parent = in; ln->fn_sernum = sernum; if (addr_bit_set(addr, bit)) { in->right = ln; in->left = fn; } else { in->left = ln; in->right = fn; } } else { /* plen <= bit */ /* * (new leaf node)[ln] * / \ * (old node)[fn] NULL */ ln = node_alloc(); if (ln == NULL) return NULL; ln->fn_bit = plen; ln->parent = pn; ln->fn_sernum = sernum; if (dir) pn->right = ln; else pn->left = ln; if (addr_bit_set(&key->addr, plen)) ln->right = fn; else ln->left = fn; fn->parent = ln; } return ln; }
static struct fib6_node * fib6_add_1(struct fib6_node *root, void *addr, int addrlen, int plen, int offset, int allow_create, int replace_required) { struct fib6_node *fn, *in, *ln; struct fib6_node *pn = NULL; struct rt6key *key; int bit; __be32 dir = 0; __u32 sernum = fib6_new_sernum(); RT6_TRACE("fib6_add_1\n"); fn = root; do { key = (struct rt6key *)((u8 *)fn->leaf + offset); if (plen < fn->fn_bit || !ipv6_prefix_equal(&key->addr, addr, fn->fn_bit)) { if (!allow_create) { if (replace_required) { pr_warn("IPv6: Can't replace route, " "no match found\n"); return ERR_PTR(-ENOENT); } pr_warn("IPv6: NLM_F_CREATE should be set " "when creating new route\n"); } goto insert_above; } if (plen == fn->fn_bit) { if (!(fn->fn_flags & RTN_RTINFO)) { rt6_release(fn->leaf); fn->leaf = NULL; } fn->fn_sernum = sernum; return fn; } fn->fn_sernum = sernum; dir = addr_bit_set(addr, fn->fn_bit); pn = fn; fn = dir ? fn->right: fn->left; } while (fn); if (!allow_create) { if (replace_required) { pr_warn("IPv6: Can't replace route, no match found\n"); return ERR_PTR(-ENOENT); } pr_warn("IPv6: NLM_F_CREATE should be set " "when creating new route\n"); } ln = node_alloc(); if (!ln) return NULL; ln->fn_bit = plen; ln->parent = pn; ln->fn_sernum = sernum; if (dir) pn->right = ln; else pn->left = ln; return ln; insert_above: pn = fn->parent; bit = __ipv6_addr_diff(addr, &key->addr, addrlen); if (plen > bit) { in = node_alloc(); ln = node_alloc(); if (!in || !ln) { if (in) node_free(in); if (ln) node_free(ln); return NULL; } in->fn_bit = bit; in->parent = pn; in->leaf = fn->leaf; atomic_inc(&in->leaf->rt6i_ref); in->fn_sernum = sernum; if (dir) pn->right = in; else pn->left = in; ln->fn_bit = plen; ln->parent = in; fn->parent = in; ln->fn_sernum = sernum; if (addr_bit_set(addr, bit)) { in->right = ln; in->left = fn; } else { in->left = ln; in->right = fn; } } else { ln = node_alloc(); if (!ln) return NULL; ln->fn_bit = plen; ln->parent = pn; ln->fn_sernum = sernum; if (dir) pn->right = ln; else pn->left = ln; if (addr_bit_set(&key->addr, plen)) ln->right = fn; else ln->left = fn; fn->parent = ln; } return ln; }
static void fib6_repair_tree(struct fib6_node *fn) { int children; int nstate; struct fib6_node *child, *pn; struct fib6_walker_t *w; int iter = 0; for (;;) { RT6_TRACE("fixing tree: plen=%d iter=%d\n", fn->fn_bit, iter); iter++; BUG_TRAP(!(fn->fn_flags&RTN_RTINFO)); BUG_TRAP(!(fn->fn_flags&RTN_TL_ROOT)); BUG_TRAP(fn->leaf==NULL); children = 0; child = NULL; if (fn->right) child = fn->right, children |= 1; if (fn->left) child = fn->left, children |= 2; if (children == 3 || SUBTREE(fn) #ifdef CONFIG_IPV6_SUBTREES /* Subtree root (i.e. fn) may have one child */ || (children && fn->fn_flags&RTN_ROOT) #endif ) { fn->leaf = fib6_find_prefix(fn); #if RT6_DEBUG >= 2 if (fn->leaf==NULL) { BUG_TRAP(fn->leaf); fn->leaf = &ip6_null_entry; } #endif atomic_inc(&fn->leaf->rt6i_ref); return; } pn = fn->parent; #ifdef CONFIG_IPV6_SUBTREES if (SUBTREE(pn) == fn) { BUG_TRAP(fn->fn_flags&RTN_ROOT); SUBTREE(pn) = NULL; nstate = FWS_L; } else { BUG_TRAP(!(fn->fn_flags&RTN_ROOT)); #endif if (pn->right == fn) pn->right = child; else if (pn->left == fn) pn->left = child; #if RT6_DEBUG >= 2 else BUG_TRAP(0); #endif if (child) child->parent = pn; nstate = FWS_R; #ifdef CONFIG_IPV6_SUBTREES } #endif FOR_WALKERS(w) { if (child == NULL) { if (w->root == fn) { w->root = w->node = NULL; RT6_TRACE("W %p adjusted by delroot 1\n", w); } else if (w->node == fn) { RT6_TRACE("W %p adjusted by delnode 1, s=%d/%d\n", w, w->state, nstate); w->node = pn; w->state = nstate; } } else { if (w->root == fn) { w->root = child; RT6_TRACE("W %p adjusted by delroot 2\n", w); } if (w->node == fn) { w->node = child; if (children&2) { RT6_TRACE("W %p adjusted by delnode 2, s=%d\n", w, w->state); w->state = w->state>=FWS_R ? FWS_U : FWS_INIT; } else { RT6_TRACE("W %p adjusted by delnode 2, s=%d\n", w, w->state); w->state = w->state>=FWS_C ? FWS_U : FWS_INIT; } } } } node_free(fn); if (pn->fn_flags&RTN_RTINFO || SUBTREE(pn)) return; rt6_release(pn->leaf); pn->leaf = NULL; fn = pn; } }