diff --git a/doc/bird.sgml b/doc/bird.sgml
index ced927d4..7519a2ed 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -2912,6 +2912,38 @@ using the following configuration parameters:
set manually by an external utility on NetBSD and OpenBSD. Default:
enabled (ignored on non-FreeBSD).
+ authentication keyed tcp AO
+ 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.
+
passive
Standard BGP behavior is both initiating outgoing connections and
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
neighbor 198.51.100.130 as 64496; # Our neighbor ...
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 {
export filter { # We use non-trivial export rules
if source = RTS_STATIC then { # Export only static routes
diff --git a/lib/socket.h b/lib/socket.h
index 231c10d8..c47f673d 100644
--- a/lib/socket.h
+++ b/lib/socket.h
@@ -36,6 +36,29 @@ struct ssh_sock {
};
#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 {
resource r;
pool *pool; /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */
@@ -77,6 +100,11 @@ typedef struct birdsock {
node n;
void *rbuf_alloc, *tbuf_alloc;
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 */
struct ssh_sock *ssh; /* Used in SK_SSH */
} 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_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 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_icmp6_filter(sock *s, int p1, int p2);
void sk_log_error(sock *s, const char *p);
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index bd6e90d6..2116b132 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -233,7 +233,7 @@ bgp_close(struct bgp_proto *p)
static inline int
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;
int pxlen = -1;
@@ -243,10 +243,37 @@ bgp_setup_auth(struct bgp_proto *p, int enable)
prefix = net_prefix(p->cf->remote_range);
pxlen = net_pxlen(p->cf->remote_range);
}
-
- int rv = sk_set_md5_auth(p->sock->sk,
+ int rv = 0;
+ 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,
- 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)
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->tos = IP_PREC_INTERNET_CONTROL;
s->password = p->cf->password;
+ s->ao_key_init = p->ao_key;
s->tx_hook = bgp_connected;
s->flags = p->cf->free_bind ? SKF_FREEBIND : 0;
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);
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
@@ -1273,6 +1311,26 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
if (sk_set_min_ttl(sk, 256 - hops) < 0)
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)
{
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_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() */
if (cf->c.parent)
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
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)
return 0;
+ log("bgp_reconfigure, will tcp ao?");
int same = !memcmp(((byte *) old) + sizeof(struct proto_config),
((byte *) new) + sizeof(struct proto_config),
// password item is last and must be checked separately
OFFSETOF(struct bgp_config, password) - sizeof(struct proto_config))
&& !bstrcmp(old->password, new->password)
+ && reconfigure_tcp_ao(p, *new)
&& ((!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)
@@ -2654,7 +2868,18 @@ bgp_show_proto_info(struct proto *P)
tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time);
cli_msg(-1006, " Send hold timer: %t/%u",
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
struct bgp_stats *s = &p->stats;
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index 7127bc88..b9e6b15f 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -136,6 +136,7 @@ struct bgp_config {
u32 disable_after_cease; /* Disable it when cease is received, bitfield */
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 */
const char *dynamic_name; /* Name pattern for dynamic BGP */
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 neighbor *neigh; /* Neighbor entry corresponding to remote ip, NULL if multihop */
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 birdsock *postponed_sk; /* Postponed incoming socket for dynamic BGP */
struct bgp_stats stats; /* BGP statistics */
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index 4ce06488..c0030bf9 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -32,7 +32,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS,
DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
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 bgp_nh
%type bgp_afi
@@ -201,7 +202,8 @@ bgp_proto:
| 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 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 PASSIVE bool ';' { BGP_CFG->passive = $3; }
| bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; }
@@ -242,6 +244,95 @@ bgp_afi:
| 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
{
const struct bgp_af_desc *desc = bgp_get_af_desc($1);
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index f1e03621..bbff8927 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -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
* @sk: socket
@@ -3491,6 +3517,25 @@ int
bgp_rx(sock *sk, uint size)
{
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 *end = pkt_start + size;
uint i, len;
diff --git a/sysdep/linux/sysio.h b/sysdep/linux/sysio.h
index f13eda7c..855eee3d 100644
--- a/sysdep/linux/sysio.h
+++ b/sysdep/linux/sysio.h
@@ -6,6 +6,8 @@
* Can be freely distributed and used under the terms of the GNU GPL.
*/
+#include "sysdep/linux/tcp-ao.h"
+
#ifndef IPV6_MINHOPCOUNT
#define IPV6_MINHOPCOUNT 73
#endif
@@ -22,6 +24,7 @@
#define TCP_MD5SIG_FLAG_PREFIX 1
#endif
+
/* We redefine the tcp_md5sig structure with different name to avoid collision with older headers */
struct tcp_md5sig_ext {
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;
}
+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; ikfd);
+
+ 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
sk_set_min_ttl4(sock *s, int ttl)
{
diff --git a/sysdep/linux/tcp-ao.h b/sysdep/linux/tcp-ao.h
new file mode 100644
index 00000000..6cb92740
--- /dev/null
+++ b/sysdep/linux/tcp-ao.h
@@ -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*/
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 9b499020..32645800 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -1480,9 +1480,24 @@ sk_open(sock *s)
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)
goto err;
+ }
switch (s->type)
{