HASH_DEFINE_REHASH_FN(PXH, struct bgp_prefix)
void
-bgp_init_prefix_table(struct bgp_proto *p, u32 order)
+bgp_init_prefix_table(struct bgp_channel *c)
{
- HASH_INIT(p->prefix_hash, p->p.pool, order);
+ HASH_INIT(c->prefix_hash, c->pool, 8);
- p->prefix_slab = sl_new(p->p.pool, sizeof(struct bgp_prefix));
+ uint alen = net_addr_length[c->c.net_type];
+ c->prefix_slab = alen ? sl_new(c->pool, sizeof(struct bgp_prefix) + alen) : NULL;
}
-bgp_free_prefix_table(struct bgp_proto *p)
+ void
- HASH_FREE(p->prefix_hash);
++bgp_free_prefix_table(struct bgp_channel *c)
+ {
- rfree(p->prefix_slab);
- p->prefix_slab = NULL;
++ HASH_FREE(c->prefix_hash);
+
++ rfree(c->prefix_slab);
++ c->prefix_slab = NULL;
+ }
+
static struct bgp_prefix *
-bgp_get_prefix(struct bgp_proto *p, ip_addr prefix, int pxlen, u32 path_id)
+bgp_get_prefix(struct bgp_channel *c, net_addr *net, u32 path_id)
{
- struct bgp_prefix *bp = HASH_FIND(p->prefix_hash, PXH, prefix, pxlen, path_id);
+ u32 hash = net_hash(net) ^ u32_hash(path_id);
+ struct bgp_prefix *px = HASH_FIND(c->prefix_hash, PXH, net, path_id, hash);
+
+ if (px)
+ {
+ rem_node(&px->buck_node);
+ return px;
+ }
- if (bp)
- return bp;
+ if (c->prefix_slab)
+ px = sl_alloc(c->prefix_slab);
+ else
+ px = mb_alloc(c->pool, sizeof(struct bgp_prefix) + net->length);
- bp = sl_alloc(p->prefix_slab);
- bp->n.prefix = prefix;
- bp->n.pxlen = pxlen;
- bp->path_id = path_id;
- bp->bucket_node.next = NULL;
+ px->buck_node.next = NULL;
+ px->buck_node.prev = NULL;
+ px->hash = hash;
+ px->path_id = path_id;
+ net_copy(px->net, net);
- HASH_INSERT2(p->prefix_hash, PXH, p->p.pool, bp);
+ HASH_INSERT2(c->prefix_hash, PXH, c->pool, px);
- return bp;
+ return px;
}
void
}
-void
-bgp_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs)
+/*
+ * BGP protocol glue
+ */
+
+int
+bgp_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct linpool *pool UNUSED)
{
+ rte *e = *new;
+ struct proto *SRC = e->attrs->src->proto;
struct bgp_proto *p = (struct bgp_proto *) P;
- struct bgp_bucket *buck;
- struct bgp_prefix *px;
- rte *key;
- u32 path_id;
+ struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (struct bgp_proto *) SRC : NULL;
- DBG("BGP: Got route %I/%d %s\n", n->n.prefix, n->n.pxlen, new ? "up" : "down");
+ /* Reject our routes */
+ if (src == p)
+ return -1;
- if (new)
- {
- key = new;
- buck = bgp_get_bucket(p, n, attrs, new->attrs->source != RTS_BGP);
- if (!buck) /* Inconsistent attribute list */
- return;
- }
- else
- {
- key = old;
- if (!(buck = p->withdraw_bucket))
- {
- buck = p->withdraw_bucket = mb_alloc(P->pool, sizeof(struct bgp_bucket));
- init_list(&buck->prefixes);
- }
- }
- path_id = p->add_path_tx ? key->attrs->src->global_id : 0;
- px = bgp_get_prefix(p, n->n.prefix, n->n.pxlen, path_id);
- if (px->bucket_node.next)
- {
- DBG("\tRemoving old entry.\n");
- rem_node(&px->bucket_node);
- }
- add_tail(&buck->prefixes, &px->bucket_node);
- bgp_schedule_packet(p->conn, PKT_UPDATE);
-}
+ /* Accept non-BGP routes */
+ if (src == NULL)
+ return 0;
-static int
-bgp_create_attrs(struct bgp_proto *p, rte *e, ea_list **attrs, struct linpool *pool)
-{
- ea_list *ea = lp_alloc(pool, sizeof(ea_list) + 4*sizeof(eattr));
- rta *rta = e->attrs;
- byte *z;
+ // XXXX: Check next hop AF
- ea->next = *attrs;
- *attrs = ea;
- ea->flags = EALF_SORTED;
- ea->count = 4;
+ /* IBGP route reflection, RFC 4456 */
+ if (p->is_internal && src->is_internal && (p->local_as == src->local_as))
+ {
+ /* Rejected unless configured as route reflector */
+ if (!p->rr_client && !src->rr_client)
+ return -1;
+
+ /* Generally, this should be handled when path is received, but we check it
+ also here as rr_cluster_id may be undefined or different in src. */
+ if (p->rr_cluster_id && bgp_cluster_list_loopy(p, e->attrs->eattrs))
+ return -1;
+ }
- bgp_set_attr(ea->attrs, BA_ORIGIN,
- ((rta->source == RTS_OSPF_EXT1) || (rta->source == RTS_OSPF_EXT2)) ? ORIGIN_INCOMPLETE : ORIGIN_IGP);
+ /* Handle well-known communities, RFC 1997 */
+ struct eattr *c;
+ if (p->cf->interpret_communities &&
+ (c = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_COMMUNITY))))
+ {
+ struct adata *d = c->u.ptr;
- if (p->is_internal)
- bgp_set_attr_wa(ea->attrs+1, pool, BA_AS_PATH, 0);
- else
- {
- z = bgp_set_attr_wa(ea->attrs+1, pool, BA_AS_PATH, 6);
- z[0] = AS_PATH_SEQUENCE;
- z[1] = 1; /* 1 AS */
- put_u32(z+2, p->local_as);
- }
+ /* Do not export anywhere */
+ if (int_set_contains(d, BGP_COMM_NO_ADVERTISE))
+ return -1;
- /* iBGP -> use gw, eBGP multi-hop -> use source_addr,
- eBGP single-hop -> use gw if on the same iface */
- z = bgp_set_attr_wa(ea->attrs+2, pool, BA_NEXT_HOP, NEXT_HOP_LENGTH);
- if (p->cf->next_hop_self ||
- rta->dest != RTD_ROUTER ||
- ipa_equal(rta->gw, IPA_NONE) ||
- ipa_is_link_local(rta->gw) ||
- (!p->is_internal && !p->cf->next_hop_keep &&
- (!p->neigh || (rta->iface != p->neigh->iface))))
- set_next_hop(z, p->source_addr);
- else
- set_next_hop(z, rta->gw);
+ /* Do not export outside of AS (or member-AS) */
+ if (!p->is_internal && int_set_contains(d, BGP_COMM_NO_EXPORT_SUBCONFED))
+ return -1;
- bgp_set_attr(ea->attrs+3, BA_LOCAL_PREF, p->cf->default_local_pref);
+ /* Do not export outside of AS (or confederation) */
+ if (!p->is_interior && int_set_contains(d, BGP_COMM_NO_EXPORT))
+ return -1;
+ }
- return 0; /* Leave decision to the filters */
+ return 0;
}
-static inline int
-bgp_as_path_loopy(struct bgp_proto *p, rta *a)
-{
- int num = p->cf->allow_local_as + 1;
- eattr *e = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH));
- return (e && (num > 0) && as_path_contains(e->u.ptr, p->local_as, num));
-}
-
-static inline int
-bgp_originator_id_loopy(struct bgp_proto *p, rta *a)
-{
- eattr *e = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_ORIGINATOR_ID));
- return (e && (e->u.data == p->local_id));
-}
+
+static adata null_adata; /* adata of length 0 */
-static inline int
-bgp_cluster_list_loopy(struct bgp_proto *p, rta *a)
+static ea_list *
+bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *attrs0, struct linpool *pool)
{
- eattr *e = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_CLUSTER_LIST));
- return (e && p->rr_client && int_set_contains(e->u.ptr, p->rr_cluster_id));
-}
+ struct proto *SRC = e->attrs->src->proto;
+ struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (void *) SRC : NULL;
+ struct bgp_export_state s = { .proto = p, .channel =c, .pool = pool, .src = src, .route = e };
+ ea_list *attrs = attrs0;
+ eattr *a;
+ adata *ad;
+ /* ORIGIN attribute - mandatory, attach if missing */
+ if (! bgp_find_attr(attrs0, BA_ORIGIN))
+ bgp_set_attr_u32(&attrs, pool, BA_ORIGIN, 0, src ? ORIGIN_INCOMPLETE : ORIGIN_IGP);
-static inline void
-bgp_path_prepend(rte *e, ea_list **attrs, struct linpool *pool, u32 as)
-{
- eattr *a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH));
- bgp_attach_attr(attrs, pool, BA_AS_PATH, (uintptr_t) as_path_prepend(pool, a->u.ptr, as));
-}
+ /* AS_PATH attribute - mandatory */
+ a = bgp_find_attr(attrs0, BA_AS_PATH);
+ ad = a ? a->u.ptr : &null_adata;
-static inline void
-bgp_cluster_list_prepend(rte *e, ea_list **attrs, struct linpool *pool, u32 cid)
-{
- eattr *a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_CLUSTER_LIST));
- bgp_attach_attr(attrs, pool, BA_CLUSTER_LIST, (uintptr_t) int_set_prepend(pool, a ? a->u.ptr : NULL, cid));
-}
+ /* AS_PATH attribute - strip AS_CONFED* segments outside confederation */
+ if ((!p->cf->confederation || !p->is_interior) && as_path_contains_confed(ad))
+ ad = as_path_strip_confed(pool, ad);
-static int
-bgp_update_attrs(struct bgp_proto *p, rte *e, ea_list **attrs, struct linpool *pool, int rr)
-{
- eattr *a;
+ /* AS_PATH attribute - keep or prepend ASN */
+ if (p->is_internal ||
+ (p->rs_client && src && src->rs_client))
+ {
+ /* IBGP or route server -> just ensure there is one */
+ if (!a)
+ bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, &null_adata);
+ }
+ else if (p->is_interior)
+ {
+ /* Confederation -> prepend ASN as AS_CONFED_SEQUENCE */
+ ad = as_path_prepend2(pool, ad, AS_PATH_CONFED_SEQUENCE, p->public_as);
+ bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, ad);
+ }
+ else /* Regular EBGP (no RS, no confederation) */
+ {
+ /* Regular EBGP -> prepend ASN as regular sequence */
+ ad = as_path_prepend2(pool, ad, AS_PATH_SEQUENCE, p->public_as);
+ bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, ad);
+
+ /* MULTI_EXIT_DESC attribute - accept only if set in export filter */
+ a = bgp_find_attr(attrs0, BA_MULTI_EXIT_DISC);
+ if (a && !(a->type & EAF_FRESH))
+ bgp_unset_attr(&attrs, pool, BA_MULTI_EXIT_DISC);
+ }
- if (!p->is_internal && !p->rs_client)
- {
- bgp_path_prepend(e, attrs, pool, p->local_as);
-
- /* The MULTI_EXIT_DISC attribute received from a neighboring AS MUST NOT be
- * propagated to other neighboring ASes.
- * Perhaps it would be better to undefine it.
- */
- a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC));
- if (a)
- bgp_attach_attr(attrs, pool, BA_MULTI_EXIT_DISC, 0);
- }
+ /* NEXT_HOP attribute - delegated to AF-specific hook */
+ a = bgp_find_attr(attrs0, BA_NEXT_HOP);
+ bgp_update_next_hop(&s, a, &attrs);
- /* iBGP -> keep next_hop, eBGP multi-hop -> use source_addr,
- * eBGP single-hop -> keep next_hop if on the same iface.
- * If the next_hop is zero (i.e. link-local), keep only if on the same iface.
- *
- * Note that same-iface-check uses iface from route, which is based on gw.
- */
- a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_NEXT_HOP));
- if (a && !p->cf->next_hop_self &&
- (p->cf->next_hop_keep ||
- (p->is_internal && ipa_nonzero(*((ip_addr *) a->u.ptr->data))) ||
- (p->neigh && (e->attrs->iface == p->neigh->iface))))
- {
- /* Leave the original next hop attribute, will check later where does it point */
- }
- else
- {
- /* Need to create new one */
- byte *b = bgp_attach_attr_wa(attrs, pool, BA_NEXT_HOP, NEXT_HOP_LENGTH);
- set_next_hop(b, p->source_addr);
- }
+ /* LOCAL_PREF attribute - required for IBGP, attach if missing */
+ if (p->is_interior && ! bgp_find_attr(attrs0, BA_LOCAL_PREF))
+ bgp_set_attr_u32(&attrs, pool, BA_LOCAL_PREF, 0, p->cf->default_local_pref);
- if (rr)
- {
- /* Handling route reflection, RFC 4456 */
- struct bgp_proto *src = (struct bgp_proto *) e->attrs->src->proto;
+ /* IBGP route reflection, RFC 4456 */
+ if (src && src->is_internal && p->is_internal && (src->local_as == p->local_as))
+ {
+ /* ORIGINATOR_ID attribute - attach if not already set */
+ if (! bgp_find_attr(attrs0, BA_ORIGINATOR_ID))
+ bgp_set_attr_u32(&attrs, pool, BA_ORIGINATOR_ID, 0, src->remote_id);
- a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGINATOR_ID));
- if (!a)
- bgp_attach_attr(attrs, pool, BA_ORIGINATOR_ID, src->remote_id);
+ /* CLUSTER_LIST attribute - prepend cluster ID */
+ a = bgp_find_attr(attrs0, BA_CLUSTER_LIST);
+ ad = a ? a->u.ptr : NULL;
- /* We attach proper cluster ID according to whether the route is entering or leaving the cluster */
- bgp_cluster_list_prepend(e, attrs, pool, src->rr_client ? src->rr_cluster_id : p->rr_cluster_id);
+ /* Prepend src cluster ID */
+ if (src->rr_cluster_id)
- ad = int_set_add(pool, ad, src->rr_cluster_id);
++ ad = int_set_prepend(pool, ad, src->rr_cluster_id);
- /* Two RR clients with different cluster ID, hmmm */
- if (src->rr_client && p->rr_client && (src->rr_cluster_id != p->rr_cluster_id))
- bgp_cluster_list_prepend(e, attrs, pool, p->rr_cluster_id);
- }
+ /* Prepend dst cluster ID if src and dst clusters are different */
+ if (p->rr_cluster_id && (src->rr_cluster_id != p->rr_cluster_id))
- ad = int_set_add(pool, ad, p->rr_cluster_id);
++ ad = int_set_prepend(pool, ad, p->rr_cluster_id);
- return 0; /* Leave decision to the filters */
-}
+ /* Should be at least one prepended cluster ID */
+ bgp_set_attr_ptr(&attrs, pool, BA_CLUSTER_LIST, 0, ad);
+ }
-static int
-bgp_community_filter(struct bgp_proto *p, rte *e)
-{
- eattr *a;
- struct adata *d;
+ /* AS4_* transition attributes, RFC 6793 4.2.2 */
+ if (! p->as4_session)
+ {
+ a = bgp_find_attr(attrs, BA_AS_PATH);
+ if (a && as_path_contains_as4(a->u.ptr))
+ {
+ bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, as_path_to_old(pool, a->u.ptr));
+ bgp_set_attr_ptr(&attrs, pool, BA_AS4_PATH, 0, as_path_strip_confed(pool, a->u.ptr));
+ }
- /* Check if we aren't forbidden to export the route by communities */
- a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_COMMUNITY));
- if (a)
+ a = bgp_find_attr(attrs, BA_AGGREGATOR);
+ if (a && aggregator_contains_as4(a->u.ptr))
{
- d = a->u.ptr;
- if (int_set_contains(d, BGP_COMM_NO_ADVERTISE))
- {
- DBG("\tNO_ADVERTISE\n");
- return 1;
- }
- if (!p->is_internal &&
- (int_set_contains(d, BGP_COMM_NO_EXPORT) ||
- int_set_contains(d, BGP_COMM_NO_EXPORT_SUBCONFED)))
- {
- DBG("\tNO_EXPORT\n");
- return 1;
- }
+ bgp_set_attr_ptr(&attrs, pool, BA_AGGREGATOR, 0, aggregator_to_old(pool, a->u.ptr));
+ bgp_set_attr_ptr(&attrs, pool, BA_AS4_AGGREGATOR, 0, a->u.ptr);
}
+ }
- return 0;
+ /*
+ * Presence of mandatory attributes ORIGIN and AS_PATH is ensured by above
+ * conditions. Presence and validity of quasi-mandatory NEXT_HOP attribute
+ * should be checked in AF-specific hooks.
+ */
+
+ /* Apply per-attribute export hooks for validatation and normalization */
+ return bgp_export_attrs(&s, attrs);
}
-int
-bgp_import_control(struct proto *P, rte **new, ea_list **attrs, struct linpool *pool)
+void
+bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old, ea_list *attrs)
{
- rte *e = *new;
- struct bgp_proto *p = (struct bgp_proto *) P;
- struct bgp_proto *new_bgp = (e->attrs->src->proto->proto == &proto_bgp) ?
- (struct bgp_proto *) e->attrs->src->proto : NULL;
+ struct bgp_proto *p = (void *) P;
+ struct bgp_channel *c = (void *) C;
+ struct bgp_bucket *buck;
+ struct bgp_prefix *px;
+ u32 path;
- if (p == new_bgp) /* Poison reverse updates */
- return -1;
- if (new_bgp)
- {
- /* We should check here for cluster list loop, because the receiving BGP instance
- might have different cluster ID */
- if (bgp_cluster_list_loopy(p, e->attrs))
- return -1;
-
- if (p->cf->interpret_communities && bgp_community_filter(p, e))
- return -1;
-
- if (p->local_as == new_bgp->local_as && p->is_internal && new_bgp->is_internal)
- {
- /* Redistribution of internal routes with IBGP */
- if (p->rr_client || new_bgp->rr_client)
- /* Route reflection, RFC 4456 */
- return bgp_update_attrs(p, e, attrs, pool, 1);
- else
- return -1;
- }
- else
- return bgp_update_attrs(p, e, attrs, pool, 0);
- }
+ if (new)
+ {
+ attrs = bgp_update_attrs(p, c, new, attrs, bgp_linpool);
+
+ /* If attributes are invalid, we fail back to withdraw */
+ buck = attrs ? bgp_get_bucket(c, attrs) : bgp_get_withdraw_bucket(c);
+ path = new->attrs->src->global_id;
+
+ lp_flush(bgp_linpool);
+ }
else
- return bgp_create_attrs(p, e, attrs, pool);
+ {
+ buck = bgp_get_withdraw_bucket(c);
+ path = old->attrs->src->global_id;
+ }
+
+ px = bgp_get_prefix(c, n->n.addr, c->add_path_tx ? path : 0);
+ add_tail(&buck->prefixes, &px->buck_node);
+
+ bgp_schedule_packet(p->conn, c, PKT_UPDATE);
}
+
static inline u32
bgp_get_neighbor(rte *r)
{