0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 09:41:54 +00:00

TCP-AO implementation based on RFC 5925, used Linux TCP-AO. Tested against JUNIPER.

This commit is contained in:
Kateřina Kubecová 2024-06-26 09:23:12 +02:00 committed by Katerina Kubecova
parent adfff44878
commit e8696ac484
9 changed files with 826 additions and 8 deletions

View File

@ -2912,6 +2912,38 @@ using the following configuration parameters:
set manually by an external utility on NetBSD and OpenBSD. Default: set manually by an external utility on NetBSD and OpenBSD. Default:
enabled (ignored on non-FreeBSD). enabled (ignored on non-FreeBSD).
<tag><label id="bgp-tcp-ao">authentication keyed tcp AO</tag>
This authentication is similar to md5, but enables changing keys on living connection.
Key change is done via reconfiguring.
Key configuration of one key consists of two ids - one for local and one for remote machine.
The ids may, but does not have to be the same and must be in range 0 - 255. Among keys
on one protocol the local ids must be unique and the remote ids must be unique.
Used cryphtographic algorithm must be specified for each key.
Possible ciphers are "cmac(aes128)", "hmac(md5)" "hmac(sha1)", "hmac(sha224)",
"hmac(sha256)", "hmac(sha384)" and "hmac(sha512)". And, of course, there must
be specified a string password.
One key must be marked as "required". This key will be send as rnext key.
If the other site knows the required key, it uses the key for next packet.
In order to delete a currently used key (key which is required by the other site),
it is possible to mark the key as "deprecated". This key will be deleted first time
the other site requires another key.
Deleting a currently used key in config causes restart of the protocol.
The problem of directly deleting current key is, that we could treat such deleted key as deprecated,
but only until the protocol restarts. If it restarts, key is lost.
For example, connection is established. Then one side decides to remove current key and requires a newly added key.
The other side does not know the new key yet. For now, this is not problem, they still use the old key.
But, at this moment, some unexpected error occures at the first site.
It restarts, but it does not have the old key in config. One site does not have old key,
the other new key and trying other keys than required ones is not supported.
Editing existing keys (except of marking them "required" or "deprecated")
is not recommended and leads to restarting protocol.
<tag><label id="bgp-passive">passive <m/switch/</tag> <tag><label id="bgp-passive">passive <m/switch/</tag>
Standard BGP behavior is both initiating outgoing connections and Standard BGP behavior is both initiating outgoing connections and
accepting incoming connections. In passive mode, outgoing connections accepting incoming connections. In passive mode, outgoing connections
@ -3657,6 +3689,20 @@ protocol bgp {
local 198.51.100.14 as 65000; # Use a private AS number local 198.51.100.14 as 65000; # Use a private AS number
neighbor 198.51.100.130 as 64496; # Our neighbor ... neighbor 198.51.100.130 as 64496; # Our neighbor ...
multihop; # ... which is connected indirectly multihop; # ... which is connected indirectly
authenticate manual {
key {
local id 2;
remote id 1;
cipher "cmac(aes128)";
master key "hello321";
required;
}key {
local id 3;
remote id 3;
cipher "cmac(aes128)";
master key "bye123";
}
}
ipv4 { ipv4 {
export filter { # We use non-trivial export rules export filter { # We use non-trivial export rules
if source = RTS_STATIC then { # Export only static routes if source = RTS_STATIC then { # Export only static routes

View File

@ -36,6 +36,29 @@ struct ssh_sock {
}; };
#endif #endif
struct ao_key
{
int local_id;
int remote_id;
const char *cipher;
const char *master_key;
int required;
};
struct ao_config
{
struct ao_key key;
struct ao_config *next_key;
};
struct bgp_ao_key {
struct ao_key key;
int activ_alive; /* this ao key is in activ socket */
int passiv_alive; /* this ao key is in passiv socket */
int to_delete; /* flag for reconfig */
struct bgp_ao_key *next_key;
};
typedef struct birdsock { typedef struct birdsock {
resource r; resource r;
pool *pool; /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */ pool *pool; /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */
@ -77,6 +100,11 @@ typedef struct birdsock {
node n; node n;
void *rbuf_alloc, *tbuf_alloc; void *rbuf_alloc, *tbuf_alloc;
const char *password; /* Password for MD5 authentication */ const char *password; /* Password for MD5 authentication */
struct bgp_ao_key *ao_key_init; /* Key for tcp ao authentication icialization. */
struct bgp_proto *proto_del_ao_key; /* For deletion of the currently used deprecated ao key */
char use_ao; /* This is the only reliable flag saying if the socket use ao or not */
int last_used_ao_key; /* Last ID the other site requested */
int desired_ao_key; /* ID of requested ao key */
const char *err; /* Error message */ const char *err; /* Error message */
struct ssh_sock *ssh; /* Used in SK_SSH */ struct ssh_sock *ssh; /* Used in SK_SSH */
} sock; } sock;
@ -107,6 +135,16 @@ int sk_setup_broadcast(sock *s);
int sk_set_ttl(sock *s, int ttl); /* Set transmit TTL for given socket */ int sk_set_ttl(sock *s, int ttl); /* Set transmit TTL for given socket */
int sk_set_min_ttl(sock *s, int ttl); /* Set minimal accepted TTL for given socket */ int sk_set_min_ttl(sock *s, int ttl); /* Set minimal accepted TTL for given socket */
int sk_set_md5_auth(sock *s, ip_addr local, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int setkey); int sk_set_md5_auth(sock *s, ip_addr local, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int setkey);
int get_current_key_id(int sock_fd);
int get_rnext_key_id(int sock_fd);
int sk_set_ao_auth(sock *s, ip_addr local, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int passwd_id_loc, int passwd_id_rem, const char *cipher, int set_current);
int ao_delete_key(sock *s, ip_addr remote, int pxlen, struct iface *ifa, int passwd_id_rem, int passwd_id_loc);
void log_tcp_ao_info(int sock_fd);
void log_tcp_ao_get_key(int sock_fd);
void tcp_ao_get_info(int sock_fd, int key_info[4]);
int check_ao_keys_id(int sock_fd, struct bgp_ao_key *key);
void ao_try_change_master(sock *s, int next_key_id_loc, int next_id_rem);
int sk_set_ipv6_checksum(sock *s, int offset); int sk_set_ipv6_checksum(sock *s, int offset);
int sk_set_icmp6_filter(sock *s, int p1, int p2); int sk_set_icmp6_filter(sock *s, int p1, int p2);
void sk_log_error(sock *s, const char *p); void sk_log_error(sock *s, const char *p);

View File

@ -233,7 +233,7 @@ bgp_close(struct bgp_proto *p)
static inline int static inline int
bgp_setup_auth(struct bgp_proto *p, int enable) bgp_setup_auth(struct bgp_proto *p, int enable)
{ {
if (p->cf->password) if (p->cf->password || p->cf->ao_key)
{ {
ip_addr prefix = p->cf->remote_ip; ip_addr prefix = p->cf->remote_ip;
int pxlen = -1; int pxlen = -1;
@ -243,10 +243,37 @@ bgp_setup_auth(struct bgp_proto *p, int enable)
prefix = net_prefix(p->cf->remote_range); prefix = net_prefix(p->cf->remote_range);
pxlen = net_pxlen(p->cf->remote_range); pxlen = net_pxlen(p->cf->remote_range);
} }
int rv = 0;
int rv = sk_set_md5_auth(p->sock->sk, if (p->cf->ao_key)
{
if (enable)
{
struct bgp_ao_key *key = p->ao_key;
do {
rv = sk_set_ao_auth(p->sock->sk,
p->cf->local_ip, prefix, pxlen, p->cf->iface, p->cf->local_ip, prefix, pxlen, p->cf->iface,
enable ? p->cf->password : NULL, p->cf->setkey); key->key.master_key, key->key.local_id, key->key.remote_id, key->key.cipher, 0);
if (rv == 0)
key->passiv_alive = 1;
key = key->next_key;
} while(key);
}
else
{
struct bgp_ao_key *key = p->ao_key;
while (key)
{
if (key->passiv_alive)
ao_delete_key(p->sock->sk, p->remote_ip, -1, p->sock->sk->iface, key->key.local_id, key->key.remote_id);
key = key->next_key;
}
}
}
else if (enable)
rv = sk_set_md5_auth(p->sock->sk,
p->cf->local_ip, prefix, pxlen, p->cf->iface,
p->cf->password, p->cf->setkey);
if (rv < 0) if (rv < 0)
sk_log_error(p->sock->sk, p->p.name); sk_log_error(p->sock->sk, p->p.name);
@ -1142,6 +1169,7 @@ bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing c
s->tbsize = p->cf->enable_extended_messages ? BGP_TX_BUFFER_EXT_SIZE : BGP_TX_BUFFER_SIZE; s->tbsize = p->cf->enable_extended_messages ? BGP_TX_BUFFER_EXT_SIZE : BGP_TX_BUFFER_SIZE;
s->tos = IP_PREC_INTERNET_CONTROL; s->tos = IP_PREC_INTERNET_CONTROL;
s->password = p->cf->password; s->password = p->cf->password;
s->ao_key_init = p->ao_key;
s->tx_hook = bgp_connected; s->tx_hook = bgp_connected;
s->flags = p->cf->free_bind ? SKF_FREEBIND : 0; s->flags = p->cf->free_bind ? SKF_FREEBIND : 0;
BGP_TRACE(D_EVENTS, "Connecting to %I%J from local address %I%J", BGP_TRACE(D_EVENTS, "Connecting to %I%J from local address %I%J",
@ -1231,6 +1259,16 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
rfree(sk); rfree(sk);
return 0; return 0;
} }
if (p->cf->ao_key)
{
if (get_current_key_id(sk->fd) == -1)
{
log(L_WARN "BGP: Connection from address %I%J (port %d) has no TCP AO key",
sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL, sk->dport);
rfree(sk);
return 0;
}
}
/* /*
* BIRD should keep multiple incoming connections in OpenSent state (for * BIRD should keep multiple incoming connections in OpenSent state (for
@ -1273,6 +1311,26 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
if (sk_set_min_ttl(sk, 256 - hops) < 0) if (sk_set_min_ttl(sk, 256 - hops) < 0)
goto err; goto err;
if (p->ao_key)
{
if (check_ao_keys_id(sk->fd, p->ao_key) == 0)
{
sk->use_ao = 1;
for (struct bgp_ao_key *key = p->ao_key; key; key = key->next_key)
{
key->activ_alive = key->passiv_alive;
if (key->key.required == 1)
{
sk->desired_ao_key = key->key.local_id;
ao_try_change_master(sk, key->key.local_id, key->key.remote_id);
}
else if (key->key.required == -1)
{
sk->proto_del_ao_key = p;
}
}
}
}
if (p->cf->enable_extended_messages) if (p->cf->enable_extended_messages)
{ {
sk->rbsize = BGP_RX_BUFFER_EXT_SIZE; sk->rbsize = BGP_RX_BUFFER_EXT_SIZE;
@ -1755,6 +1813,20 @@ bgp_init(struct proto_config *CF)
p->remote_ip = cf->remote_ip; p->remote_ip = cf->remote_ip;
p->remote_as = cf->remote_as; p->remote_as = cf->remote_as;
if (cf->ao_key)
{
struct ao_config *cf_key = cf->ao_key;
do {
struct bgp_ao_key *key = mb_alloc(proto_pool, sizeof(struct bgp_ao_key));
key->key = cf_key->key;
key->activ_alive = 0;
key->passiv_alive = 0;
key->next_key = p->ao_key;
p->ao_key = key;
cf_key = cf_key->next_key;
} while (cf_key);
}
/* Hack: We use cf->remote_ip just to pass remote_ip from bgp_spawn() */ /* Hack: We use cf->remote_ip just to pass remote_ip from bgp_spawn() */
if (cf->c.parent) if (cf->c.parent)
cf->remote_ip = IPA_NONE; cf->remote_ip = IPA_NONE;
@ -2180,6 +2252,146 @@ bgp_postconfig(struct proto_config *CF)
} }
} }
int compare_aos(struct ao_key *a, struct ao_key *b)
{
if (a->local_id != b->local_id)
return 1;
if (a->remote_id != b->remote_id)
return 1;
if (strcmp(a->cipher, b->cipher))
return 1;
return strcmp(a->master_key, b->master_key);
}
int reconfigure_tcp_ao(struct bgp_proto *old_proto, struct bgp_config new)
{
log("reconfiguring proto");
if (old_proto->cf->ao_key == NULL && new.ao_key == NULL)
return 1; // tcp ao not used
if (old_proto->cf->ao_key == NULL || new.ao_key == NULL)
return 0; // connection is changing from ao to no ao or no ao to ao
if (!old_proto->conn)
{
log("tcp ao: reconfigure nonestablished connection");
return 0; // Connection was not (re)established, so we can not change it.
}
sock *s_passiv = old_proto->sock->sk;
sock *s_activ = old_proto->conn->sk;
int key_in_use_loc = get_current_key_id(s_activ->fd);
if (key_in_use_loc == -1)
{
log(L_WARN "TCP AO: Unable to detect currently used key");
return 0;
}
for (struct bgp_ao_key *ao_key = old_proto->ao_key; ao_key; ao_key = ao_key->next_key)
ao_key->to_delete = 1;
struct bgp_ao_key *first = old_proto->ao_key;
for (struct ao_config *cf_ao = new.ao_key; cf_ao; cf_ao = cf_ao->next_key)
{
if (cf_ao->key.required == -1 && cf_ao->key.local_id != key_in_use_loc)
continue;
struct bgp_ao_key *found = NULL;
for (struct bgp_ao_key *old_ao = first; old_ao && !found; old_ao = old_ao->next_key)
{
if (old_ao->key.local_id == cf_ao->key.local_id || old_ao->key.remote_id == cf_ao->key.remote_id)
{
if (compare_aos(&old_ao->key, &cf_ao->key))
return 0;
if (old_ao->activ_alive == 0 && cf_ao->key.required >= 0)
{
if (sk_set_ao_auth(s_activ, old_proto->local_ip, old_proto->remote_ip, -1, s_activ->iface,
old_ao->key.master_key, old_ao->key.local_id, old_ao->key.remote_id, old_ao->key.cipher, 0))
return 0;
old_ao->activ_alive = 1;
}
if (old_ao->passiv_alive == 0 && cf_ao->key.required >= 0)
{
if (sk_set_ao_auth(s_passiv, old_proto->local_ip, old_proto->remote_ip, -1, s_passiv->iface,
old_ao->key.master_key, old_ao->key.local_id, old_ao->key.remote_id, old_ao->key.cipher, 0))
return 0;
old_ao->passiv_alive = 1;
}
if (cf_ao->key.required == 1 && old_ao->key.required != 1)
{
s_activ->desired_ao_key = old_ao->key.local_id;
s_passiv->desired_ao_key = old_ao->key.local_id;
ao_try_change_master(s_activ, old_ao->key.local_id, old_ao->key.remote_id);
if (old_proto->conn->hold_timer->expires != 0)
bgp_schedule_packet(old_proto->conn, NULL, PKT_KEEPALIVE); // We might send this keepalive shortly after another.
// RFC says we should wait, but since reconfiguration is rare, this is harmless.
}
old_ao->key = cf_ao->key;
old_ao->to_delete = 0;
found = old_ao;
}
}
if (!found)
{
struct bgp_ao_key *key = mb_alloc(old_proto->p.pool, sizeof(struct bgp_ao_key));
key->key = cf_ao->key;
key->activ_alive = 0;
key->passiv_alive = 0;
key->next_key = first;
old_proto->ao_key = key;
if (sk_set_ao_auth(s_passiv, old_proto->local_ip, old_proto->remote_ip, -1, s_passiv->iface,
cf_ao->key.master_key, cf_ao->key.local_id, cf_ao->key.remote_id, cf_ao->key.cipher, 0))
return 0;
key->passiv_alive = 1;
if (sk_set_ao_auth(s_activ, old_proto->local_ip, old_proto->remote_ip, -1, s_passiv->iface,
cf_ao->key.master_key, cf_ao->key.local_id, cf_ao->key.remote_id, cf_ao->key.cipher, 0))
return 0;
key->activ_alive = 1;
key->to_delete = 0;
found = key;
if (found->key.required == 1)
{
s_activ->desired_ao_key = found->key.local_id;
s_passiv->desired_ao_key = found->key.local_id;
ao_try_change_master(s_activ, found->key.local_id, found->key.remote_id);
if (old_proto->conn->hold_timer->expires != 0)
bgp_schedule_packet(old_proto->conn, NULL, PKT_KEEPALIVE); // We might send this keepalive shortly after another.
// RFC says we should wait, but since reconfiguration is rare, this is harmless.
}
}
}
key_in_use_loc = get_current_key_id(s_activ->fd);
struct bgp_ao_key *previous = NULL;
for (struct bgp_ao_key *old_ao = old_proto->ao_key; old_ao; old_ao = old_ao->next_key)
{
if (old_ao->to_delete)
{
if (old_ao->key.local_id == key_in_use_loc)
{
log(L_WARN "TCP AO: deleting currently used key");
return 0;
}
if (ao_delete_key(s_activ, old_proto->remote_ip, -1, s_activ->iface, old_ao->key.local_id, old_ao->key.remote_id))
return 0;
old_ao->activ_alive = 0;
if (ao_delete_key(s_passiv, old_proto->remote_ip, -1, s_passiv->iface, old_ao->key.local_id, old_ao->key.remote_id))
return 0;
old_ao->passiv_alive = 0;
if (previous)
previous->next_key = old_ao->next_key;
else
old_proto->ao_key = old_ao->next_key;
}
else
previous = old_ao;
}
return 1;
}
static int static int
bgp_reconfigure(struct proto *P, struct proto_config *CF) bgp_reconfigure(struct proto *P, struct proto_config *CF)
{ {
@ -2190,11 +2402,13 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF)
if (proto_get_router_id(CF) != p->local_id) if (proto_get_router_id(CF) != p->local_id)
return 0; return 0;
log("bgp_reconfigure, will tcp ao?");
int same = !memcmp(((byte *) old) + sizeof(struct proto_config), int same = !memcmp(((byte *) old) + sizeof(struct proto_config),
((byte *) new) + sizeof(struct proto_config), ((byte *) new) + sizeof(struct proto_config),
// password item is last and must be checked separately // password item is last and must be checked separately
OFFSETOF(struct bgp_config, password) - sizeof(struct proto_config)) OFFSETOF(struct bgp_config, password) - sizeof(struct proto_config))
&& !bstrcmp(old->password, new->password) && !bstrcmp(old->password, new->password)
&& reconfigure_tcp_ao(p, *new)
&& ((!old->remote_range && !new->remote_range) && ((!old->remote_range && !new->remote_range)
|| (old->remote_range && new->remote_range && net_equal(old->remote_range, new->remote_range))) || (old->remote_range && new->remote_range && net_equal(old->remote_range, new->remote_range)))
&& !bstrcmp(old->dynamic_name, new->dynamic_name) && !bstrcmp(old->dynamic_name, new->dynamic_name)
@ -2654,6 +2868,17 @@ bgp_show_proto_info(struct proto *P)
tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time); tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time);
cli_msg(-1006, " Send hold timer: %t/%u", cli_msg(-1006, " Send hold timer: %t/%u",
tm_remains(p->conn->send_hold_timer), p->conn->send_hold_time); tm_remains(p->conn->send_hold_timer), p->conn->send_hold_time);
if (p->cf->ao_key)
{
int tmp[4];
tcp_ao_get_info(p->conn->sk->fd, tmp);
cli_msg(-1006, " TCP AO:");
cli_msg(-1006, " current key remote id %i", tmp[0]);
cli_msg(-1006, " rnext key local id %i", tmp[1]);
cli_msg(-1006, " good packets %i", tmp[2]);
cli_msg(-1006, " bad packets %i", tmp[3]);
}
} }
#if 0 #if 0

View File

@ -136,6 +136,7 @@ struct bgp_config {
u32 disable_after_cease; /* Disable it when cease is received, bitfield */ u32 disable_after_cease; /* Disable it when cease is received, bitfield */
const char *password; /* Password used for MD5 authentication */ const char *password; /* Password used for MD5 authentication */
struct ao_config *ao_key; /* Keys for tcp ao authentication */
net_addr *remote_range; /* Allowed neighbor range for dynamic BGP */ net_addr *remote_range; /* Allowed neighbor range for dynamic BGP */
const char *dynamic_name; /* Name pattern for dynamic BGP */ const char *dynamic_name; /* Name pattern for dynamic BGP */
int dynamic_name_digits; /* Minimum number of digits for dynamic names */ int dynamic_name_digits; /* Minimum number of digits for dynamic names */
@ -353,6 +354,7 @@ struct bgp_proto {
struct object_lock *lock; /* Lock for neighbor connection */ struct object_lock *lock; /* Lock for neighbor connection */
struct neighbor *neigh; /* Neighbor entry corresponding to remote ip, NULL if multihop */ struct neighbor *neigh; /* Neighbor entry corresponding to remote ip, NULL if multihop */
struct bgp_socket *sock; /* Shared listening socket */ struct bgp_socket *sock; /* Shared listening socket */
struct bgp_ao_key *ao_key; /* Linked list for ao keys */
struct bfd_request *bfd_req; /* BFD request, if BFD is used */ struct bfd_request *bfd_req; /* BFD request, if BFD is used */
struct birdsock *postponed_sk; /* Postponed incoming socket for dynamic BGP */ struct birdsock *postponed_sk; /* Postponed incoming socket for dynamic BGP */
struct bgp_stats stats; /* BGP statistics */ struct bgp_stats stats; /* BGP statistics */

View File

@ -32,7 +32,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS, LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS,
DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE, DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER, FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND) RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND,
AUTHENTICATE, MANUAL, KEY, MASTER, DEPRECATED, REQUIRED, CIPHER, LOCAL, REMOTE)
%type <i> bgp_nh %type <i> bgp_nh
%type <i32> bgp_afi %type <i32> bgp_afi
@ -201,7 +202,8 @@ bgp_proto:
| bgp_proto REQUIRE GRACEFUL RESTART bool ';' { BGP_CFG->require_gr = $5; } | bgp_proto REQUIRE GRACEFUL RESTART bool ';' { BGP_CFG->require_gr = $5; }
| bgp_proto REQUIRE LONG LIVED GRACEFUL RESTART bool ';' { BGP_CFG->require_llgr = $7; } | bgp_proto REQUIRE LONG LIVED GRACEFUL RESTART bool ';' { BGP_CFG->require_llgr = $7; }
| bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; } | bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; }
| bgp_proto PASSWORD text ';' { BGP_CFG->password = $3; } | bgp_proto PASSWORD text ';' { log("%s", $3); BGP_CFG->password = $3; }
| bgp_proto AUTHENTICATE MANUAL '{' ao_keys '}' tcp_ao_end
| bgp_proto SETKEY bool ';' { BGP_CFG->setkey = $3; } | bgp_proto SETKEY bool ';' { BGP_CFG->setkey = $3; }
| bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; } | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; }
| bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; } | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; }
@ -242,6 +244,95 @@ bgp_afi:
| FLOW6 { $$ = BGP_AF_FLOW6; } | FLOW6 { $$ = BGP_AF_FLOW6; }
; ;
ao_keys:
KEY '{' ao_first_item ao_key '}'
| KEY '{' ao_first_item ao_key '}' ao_keys
;
ao_key:
ao_item
| ao_item ao_key
;
ao_first_item:
LOCAL ID expr ';' {
if ($3 >= 256)
cf_error("Key ids ust be in range 0 - 255");
struct ao_config *new_key = cfg_alloc(sizeof(struct ao_config));
new_key->next_key = BGP_CFG->ao_key;
BGP_CFG->ao_key = new_key;
BGP_CFG->ao_key->key.required = 0;
BGP_CFG->ao_key->key.local_id = $3;
BGP_CFG->ao_key->key.remote_id = -1;
}
;
ao_item:
REMOTE ID expr ';' {
if ($3 > 255)
cf_error("TCP AO: Key id must be in range 0 - 255");
BGP_CFG->ao_key->key.remote_id = $3; }
| CIPHER text ';' {
if (strcmp($2, "cmac(aes128)") & strcmp($2, "hmac(sha1)") & strcmp($2, "hmac(sha224)") & strcmp($2, "hmac(sha256)") & strcmp($2, "hmac(sha384)") & strcmp($2, "hmac(sha512)")& strcmp($2, "hmac(md5)"))
cf_error("TCP AO: Here are ciphers 'cmac(aes128)', 'hmac(md5)', 'hmac(sha1)', 'hmac(sha224)', 'hmac(sha256)', 'hmac(sha384)' and 'hmac(sha512)' hardcoded. If there is another cipher available in kernel, please contact BIRD developers.");
char *c = cfg_alloc(strlen($2)+1);
memcpy(c, $2, strlen($2)+1);
BGP_CFG->ao_key->key.cipher = c;
}
| MASTER KEY text ';' {
char *k = cfg_alloc(strlen($3)+1);
memcpy(k, $3, strlen($3)+1);
BGP_CFG->ao_key->key.master_key = k;
}
| DEPRECATED ';' {
if (BGP_CFG->ao_key->key.required == 0)
BGP_CFG->ao_key->key.required = -1;
else
cf_error("TCP AO: Key can be only once deprecated or once required, key id %i", BGP_CFG->ao_key->key.local_id);
}
| REQUIRED ';' {
if (BGP_CFG->ao_key->key.required == 0)
BGP_CFG->ao_key->key.required = 1;
else
cf_error("TCP AO: Key can be only once deprecated or once required, key id %i", BGP_CFG->ao_key->key.local_id);
}
;
tcp_ao_end:
{
char used_aos_id_loc[256];
char used_aos_id_rem[256];
memset(used_aos_id_loc, 0, sizeof(char)*256);
memset(used_aos_id_rem, 0, sizeof(char)*256);
int required_found = 0;
struct ao_config *key = BGP_CFG->ao_key;
while (key)
{
if (used_aos_id_loc[key->key.local_id])
cf_error("TCP AO: Reused local key id %i", key->key.local_id);
used_aos_id_loc[key->key.local_id] = 1;
if (key->key.remote_id == -1)
cf_error("TCP AO: No remote key id for local id %i", key->key.local_id);
if (used_aos_id_rem[key->key.remote_id])
cf_error("TCP AO: Reused remote key id %i", key->key.remote_id);
used_aos_id_rem[key->key.remote_id] = 1;
if (!key->key.cipher)
cf_error("TCP AO: No cipher given for key id %i.", key->key.local_id);
if (!key->key.master_key)
cf_error("TCP AO: No master key given for key id %i.", key->key.local_id);
if (key->key.required == 1)
{
if (required_found)
cf_error("TCP AO: How do you want to use two keys at once? Check 'REQUIRED'");
required_found = 1;
}
key = key->next_key;
}
if (required_found == 0)
cf_error("TCP AO: Missing 'REQUIRED'. Which key should be used?");
}
bgp_channel_start: bgp_afi bgp_channel_start: bgp_afi
{ {
const struct bgp_af_desc *desc = bgp_get_af_desc($1); const struct bgp_af_desc *desc = bgp_get_af_desc($1);

View File

@ -3477,6 +3477,32 @@ bgp_rx_packet(struct bgp_conn *conn, byte *pkt, uint len)
} }
} }
int
delete_deprecated_keys(sock *sk, struct bgp_proto *p, int new_lnext)
{
struct bgp_ao_key *key = p->ao_key;
int ret = 1;
while (key)
{
if (key->key.required == -1)
{
if (new_lnext == key->key.local_id)
ret = 0;
else
{
if (ao_delete_key(sk, p->remote_ip, -1, sk->iface, key->key.local_id, key->key.remote_id))
bug("TCP AO: Can not delete deprecated key %i %i on socket %i", key->key.local_id, key->key.remote_id, sk->fd);
key->activ_alive = 0;
if (ao_delete_key(p->sock->sk, p->remote_ip, -1, p->sock->sk->iface, key->key.local_id, key->key.remote_id))
bug("TCP AO: Can not delete deprecated key %i %i on socket %i", key->key.local_id, key->key.remote_id, p->sock->sk->fd);
key->passiv_alive = 0;
}
}
key = key->next_key;
}
return ret;
}
/** /**
* bgp_rx - handle received data * bgp_rx - handle received data
* @sk: socket * @sk: socket
@ -3491,6 +3517,25 @@ int
bgp_rx(sock *sk, uint size) bgp_rx(sock *sk, uint size)
{ {
struct bgp_conn *conn = sk->data; struct bgp_conn *conn = sk->data;
if (sk->use_ao && sk->desired_ao_key != sk->last_used_ao_key)
{
int new_lnext = get_current_key_id(sk->fd);
if (new_lnext != sk->last_used_ao_key)
{
if (conn->hold_timer->expires != 0)
bgp_schedule_packet(conn, NULL, PKT_KEEPALIVE); // We might send this keepalive shortly after another. RFC says we should wait, but since reconfiguration is rare, this is harmless.
log(L_INFO "TCP AO: Expected key rotation: desired lnext %i, received %i", sk->desired_ao_key, new_lnext);
log_tcp_ao_info(sk->fd);
if (sk->proto_del_ao_key && sk->desired_ao_key == new_lnext)
{
if (delete_deprecated_keys(sk, sk->proto_del_ao_key, new_lnext))
sk->proto_del_ao_key = NULL;
}
sk->last_used_ao_key = new_lnext;
}
}
byte *pkt_start = sk->rbuf; byte *pkt_start = sk->rbuf;
byte *end = pkt_start + size; byte *end = pkt_start + size;
uint i, len; uint i, len;

View File

@ -6,6 +6,8 @@
* Can be freely distributed and used under the terms of the GNU GPL. * Can be freely distributed and used under the terms of the GNU GPL.
*/ */
#include "sysdep/linux/tcp-ao.h"
#ifndef IPV6_MINHOPCOUNT #ifndef IPV6_MINHOPCOUNT
#define IPV6_MINHOPCOUNT 73 #define IPV6_MINHOPCOUNT 73
#endif #endif
@ -22,6 +24,7 @@
#define TCP_MD5SIG_FLAG_PREFIX 1 #define TCP_MD5SIG_FLAG_PREFIX 1
#endif #endif
/* We redefine the tcp_md5sig structure with different name to avoid collision with older headers */ /* We redefine the tcp_md5sig structure with different name to avoid collision with older headers */
struct tcp_md5sig_ext { struct tcp_md5sig_ext {
struct sockaddr_storage tcpm_addr; /* Address associated */ struct sockaddr_storage tcpm_addr; /* Address associated */
@ -209,6 +212,250 @@ sk_set_md5_auth(sock *s, ip_addr local UNUSED, ip_addr remote, int pxlen, struct
return 0; return 0;
} }
void log_tcp_ao_info(int sock_fd)
{
struct tcp_ao_info_opt_ext tmp;
memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
socklen_t len = sizeof(tmp);
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
{
log(L_WARN "TCP AO: log tcp ao info failed with err code %i", errno);
return;
}
else
log(L_INFO "TCP AO on socket %i:\ncurrent key id %i (loc), next key %i (rem),\n set current %i, is ao required %i\n good packets %i, bad packets %i",
sock_fd, tmp.current_key, tmp.rnext, tmp.set_current, tmp.ao_required, tmp.pkt_good, tmp.pkt_bad);
}
int get_current_key_id(int sock_fd)
{
struct tcp_ao_info_opt_ext tmp;
memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
socklen_t len = sizeof(tmp);
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
{
log(L_WARN "TCP AO: Getting current ao key for socket file descriptor %i failed with errno %i", sock_fd, errno);
return -1;
}
else
return tmp.current_key;
}
int get_rnext_key_id(int sock_fd)
{
struct tcp_ao_info_opt_ext tmp;
memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
socklen_t len = sizeof(tmp);
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
{
log(L_WARN "TCP AO: Getting rnext ao key for socket file descriptor %i failed with errno %i", sock_fd, errno);
return -1;
}
else
return tmp.rnext;
}
int get_num_ao_keys(int sock_fd)
{
struct tcp_ao_getsockopt_ext tmp;
memset(&tmp, 0, sizeof(struct tcp_ao_getsockopt_ext));
socklen_t len = sizeof(tmp);
tmp.nkeys = 1;
tmp.get_all = 1;
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, &tmp, &len))
{
log(L_WARN "TCP AO: get keys on socket fd %i failed with err code %i", sock_fd, errno);
return -1;
}
return tmp.nkeys;
}
void
log_tcp_ao_get_key(int sock_fd)
{
int nkeys = get_num_ao_keys(sock_fd);
if (nkeys < 0)
return;
struct tcp_ao_getsockopt_ext tm_all[nkeys];
socklen_t len = sizeof(struct tcp_ao_getsockopt_ext);
memset(tm_all, 0, sizeof(struct tcp_ao_getsockopt_ext)*nkeys);
tm_all[0].nkeys = nkeys;
tm_all[0].get_all = 1;
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, tm_all, &len)) // len should be still size of one struct. Because kernel net/ipv4/tcp_ao.c line 2165
{
log(L_WARN "TCP AO: getting keys on socket fd %i failed with err code %i", sock_fd, errno);
return;
}
log(L_INFO "TCP AO on socket fd %i has %i keys", tm_all[0].nkeys);
for (int i = 0; i < nkeys; i++)
{
char key_val[TCP_AO_MAXKEYLEN_*2+1];
for (int ik = 0; ik<TCP_AO_MAXKEYLEN_; ik++)
sprintf(&key_val[ik*2], "%x", tm_all[i].key[ik]);
key_val[TCP_AO_MAXKEYLEN_*2] = 0;
log(L_INFO "sndid %i rcvid %i, %s %s, cipher %s key %x (%i/%i)",
tm_all[i].sndid, tm_all[i].rcvid, tm_all[i].is_current ? "current" : "",
tm_all[i].is_rnext ? "rnext" : "", tm_all[i].alg_name, key_val, i+1, tm_all[0].nkeys);
}
}
void
tcp_ao_get_info(int sock_fd, int key_info[4])
{
struct tcp_ao_info_opt_ext tmp;
memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
socklen_t len = sizeof(tmp);
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
{
log(L_WARN "TCP AO: log tcp ao info failed with err code %i", errno);
return;
}
key_info[0] = tmp.current_key;
key_info[1] = tmp.rnext;
key_info[2] = tmp.pkt_good;
key_info[3] = tmp.pkt_bad;
}
int
sk_set_ao_auth(sock *s, ip_addr local UNUSED, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int passwd_id_loc, int passwd_id_rem, const char* cipher, int set_current)
{
struct tcp_ao_add_ext ao;
memset(&ao, 0, sizeof(struct tcp_ao_add_ext));
log(L_DEBUG "tcp ao: socket sets ao, password %s socket fd %i", passwd, s->fd);
sockaddr_fill((sockaddr *) &ao.addr, s->af, remote, ifa, 0);
if (set_current)
{
ao.set_rnext = 1;
ao.set_current = 1;
}
if (pxlen >= 0)
ao.prefix = pxlen;
else if(s->af == AF_INET)
ao.prefix = 32;
else
ao.prefix = 128;
ao.sndid = passwd_id_loc;
ao.rcvid = passwd_id_rem;
ao.maclen = 0;
ao.keyflags = 0;
ao.ifindex = 0;
strncpy(ao.alg_name, (cipher) ? cipher : DEFAULT_TEST_ALGO, 64);
ao.keylen = strlen(passwd);
memcpy(ao.key, passwd, (strlen(passwd) > TCP_AO_MAXKEYLEN_) ? TCP_AO_MAXKEYLEN_ : strlen(passwd));
if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_ADD_KEY, &ao, sizeof(ao)) < 0)
{
if (errno == ENOPROTOOPT)
ERR_MSG("Kernel does not support extended TCP AO signatures");
else
ERR("TCP_AOSIG_EXT");
}
s->use_ao = 1;
if (set_current)
s->desired_ao_key = passwd_id_loc;
log_tcp_ao_get_key(s->fd);
return 0;
}
int
ao_delete_key(sock *s, ip_addr remote, int pxlen, struct iface *ifa, int passwd_id_loc, int passwd_id_rem)
{
struct tcp_ao_del_ext del;
memset(&del, 0, sizeof(struct tcp_ao_del_ext));
sockaddr_fill((sockaddr *) &del.addr, s->af, remote, ifa, 0);
del.sndid = passwd_id_loc;
del.rcvid = passwd_id_rem;
if (pxlen >= 0)
del.prefix = pxlen;
else if(s->af == AF_INET)
del.prefix = 32;
else
del.prefix = 128;
if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_DEL_KEY, &del, sizeof(del)) < 0)
{
log(L_WARN "TCP AO: deletion of key %i %i on socket fd %i failed with err %i", passwd_id_loc, passwd_id_rem, s->fd, errno);
return errno;
}
log(L_DEBUG "tcp ao: key %i %i deleted", passwd_id_loc, passwd_id_rem);
return 0;
}
void
ao_try_change_master(sock *s, int next_master_id_loc, int next_master_id_rem)
{
struct tcp_ao_info_opt_ext tmp;
memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
tmp.set_rnext = 1;
tmp.rnext = next_master_id_rem;
if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, sizeof(tmp)))
{
log(L_WARN "TCP AO: change master key failed with err code %i", errno);
log_tcp_ao_get_key(s->fd);
return;
}
else
log(L_DEBUG "tcp ao: tried to change master to %i %i", next_master_id_loc, next_master_id_rem);
s->desired_ao_key = next_master_id_loc;
}
int check_ao_keys_id(int sock_fd, struct bgp_ao_key *keys)
{
int errors = 0;
int expected_keys[256]; //can not have char, because we must support 0 key id
memset(expected_keys, 0, sizeof(int)*256);
for (struct bgp_ao_key *key = keys; key; key = key->next_key)
expected_keys[key->key.local_id] = key->key.remote_id + 1; // the + 1 because we do not want 0 id be 0
int nkeys = get_num_ao_keys(sock_fd);
if (nkeys == -1)
{
log(L_WARN "TCP AO: unable to get num of keys");
return 1;
}
struct tcp_ao_getsockopt_ext tm_all[nkeys];
socklen_t len = sizeof(struct tcp_ao_getsockopt_ext);
memset(tm_all, 0, sizeof(struct tcp_ao_getsockopt_ext)*nkeys);
tm_all[0].nkeys = nkeys;
tm_all[0].get_all = 1;
if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, tm_all, &len)) // len should be still size of one struct. Because kernel net/ipv4/tcp_ao.c line 2165
{
log(L_WARN "TCP AO: log tcp ao get keys failed with err code %i", errno);
return 1;
}
for (int i = 0; i< nkeys; i++)
{
struct tcp_ao_getsockopt_ext sock_key = tm_all[i];
if (expected_keys[sock_key.sndid] - 1 != sock_key.rcvid)
{
if (expected_keys[sock_key.rcvid] == 0)
log(L_WARN "TCP AO: unexpected ao key %i %i", sock_key.rcvid, sock_key.sndid);
else
log(L_WARN "TCP AO: expected key local id %i has different remote id than expected (%i vs %i)", sock_key.sndid, expected_keys[sock_key.sndid] - 1, sock_key.rcvid);
errors++;
}
expected_keys[sock_key.sndid] = 0;
}
for (int i = 0; i < 256; i++)
{
if (expected_keys[i] != 0)
{
log(L_WARN "TCP AO: key %i %i is not in socket", i, expected_keys - 1);
errors++;
}
}
return errors;
}
static inline int static inline int
sk_set_min_ttl4(sock *s, int ttl) sk_set_min_ttl4(sock *s, int ttl)
{ {

109
sysdep/linux/tcp-ao.h Normal file
View File

@ -0,0 +1,109 @@
#ifndef TCP_AO_ADD_KEY
#define TCP_AO_ADD_KEY 38 /* Add/Set MKT */
#define TCP_AO_DEL_KEY 39 /* Delete MKT */
#define TCP_AO_INFO 40 /* Set/list TCP-AO per-socket options */
#define TCP_AO_GET_KEYS 41 /* List MKT(s) */
#define TCP_AO_REPAIR 42 /* Get/Set SNEs and ISNs */
#endif
#ifndef TCP_AO_STRUCTS
#define TCP_AO_STRUCTS
#define TCP_AO_MAXKEYLEN_ 80
#define DEFAULT_TEST_ALGO "cmac(aes128)"
struct tcp_ao_add_ext { /* setsockopt(TCP_AO_ADD_KEY) */
struct sockaddr_storage addr; /* peer's address for the key */
char alg_name[64]; /* crypto hash algorithm to use */
s32 ifindex; /* L3 dev index for VRF */
u32 set_current :1, /* set key as Current_key at once */
set_rnext :1, /* request it from peer with RNext_key */
reserved :30; /* must be 0 */
u16 reserved2; /* padding, must be 0 */
u8 prefix; /* peer's address prefix */
u8 sndid; /* SendID for outgoing segments */
u8 rcvid; /* RecvID to match for incoming seg */
u8 maclen; /* length of authentication code (hash) */
u8 keyflags; /* see TCP_AO_KEYF_ */
u8 keylen; /* length of ::key */
u8 key[TCP_AO_MAXKEYLEN_];
} __attribute__((aligned(8)));
struct tcp_ao_del_ext { /* setsockopt(TCP_AO_DEL_KEY) */
struct sockaddr_storage addr; /* peer's address for the key */
s32 ifindex; /* L3 dev index for VRF */
u32 set_current :1, /* corresponding ::current_key */
set_rnext :1, /* corresponding ::rnext */
del_async :1, /* only valid for listen sockets */
reserved :29; /* must be 0 */
u16 reserved2; /* padding, must be 0 */
u8 prefix; /* peer's address prefix */
u8 sndid; /* SendID for outgoing segments */
u8 rcvid; /* RecvID to match for incoming seg */
u8 current_key; /* KeyID to set as Current_key */
u8 rnext; /* KeyID to set as Rnext_key */
u8 keyflags; /* see TCP_AO_KEYF_ */
} __attribute__((aligned(8)));
struct tcp_ao_info_opt_ext { /* setsockopt(TCP_AO_INFO), getsockopt(TCP_AO_INFO) */
/* Here 'in' is for setsockopt(), 'out' is for getsockopt() */
u32 set_current :1, /* in/out: corresponding ::current_key */
set_rnext :1, /* in/out: corresponding ::rnext */
ao_required :1, /* in/out: don't accept non-AO connects */
set_counters :1, /* in: set/clear ::pkt_* counters */
accept_icmps :1, /* in/out: accept incoming ICMPs */
reserved :27; /* must be 0 */
u16 reserved2; /* padding, must be 0 */
u8 current_key; /* in/out: KeyID of Current_key */
u8 rnext; /* in/out: keyid of RNext_key */
u64 pkt_good; /* in/out: verified segments */
u64 pkt_bad; /* in/out: failed verification */
u64 pkt_key_not_found; /* in/out: could not find a key to verify */
u64 pkt_ao_required; /* in/out: segments missing TCP-AO sign */
u64 pkt_dropped_icmp; /* in/out: ICMPs that were ignored */
} __attribute__((aligned(8)));
struct tcp_ao_getsockopt_ext { /* getsockopt(TCP_AO_GET_KEYS) */
struct sockaddr_storage addr; /* in/out: dump keys for peer
* with this address/prefix
*/
char alg_name[64]; /* out: crypto hash algorithm */
u8 key[TCP_AO_MAXKEYLEN_];
u32 nkeys; /* in: size of the userspace buffer
* @optval, measured in @optlen - the
* sizeof(struct tcp_ao_getsockopt)
* out: number of keys that matched
*/
u16 is_current :1, /* in: match and dump Current_key,
* out: the dumped key is Current_key
*/
is_rnext :1, /* in: match and dump RNext_key,
* out: the dumped key is RNext_key
*/
get_all :1, /* in: dump all keys */
reserved :13; /* padding, must be 0 */
u8 sndid; /* in/out: dump keys with SendID */
u8 rcvid; /* in/out: dump keys with RecvID */
u8 prefix; /* in/out: dump keys with address/prefix */
u8 maclen; /* out: key's length of authentication
* code (hash)
*/
u8 keyflags; /* in/out: see TCP_AO_KEYF_ */
u8 keylen; /* out: length of ::key */
s32 ifindex; /* in/out: L3 dev index for VRF */
u64 pkt_good; /* out: verified segments */
u64 pkt_bad; /* out: segments that failed verification */
} __attribute__((aligned(8)));
struct tcp_ao_repair_ext { /* {s,g}etsockopt(TCP_AO_REPAIR) */
u32 snt_isn; //should be __be32 alias fdt32_t - 32-bit, big-endian, unsigned integer
u32 rcv_isn; //should be __be32 alias fdt32_t - 32-bit, big-endian, unsigned integer
u32 snd_sne;
u32 rcv_sne;
} __attribute__((aligned(8)));
#endif /* TCP_AO_STRUCTS*/

View File

@ -1480,9 +1480,24 @@ sk_open(sock *s)
ERR2("bind"); ERR2("bind");
} }
if (s->password) if (s->ao_key_init)
{
struct bgp_ao_key *key = s->ao_key_init;
do {
if (sk_set_ao_auth(s, s->saddr, s->daddr, -1, s->iface, key->key.master_key, key->key.local_id, key->key.remote_id, key->key.cipher, key->key.required == 1) < 0)
goto err;
if (s->type == SK_TCP_ACTIVE)
key->activ_alive = 1;
else
key->passiv_alive = 1;
key = key->next_key;
} while (key);
}
else if (s->password)
{
if (sk_set_md5_auth(s, s->saddr, s->daddr, -1, s->iface, s->password, 0) < 0) if (sk_set_md5_auth(s, s->saddr, s->daddr, -1, s->iface, s->password, 0) < 0)
goto err; goto err;
}
switch (s->type) switch (s->type)
{ {