Fixes some problems in BGP error handling.
This commit is contained in:
parent
83696b3913
commit
06fb60c4af
2 changed files with 156 additions and 84 deletions
|
@ -22,6 +22,42 @@
|
||||||
|
|
||||||
#include "bgp.h"
|
#include "bgp.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UPDATE message error handling
|
||||||
|
*
|
||||||
|
* All checks from RFC 4271 6.3 are done as specified with these exceptions:
|
||||||
|
* - The semantic check of an IP address from NEXT_HOP attribute is missing.
|
||||||
|
* - Checks of some optional attribute values are missing.
|
||||||
|
* - Syntactic and semantic checks of NLRIs (done in DECODE_PREFIX())
|
||||||
|
* are probably inadequate.
|
||||||
|
*
|
||||||
|
* Loop detection based on AS_PATH causes updates to be withdrawn. RFC
|
||||||
|
* 4271 does not explicitly specifiy the behavior in that case.
|
||||||
|
*
|
||||||
|
* Loop detection related to route reflection (based on ORIGINATOR_ID
|
||||||
|
* and CLUSTER_LIST) causes updates to be withdrawn. RFC 4456 8
|
||||||
|
* specifies that such updates should be ignored, but that is generally
|
||||||
|
* a bad idea.
|
||||||
|
*
|
||||||
|
* Error checking of optional transitive attributes is done according to
|
||||||
|
* draft-ietf-idr-optional-transitive-03, but errors are handled always
|
||||||
|
* as withdraws.
|
||||||
|
*
|
||||||
|
* Unexpected AS_CONFED_* segments in AS_PATH are logged and removed,
|
||||||
|
* but unknown segments cause a session drop with Malformed AS_PATH
|
||||||
|
* error (see validate_path()). The behavior in such case is not
|
||||||
|
* explicitly specified by RFC 4271. RFC 5065 specifies that
|
||||||
|
* inconsistent AS_CONFED_* segments should cause a session drop, but
|
||||||
|
* implementations that pass invalid AS_CONFED_* segments are
|
||||||
|
* widespread.
|
||||||
|
*
|
||||||
|
* Error handling of AS4_* attributes is done as specified by
|
||||||
|
* draft-ietf-idr-rfc4893bis-03. There are several possible
|
||||||
|
* inconsistencies between AGGREGATOR and AS4_AGGREGATOR that are not
|
||||||
|
* handled by that draft, these are logged and ignored (see
|
||||||
|
* bgp_reconstruct_4b_attrs()).
|
||||||
|
*/
|
||||||
|
|
||||||
static byte bgp_mandatory_attrs[] = { BA_ORIGIN, BA_AS_PATH
|
static byte bgp_mandatory_attrs[] = { BA_ORIGIN, BA_AS_PATH
|
||||||
#ifndef IPV6
|
#ifndef IPV6
|
||||||
,BA_NEXT_HOP
|
,BA_NEXT_HOP
|
||||||
|
@ -38,6 +74,9 @@ struct attr_desc {
|
||||||
void (*format)(eattr *ea, byte *buf, int buflen);
|
void (*format)(eattr *ea, byte *buf, int buflen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define IGNORE -1
|
||||||
|
#define WITHDRAW -2
|
||||||
|
|
||||||
static int
|
static int
|
||||||
bgp_check_origin(struct bgp_proto *p UNUSED, byte *a, int len UNUSED)
|
bgp_check_origin(struct bgp_proto *p UNUSED, byte *a, int len UNUSED)
|
||||||
{
|
{
|
||||||
|
@ -152,7 +191,7 @@ static int
|
||||||
bgp_check_next_hop(struct bgp_proto *p UNUSED, byte *a, int len)
|
bgp_check_next_hop(struct bgp_proto *p UNUSED, byte *a, int len)
|
||||||
{
|
{
|
||||||
#ifdef IPV6
|
#ifdef IPV6
|
||||||
return -1;
|
return IGNORE;
|
||||||
#else
|
#else
|
||||||
ip_addr addr;
|
ip_addr addr;
|
||||||
|
|
||||||
|
@ -186,7 +225,7 @@ bgp_check_aggregator(struct bgp_proto *p, byte *a UNUSED, int len)
|
||||||
{
|
{
|
||||||
int exp_len = p->as4_session ? 8 : 6;
|
int exp_len = p->as4_session ? 8 : 6;
|
||||||
|
|
||||||
return (len == exp_len) ? 0 : 5;
|
return (len == exp_len) ? 0 : WITHDRAW;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -202,6 +241,13 @@ bgp_format_aggregator(eattr *a, byte *buf, int buflen UNUSED)
|
||||||
bsprintf(buf, "%d.%d.%d.%d AS%d", data[0], data[1], data[2], data[3], as);
|
bsprintf(buf, "%d.%d.%d.%d AS%d", data[0], data[1], data[2], data[3], as);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bgp_check_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len)
|
||||||
|
{
|
||||||
|
return ((len % 4) == 0) ? 0 : WITHDRAW;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
bgp_check_cluster_list(struct bgp_proto *p UNUSED, byte *a UNUSED, int len)
|
bgp_check_cluster_list(struct bgp_proto *p UNUSED, byte *a UNUSED, int len)
|
||||||
{
|
{
|
||||||
|
@ -221,7 +267,7 @@ bgp_check_reach_nlri(struct bgp_proto *p UNUSED, byte *a UNUSED, int len UNUSED)
|
||||||
p->mp_reach_start = a;
|
p->mp_reach_start = a;
|
||||||
p->mp_reach_len = len;
|
p->mp_reach_len = len;
|
||||||
#endif
|
#endif
|
||||||
return -1;
|
return IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -231,7 +277,7 @@ bgp_check_unreach_nlri(struct bgp_proto *p UNUSED, byte *a UNUSED, int len UNUSE
|
||||||
p->mp_unreach_start = a;
|
p->mp_unreach_start = a;
|
||||||
p->mp_unreach_len = len;
|
p->mp_unreach_len = len;
|
||||||
#endif
|
#endif
|
||||||
return -1;
|
return IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct attr_desc bgp_attr_table[] = {
|
static struct attr_desc bgp_attr_table[] = {
|
||||||
|
@ -252,7 +298,7 @@ static struct attr_desc bgp_attr_table[] = {
|
||||||
{ "aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AGGREGATOR */
|
{ "aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AGGREGATOR */
|
||||||
bgp_check_aggregator, bgp_format_aggregator },
|
bgp_check_aggregator, bgp_format_aggregator },
|
||||||
{ "community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_INT_SET, 1, /* BA_COMMUNITY */
|
{ "community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_INT_SET, 1, /* BA_COMMUNITY */
|
||||||
NULL, NULL },
|
bgp_check_community, NULL },
|
||||||
{ "originator_id", 4, BAF_OPTIONAL, EAF_TYPE_ROUTER_ID, 0, /* BA_ORIGINATOR_ID */
|
{ "originator_id", 4, BAF_OPTIONAL, EAF_TYPE_ROUTER_ID, 0, /* BA_ORIGINATOR_ID */
|
||||||
NULL, NULL },
|
NULL, NULL },
|
||||||
{ "cluster_list", -1, BAF_OPTIONAL, EAF_TYPE_INT_SET, 0, /* BA_CLUSTER_LIST */
|
{ "cluster_list", -1, BAF_OPTIONAL, EAF_TYPE_INT_SET, 0, /* BA_CLUSTER_LIST */
|
||||||
|
@ -1163,15 +1209,7 @@ bgp_merge_as_paths(struct adata *old2, struct adata *old4, int req_as, struct li
|
||||||
static int
|
static int
|
||||||
as4_aggregator_valid(struct adata *aggr)
|
as4_aggregator_valid(struct adata *aggr)
|
||||||
{
|
{
|
||||||
if (aggr->length != 8)
|
return aggr->length == 8;
|
||||||
return 0;
|
|
||||||
|
|
||||||
u32 *a = (u32 *) aggr->data;
|
|
||||||
|
|
||||||
if ((a[0] == 0) || (a[1] == 0))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1258,7 +1296,7 @@ bgp_remove_as4_attrs(struct bgp_proto *p, rta *a)
|
||||||
{
|
{
|
||||||
*el = (*el)->next;
|
*el = (*el)->next;
|
||||||
if (p->as4_session)
|
if (p->as4_session)
|
||||||
log(L_WARN "BGP: Unexpected AS4_* attributes received");
|
log(L_WARN "%s: Unexpected AS4_* attributes received", p->p.name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
el = &((*el)->next);
|
el = &((*el)->next);
|
||||||
|
@ -1288,6 +1326,7 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin
|
||||||
byte seen[256/8];
|
byte seen[256/8];
|
||||||
ea_list *ea;
|
ea_list *ea;
|
||||||
struct adata *ad;
|
struct adata *ad;
|
||||||
|
int withdraw = 0;
|
||||||
|
|
||||||
bzero(a, sizeof(rta));
|
bzero(a, sizeof(rta));
|
||||||
a->proto = &bgp->p;
|
a->proto = &bgp->p;
|
||||||
|
@ -1345,8 +1384,14 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin
|
||||||
errcode = desc->validate(bgp, z, l);
|
errcode = desc->validate(bgp, z, l);
|
||||||
if (errcode > 0)
|
if (errcode > 0)
|
||||||
goto err;
|
goto err;
|
||||||
if (errcode < 0)
|
if (errcode == IGNORE)
|
||||||
continue;
|
continue;
|
||||||
|
if (errcode <= WITHDRAW)
|
||||||
|
{
|
||||||
|
log(L_WARN "%s: Attribute %s is malformed, withdrawing update",
|
||||||
|
bgp->p.name, desc->name);
|
||||||
|
withdraw = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (code == BA_AS_PATH)
|
else if (code == BA_AS_PATH)
|
||||||
{
|
{
|
||||||
|
@ -1407,6 +1452,9 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (withdraw)
|
||||||
|
goto withdraw;
|
||||||
|
|
||||||
#ifdef IPV6
|
#ifdef IPV6
|
||||||
/* If we received MP_REACH_NLRI we should check mandatory attributes */
|
/* If we received MP_REACH_NLRI we should check mandatory attributes */
|
||||||
if (bgp->mp_reach_len != 0)
|
if (bgp->mp_reach_len != 0)
|
||||||
|
@ -1438,12 +1486,12 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin
|
||||||
|
|
||||||
/* If the AS path attribute contains our AS, reject the routes */
|
/* If the AS path attribute contains our AS, reject the routes */
|
||||||
if (bgp_as_path_loopy(bgp, a))
|
if (bgp_as_path_loopy(bgp, a))
|
||||||
goto loop;
|
goto withdraw;
|
||||||
|
|
||||||
/* Two checks for IBGP loops caused by route reflection, RFC 4456 */
|
/* Two checks for IBGP loops caused by route reflection, RFC 4456 */
|
||||||
if (bgp_originator_id_loopy(bgp, a) ||
|
if (bgp_originator_id_loopy(bgp, a) ||
|
||||||
bgp_cluster_list_loopy(bgp, a))
|
bgp_cluster_list_loopy(bgp, a))
|
||||||
goto loop;
|
goto withdraw;
|
||||||
|
|
||||||
/* If there's no local preference, define one */
|
/* If there's no local preference, define one */
|
||||||
if (!(seen[0] & (1 << BA_LOCAL_PREF)))
|
if (!(seen[0] & (1 << BA_LOCAL_PREF)))
|
||||||
|
@ -1451,8 +1499,7 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
|
|
||||||
loop:
|
withdraw:
|
||||||
DBG("BGP: Path loop!\n");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
malformed:
|
malformed:
|
||||||
|
|
|
@ -790,9 +790,9 @@ bgp_rx_open(struct bgp_conn *conn, byte *pkt, int len)
|
||||||
int b = *pp++; \
|
int b = *pp++; \
|
||||||
int q; \
|
int q; \
|
||||||
ll--; \
|
ll--; \
|
||||||
if (b > BITS_PER_IP_ADDRESS) { err=10; goto bad; } \
|
if (b > BITS_PER_IP_ADDRESS) { err=10; goto done; } \
|
||||||
q = (b+7) / 8; \
|
q = (b+7) / 8; \
|
||||||
if (ll < q) { err=1; goto bad; } \
|
if (ll < q) { err=1; goto done; } \
|
||||||
memcpy(&prefix, pp, q); \
|
memcpy(&prefix, pp, q); \
|
||||||
pp += q; \
|
pp += q; \
|
||||||
ll -= q; \
|
ll -= q; \
|
||||||
|
@ -840,11 +840,10 @@ bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
byte *attrs, int attr_len)
|
byte *attrs, int attr_len)
|
||||||
{
|
{
|
||||||
struct bgp_proto *p = conn->bgp;
|
struct bgp_proto *p = conn->bgp;
|
||||||
rta *a0;
|
|
||||||
rta *a = NULL;
|
|
||||||
ip_addr prefix;
|
|
||||||
net *n;
|
net *n;
|
||||||
int err = 0, pxlen;
|
rta *a0, *a = NULL;
|
||||||
|
ip_addr prefix;
|
||||||
|
int pxlen, err = 0;
|
||||||
|
|
||||||
/* Withdraw routes */
|
/* Withdraw routes */
|
||||||
while (withdrawn_len)
|
while (withdrawn_len)
|
||||||
|
@ -859,32 +858,43 @@ bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
return;
|
return;
|
||||||
|
|
||||||
a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, nlri_len);
|
a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, nlri_len);
|
||||||
if (a0 && nlri_len && bgp_set_next_hop(p, a0))
|
|
||||||
{
|
|
||||||
a = rta_lookup(a0);
|
|
||||||
while (nlri_len)
|
|
||||||
{
|
|
||||||
rte *e;
|
|
||||||
DECODE_PREFIX(nlri, nlri_len);
|
|
||||||
DBG("Add %I/%d\n", prefix, pxlen);
|
|
||||||
e = rte_get_temp(rta_clone(a));
|
|
||||||
n = net_get(p->p.table, prefix, pxlen);
|
|
||||||
e->net = n;
|
|
||||||
e->pflags = 0;
|
|
||||||
rte_update(p->p.table, n, &p->p, &p->p, e);
|
|
||||||
if (bgp_apply_limits(p) < 0)
|
|
||||||
goto bad2;
|
|
||||||
}
|
|
||||||
rta_free(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (conn->state != BS_ESTABLISHED) /* fatal error during decoding */
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bad:
|
if (a0 && bgp_set_next_hop(p, a0))
|
||||||
bgp_error(conn, 3, err, NULL, 0);
|
a = rta_lookup(a0);
|
||||||
bad2:
|
|
||||||
|
while (nlri_len)
|
||||||
|
{
|
||||||
|
DECODE_PREFIX(nlri, nlri_len);
|
||||||
|
DBG("Add %I/%d\n", prefix, pxlen);
|
||||||
|
|
||||||
|
if (a)
|
||||||
|
{
|
||||||
|
rte *e = rte_get_temp(rta_clone(a));
|
||||||
|
e->net = net_get(p->p.table, prefix, pxlen);
|
||||||
|
e->pflags = 0;
|
||||||
|
rte_update(p->p.table, e->net, &p->p, &p->p, e);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Forced withdraw as a result of soft error */
|
||||||
|
if (n = net_find(p->p.table, prefix, pxlen))
|
||||||
|
rte_update(p->p.table, n, &p->p, &p->p, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bgp_apply_limits(p) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
if (a)
|
if (a)
|
||||||
rta_free(a);
|
rta_free(a);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
bgp_error(conn, 3, err, NULL, 0);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,7 +905,7 @@ bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
len = len0 = p->name##_len; \
|
len = len0 = p->name##_len; \
|
||||||
if (len) \
|
if (len) \
|
||||||
{ \
|
{ \
|
||||||
if (len < 3) goto bad; \
|
if (len < 3) { err=9; goto done; } \
|
||||||
af = get_u16(x); \
|
af = get_u16(x); \
|
||||||
sub = x[2]; \
|
sub = x[2]; \
|
||||||
x += 3; \
|
x += 3; \
|
||||||
|
@ -906,6 +916,24 @@ bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
af = 0; \
|
af = 0; \
|
||||||
if (af == BGP_AF_IPV6)
|
if (af == BGP_AF_IPV6)
|
||||||
|
|
||||||
|
static void
|
||||||
|
bgp_attach_next_hop(rta *a0, byte *x)
|
||||||
|
{
|
||||||
|
ip_addr *nh = (ip_addr *) bgp_attach_attr_wa(&a0->eattrs, bgp_linpool, BA_NEXT_HOP, NEXT_HOP_LENGTH);
|
||||||
|
memcpy(nh, x+1, 16);
|
||||||
|
ipa_ntoh(nh[0]);
|
||||||
|
|
||||||
|
/* We store received link local address in the other part of BA_NEXT_HOP eattr. */
|
||||||
|
if (*x == 32)
|
||||||
|
{
|
||||||
|
memcpy(nh+1, x+17, 16);
|
||||||
|
ipa_ntoh(nh[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
nh[1] = IPA_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
bgp_do_rx_update(struct bgp_conn *conn,
|
bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
byte *withdrawn, int withdrawn_len,
|
byte *withdrawn, int withdrawn_len,
|
||||||
|
@ -916,16 +944,16 @@ bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
byte *start, *x;
|
byte *start, *x;
|
||||||
int len, len0;
|
int len, len0;
|
||||||
unsigned af, sub;
|
unsigned af, sub;
|
||||||
rta *a0;
|
|
||||||
rta *a = NULL;
|
|
||||||
ip_addr prefix;
|
|
||||||
net *n;
|
net *n;
|
||||||
int err = 0, pxlen;
|
rta *a0, *a = NULL;
|
||||||
|
ip_addr prefix;
|
||||||
|
int pxlen, err = 0;
|
||||||
|
|
||||||
p->mp_reach_len = 0;
|
p->mp_reach_len = 0;
|
||||||
p->mp_unreach_len = 0;
|
p->mp_unreach_len = 0;
|
||||||
a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, 0);
|
a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, 0);
|
||||||
if (!a0)
|
|
||||||
|
if (conn->state != BS_ESTABLISHED) /* fatal error during decoding */
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DO_NLRI(mp_unreach)
|
DO_NLRI(mp_unreach)
|
||||||
|
@ -943,52 +971,49 @@ bgp_do_rx_update(struct bgp_conn *conn,
|
||||||
{
|
{
|
||||||
/* Create fake NEXT_HOP attribute */
|
/* Create fake NEXT_HOP attribute */
|
||||||
if (len < 1 || (*x != 16 && *x != 32) || len < *x + 2)
|
if (len < 1 || (*x != 16 && *x != 32) || len < *x + 2)
|
||||||
goto bad;
|
{ err = 9; goto done; }
|
||||||
|
|
||||||
ip_addr *nh = (ip_addr *) bgp_attach_attr_wa(&a0->eattrs, bgp_linpool, BA_NEXT_HOP, NEXT_HOP_LENGTH);
|
if (a0)
|
||||||
memcpy(nh, x+1, 16);
|
bgp_attach_next_hop(a0, x);
|
||||||
ipa_ntoh(nh[0]);
|
|
||||||
|
|
||||||
/* We store received link local address in the other part of BA_NEXT_HOP eattr. */
|
|
||||||
if (*x == 32)
|
|
||||||
{
|
|
||||||
memcpy(nh+1, x+17, 16);
|
|
||||||
ipa_ntoh(nh[1]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
nh[1] = IPA_NONE;
|
|
||||||
|
|
||||||
/* Also ignore one reserved byte */
|
/* Also ignore one reserved byte */
|
||||||
len -= *x + 2;
|
len -= *x + 2;
|
||||||
x += *x + 2;
|
x += *x + 2;
|
||||||
|
|
||||||
if (bgp_set_next_hop(p, a0))
|
if (a0 && bgp_set_next_hop(p, a0))
|
||||||
{
|
|
||||||
a = rta_lookup(a0);
|
a = rta_lookup(a0);
|
||||||
|
|
||||||
while (len)
|
while (len)
|
||||||
{
|
{
|
||||||
rte *e;
|
|
||||||
DECODE_PREFIX(x, len);
|
DECODE_PREFIX(x, len);
|
||||||
DBG("Add %I/%d\n", prefix, pxlen);
|
DBG("Add %I/%d\n", prefix, pxlen);
|
||||||
e = rte_get_temp(rta_clone(a));
|
|
||||||
n = net_get(p->p.table, prefix, pxlen);
|
if (a)
|
||||||
e->net = n;
|
{
|
||||||
|
rte *e = rte_get_temp(rta_clone(a));
|
||||||
|
e->net = net_get(p->p.table, prefix, pxlen);
|
||||||
e->pflags = 0;
|
e->pflags = 0;
|
||||||
rte_update(p->p.table, n, &p->p, &p->p, e);
|
rte_update(p->p.table, e->net, &p->p, &p->p, e);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Forced withdraw as a result of soft error */
|
||||||
|
if (n = net_find(p->p.table, prefix, pxlen))
|
||||||
|
rte_update(p->p.table, n, &p->p, &p->p, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
if (bgp_apply_limits(p) < 0)
|
if (bgp_apply_limits(p) < 0)
|
||||||
goto bad2;
|
goto done;
|
||||||
}
|
|
||||||
rta_free(a);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
done:
|
||||||
|
|
||||||
bad:
|
|
||||||
bgp_error(conn, 3, 9, start, len0);
|
|
||||||
bad2:
|
|
||||||
if (a)
|
if (a)
|
||||||
rta_free(a);
|
rta_free(a);
|
||||||
|
|
||||||
|
if (err) /* Use subcode 9, not err */
|
||||||
|
bgp_error(conn, 3, 9, NULL, 0);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue