return;
}
- int new_ok = rte_is_ok(new);
- int old_ok = rte_is_ok(old);
+ RT_UNLOCK(tab);
+ }
+
+ int used = 0;
+ int no_next = 0;
- struct channel_limit *l = &c->rx_limit;
- if (l->action && !old && new && !c->in_table)
+ /* Process the export */
+ for (uint i=0; i<RT_EXPORT_BULK; i++)
+ {
+ used += rte_export(c, c->rpe_next);
+
+ if (!c->rpe_next)
{
- u32 all_routes = stats->imp_routes + stats->filt_routes;
+ no_next = 1;
+ break;
+ }
+ }
- if (all_routes >= l->limit)
- channel_notify_limit(c, l, PLD_RX, all_routes);
+ if (used)
+ RT_LOCKED(tab, t)
+ if (no_next || t->cork_active)
+ rt_export_used(c->table, c->h.req->name, no_next ? "finished export bulk" : "cork active");
- if (l->state == PLS_BLOCKED)
- {
- /* In receive limit the situation is simple, old is NULL so
- we just free new and exit like nothing happened */
+ rt_send_export_event(&c->h);
+}
- stats->imp_updates_ignored++;
- rte_trace_in(D_FILTERS, c, new, "ignored [limit]");
- rte_free_quick(new);
- return;
- }
+
+static inline int
+rte_validate(struct channel *ch, rte *e)
+{
+ int c;
+ const net_addr *n = e->net;
+
+ if (!net_validate(n))
+ {
+ log(L_WARN "Ignoring bogus prefix %N received via %s",
+ n, ch->proto->name);
+ return 0;
+ }
+
+ /* FIXME: better handling different nettypes */
+ c = !net_is_flow(n) ?
+ net_classify(n): (IADDR_HOST | SCOPE_UNIVERSE);
+ if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK))
+ {
+ log(L_WARN "Ignoring bogus route %N received via %s",
+ n, ch->proto->name);
+ return 0;
+ }
+
+ if (net_type_match(n, NB_DEST))
+ {
+ eattr *nhea = ea_find(e->attrs, &ea_gen_nexthop);
+ int dest = nhea_dest(nhea);
+
+ if (dest == RTD_NONE)
+ {
+ log(L_WARN "Ignoring route %N with no destination received via %s",
+ n, ch->proto->name);
+ return 0;
}
- l = &c->in_limit;
- if (l->action && !old_ok && new_ok)
+ if ((dest == RTD_UNICAST) &&
+ !nexthop_is_sorted((struct nexthop_adata *) nhea->u.ptr))
{
- if (stats->imp_routes >= l->limit)
- channel_notify_limit(c, l, PLD_IN, stats->imp_routes);
+ log(L_WARN "Ignoring unsorted multipath route %N received via %s",
+ n, ch->proto->name);
+ return 0;
+ }
+ }
+ else if (ea_find(e->attrs, &ea_gen_nexthop))
+ {
+ log(L_WARN "Ignoring route %N having a nexthop attribute received via %s",
+ n, ch->proto->name);
+ return 0;
+ }
- if (l->state == PLS_BLOCKED)
- {
- /* In import limit the situation is more complicated. We
- shouldn't just drop the route, we should handle it like
- it was filtered. We also have to continue the route
- processing if old or new is non-NULL, but we should exit
- if both are NULL as this case is probably assumed to be
- already handled. */
+ return 1;
+}
- stats->imp_updates_ignored++;
- rte_trace_in(D_FILTERS, c, new, "ignored [limit]");
+int
+rte_same(const rte *x, const rte *y)
+{
+ /* rte.flags / rte.pflags are not checked, as they are internal to rtable */
+ return
- (
++ (x == y) || (
+ (x->attrs == y->attrs) ||
+ ((!(x->attrs->flags & EALF_CACHED) || !(y->attrs->flags & EALF_CACHED)) && ea_same(x->attrs, y->attrs))
+ ) &&
+ x->src == y->src &&
+ rte_is_filtered(x) == rte_is_filtered(y);
+}
- if (c->in_keep_filtered)
- new->flags |= REF_FILTERED;
- else
- { rte_free_quick(new); new = NULL; }
+static inline int rte_is_ok(const rte *e) { return e && !rte_is_filtered(e); }
- /* Note that old && !new could be possible when
- c->in_keep_filtered changed in the recent past. */
+static int
+rte_recalculate(struct rtable_private *table, struct rt_import_hook *c, net *net, rte *new, struct rte_src *src)
+{
+ struct rt_import_request *req = c->req;
+ struct rt_import_stats *stats = &c->stats;
+ struct rte_storage *old_best_stored = net->routes, *old_stored = NULL;
+ const rte *old_best = old_best_stored ? &old_best_stored->rte : NULL;
+ const rte *old = NULL;
+
+ /* If the new route is identical to the old one, we find the attributes in
+ * cache and clone these with no performance drop. OTOH, if we were to lookup
+ * the attributes, such a route definitely hasn't been anywhere yet,
+ * therefore it's definitely worth the time. */
+ struct rte_storage *new_stored = NULL;
+ if (new)
+ {
+ new_stored = rte_store(new, net, table);
+ new = RTES_WRITE(new_stored);
+ }
- if (!old && !new)
- return;
+ /* Find and remove original route from the same protocol */
+ struct rte_storage **before_old = rte_find(net, src);
- new_ok = 0;
- goto skip_stats1;
+ if (*before_old)
+ {
+ old = &(old_stored = (*before_old))->rte;
+
+ /* If there is the same route in the routing table but from
+ * a different sender, then there are two paths from the
+ * source protocol to this routing table through transparent
+ * pipes, which is not allowed.
+ * We log that and ignore the route. */
+ if (old->sender != c)
+ {
+ if (!old->generation && !new->generation)
+ bug("Two protocols claim to author a route with the same rte_src in table %s: %N %s/%u:%u",
+ c->table->name, net->n.addr, old->src->owner->name, old->src->private_id, old->src->global_id);
+
+ log_rl(&table->rl_pipe, L_ERR "Route source collision in table %s: %N %s/%u:%u",
+ c->table->name, net->n.addr, old->src->owner->name, old->src->private_id, old->src->global_id);
}
+
+ if (new && rte_same(old, &new_stored->rte))
+ {
+ /* No changes, ignore the new route and refresh the old one */
+ old_stored->stale_cycle = new->stale_cycle;
+
+ if (!rte_is_filtered(new))
+ {
+ stats->updates_ignored++;
+ rt_rte_trace_in(D_ROUTES, req, new, "ignored");
+ }
+
+ /* We need to free the already stored route here before returning */
+ rte_free(new_stored);
+ return 0;
+ }
+
+ *before_old = (*before_old)->next;
+ table->rt_count--;
}
+ if (!old && !new)
+ {
+ stats->withdraws_ignored++;
+ return 0;
+ }
+
+ /* If rejected by import limit, we need to pretend there is no route */
+ if (req->preimport && (req->preimport(req, new, old) == 0))
+ {
+ rte_free(new_stored);
+ new_stored = NULL;
+ new = NULL;
+ }
+
+ int new_ok = rte_is_ok(new);
+ int old_ok = rte_is_ok(old);
+
if (new_ok)
- stats->imp_updates_accepted++;
+ stats->updates_accepted++;
else if (old_ok)
- stats->imp_withdraws_accepted++;
+ stats->withdraws_accepted++;
else
- stats->imp_withdraws_ignored++;
+ stats->withdraws_ignored++;
if (old_ok || new_ok)
table->last_rt_change = current_time();