]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
TCP-AO revised version
authorOndrej Zajicek <santiago@crfreenet.org>
Sun, 12 Jan 2025 12:32:37 +0000 (13:32 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Mon, 31 Mar 2025 02:06:20 +0000 (04:06 +0200)
Reworked and finalized version of TCP-AO.

13 files changed:
doc/bird.sgml
lib/lists.h
lib/mac.h
lib/socket.h
nest/config.Y
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y
proto/bgp/packets.c
sysdep/bsd/sysio.h
sysdep/linux/sysio.h
sysdep/linux/tcp-ao.h
sysdep/unix/io.c

index 4d76d74dfcf837b5a8e5e84943f8e561d50ec745..3d968e38aef01c63cd65397d806c37c174f6ebbb 100644 (file)
@@ -1457,7 +1457,7 @@ This argument can be omitted if there exists only a single instance.
        <tag><label id="cli-debug">debug <m/protocol/|<m/pattern/|all all|off|{ states|routes|filters|events|packets [, <m/.../] }</tag>
        Control protocol debugging.
 
-       <tag><label id="cli-dump">dump resources|sockets|interfaces|neighbors|attributes|routes|protocols "<m/file/"</tag>
+       <tag><label id="cli-dump">dump resources|sockets|ao keys|events|interfaces|neighbors|attributes|routes|protocols "<m/file/"</tag>
        Creates the given file (it must not exist) and dumps contents of
        internal data structures there. By sending SIGUSR1, you get all of
        these concatenated to <cf/bird.dump/ in the current directory.
@@ -2842,6 +2842,7 @@ avoid routing loops.
 <item> <rfc id="8955"> - Dissemination of Flow Specification Rules for IPv4
 <item> <rfc id="8956"> - Dissemination of Flow Specification Rules for IPv6
 <item> <rfc id="5668"> - 4-Octet AS Specific BGP Extended Community
+<item> <rfc id="5925"> - TCP Authentication Option
 <item> <rfc id="6286"> - AS-Wide Unique BGP Identifier
 <item> <rfc id="6608"> - Subcodes for BGP Finite State Machine Error
 <item> <rfc id="6793"> - BGP Support for 4-Octet AS Numbers
@@ -2942,7 +2943,21 @@ protocol bgp [<name>] {
        check link <switch>;
        bfd <switch>|graceful| { <bfd-options> };
        ttl security <switch>;
+       authentication none|md5|ao;
        password "<text>";
+       keys {
+               key {
+                       id <number>;
+                       send id <number>;
+                       recv id <number>;
+                       secret "<text>"|<bytestring>;
+                       algorithm ( hmac md5 | hmac sha1 | hmac sha224 | hmac sha256 |
+                                   hmac sha384 | hmac sha512 | cmac aes128 );
+                       preferred;
+                       deprecated;
+               };
+               ...
+       };
        setkey <switch>;
        passive <switch>;
        confederation <number>;
@@ -3120,6 +3135,16 @@ protocol bgp [<name>] {
        Note that full (ICMP protection, for example) <rfc id="5082"> support is
        provided by Linux only. Default: disabled.
 
+       <tag><label id="bgp-authentication">authentication none|md5|ao</tag>
+       Selects authentication method to be used. <cf/none/ means that the BGP
+       session is not authenticated at all. <cf/md5/ means that the TCP MD5
+       authentication of BGP sessions (<rfc id="2385">) is used, in that case
+       the option <ref id="bgp-password" name="password"> is used to specify
+       the (single) password. Finally, <cf/ao/ means to use TCP Authentication
+       Option (TCP-AO, <rfc id="5925">), allowing multiple keys and different
+       cryptographic algorithms. These are specified using the option
+       <ref id="bgp-keys" name="keys">. Default: none.
+
        <tag><label id="bgp-password">password "<m/text/"</tag>
        Use this password for MD5 authentication of BGP sessions (<rfc id="2385">). When
        used on BSD systems, see also <cf/setkey/ option below. Default: no
@@ -3136,37 +3161,53 @@ protocol bgp [<name>] {
        set manually by an external utility on NetBSD and OpenBSD. Default:
        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.
+       <tag><label id="bgp-keys">keys { key { [<m/.../] }; [<m/.../] }</tag>
+       Define a set of cryptographic keys that are used for TCP-AO
+       authentication of BGP sessions (<rfc id="5925">). Each key has a
+       configuration block with its own sub-options ([<cf/send/ | <cf/recv/]
+       <cf/id/, <cf/secret/, <cf/algorithm/, <cf/preferred/, <cf/deprecated/).
+
+       TCP-AO key has two IDs - for outgoing and incoming direction (Send /
+       Recv ID). Among keys on one protocol all Send IDs must be unique and all
+       Recv IDs must be unique. They must be in range 0-255 and they can be set
+       independently with key options <cf/send id/ and <cf/recv id/, or
+       together with option <cf/id/. Note that specifying these IDs is
+       mandatory.
 
-       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.
+       Of course, TCP-AO key contains a shared secret key. It is specified by
+       the option <cf/secret/ as a text string or as a sequence of hexadecimal
+       digit pairs (<ref id="type-bytestring" name="bytestring">).
 
-       Editing existing keys (except of marking them "required" or "deprecated")
-       is not recommended and leads to restarting protocol.
+       Used cryptographic algorithm can be specified for each key with the
+       option <cf/algorithm/. Possible values are: <cf/hmac md5/, <cf/hmac sha1/,
+       <cf/hmac sha224/, <cf/hmac sha256/, <cf/hmac sha384/, <cf/hmac sha512/,
+       and <cf/cmac aes128/. Default value is <cf/hmac sha1/.
+
+       When multiple keys are available, BIRD selects one to advertise as RNext
+       key (the key it prefers to be used to sign incoming traffic). Keys
+       marked as <cf/preferred/ are selected before unmarked keys, while keys
+       marked as <cf/deprecated/ are never selected (but still could be used
+       when the other side asks for them). Therefore, there must be always at
+       least one non-deprecated key.
+
+       Currently, only the selected key is used during the initial handshake of
+       session establishment (and therefore must be known by the other side).
+       This may change in the future.
+
+       It is possible to add, remove, or modify keys during reconfiguration
+       without breaking the BGP session. The recommended way is to refrain from
+       removing a key that is in active use (as reported by <cf/Current key/
+       and <cf/RNext key/ in <cf/show protocols all/), instead marking the key
+       as <cf/deprecated/ on both sides of the session. It is possible to
+       remove an active key directly, BIRD would forcibly switch to another key
+       (as long as there is a non-deprecated key that is not added, removed or
+       modified during this reconfiguration). This is not recommended as it
+       skips the proper key change mechanism and may switch to a key that is
+       not available to the other side.
+
+       Modification of existing keys (except of marking them <cf/preferred/ or
+       <cf/deprecated/) is equivalent to removing and then adding them, with
+       the same issues related to removing of active keys.
 
        <tag><label id="bgp-passive">passive <m/switch/</tag>
        Standard BGP behavior is both initiating outgoing connections and
@@ -4049,20 +4090,21 @@ 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 {
+       authentication ao;                   # We use TCP-AO authentication
+       keys {
                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";
-               }
-       }
+                       id 0;
+                       secret "hello321";
+                       algorithm hmac sha256;
+                       preferred;
+               };
+               key {
+                       send id 2;
+                       recv id 1;
+                       secret "bye123";
+                       algorithm cmac aes128;
+               };
+       };
        ipv4 {
                export filter {                      # We use non-trivial export rules
                        if source = RTS_STATIC then { # Export only static routes
index 7e6d5467011f9eb9c6a160d7e4ac62a923935a59..2df64c93cf0d7a8069870020e402165bbf807431 100644 (file)
@@ -50,6 +50,7 @@ typedef union list {                  /* In fact two overlayed nodes */
 #define NODE_NEXT(n) ((void *)((NODE (n))->next))
 #define NODE_VALID(n) ((NODE (n))->next)
 #define WALK_LIST(n,list) for(n=HEAD(list); NODE_VALID(n); n=NODE_NEXT(n))
+#define WALK_LIST_(t,n,list) for(t *n=HEAD(list); NODE_VALID(n); n=NODE_NEXT(n))
 #define WALK_LIST2(n,nn,list,pos) \
   for(nn=(list).head; NODE_VALID(nn) && (n=SKIP_BACK(typeof(*n),pos,nn)); nn=nn->next)
 #define WALK_LIST_DELSAFE(n,nxt,list) \
index 99a56eedf4183baaf5df3bea51773f611d4bbead..ca15c7c5b748fb70b37fa6c9af21acea064315ba 100644 (file)
--- a/lib/mac.h
+++ b/lib/mac.h
@@ -26,6 +26,7 @@
 #define ALG_BLAKE2S_256                0x08
 #define ALG_BLAKE2B_256                0x09
 #define ALG_BLAKE2B_512                0x0A
+#define ALG_CMAC_AES128_AO     0x0B    /* Not implemented, just for TCP-AO */
 #define ALG_HMAC               0x10
 #define ALG_HMAC_MD5           0x11
 #define ALG_HMAC_SHA1          0x12
index 2a510a9fcb8c827af6d3b6b93dfc890853db4a07..91a4f12cfc4e906add2496cff48b5f8cf383227b 100644 (file)
@@ -38,27 +38,30 @@ struct ssh_sock {
 
 struct ao_key
 {
-  int local_id;
-  int remote_id;
-  const char *cipher;
-  const char *master_key;
-  int required;
+  int send_id;
+  int recv_id;
+  const char *key;
+  uint keylen;
+  int algorithm;
+  int preference;
 };
 
 struct ao_config
 {
   struct ao_key key;
-  struct ao_config *next_key;
+  struct ao_config *next;
 };
 
-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;
+struct ao_info
+{
+  int current_key;
+  int rnext_key;
+  u64 pkt_good;
+  u64 pkt_bad;
 };
 
+#define AO_MAX_KEY_LENGTH      80      /* See sysdep/linux/tcp-ao.h */
+
 typedef struct birdsock {
   resource r;
   pool *pool;                          /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */
@@ -99,12 +102,10 @@ typedef struct birdsock {
   int rcv_ttl;                         /* TTL of last received datagram */
   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 *password;                        /* Password for MD5 authentication (for SK_TCP_ACTIVE) */
+  const struct ao_key **ao_keys_init;  /* Keys for TCP-AO authentication (for SK_TCP_ACTIVE) */
+  int ao_keys_num;                     /* Number of keys in ao_keys_init */
+  // int use_ao;                       /* This is the only reliable flag saying whether the socket uses TCP-AO */
   const char *err;                     /* Error message */
   struct ssh_sock *ssh;                        /* Used in SK_SSH */
 } sock;
@@ -121,6 +122,7 @@ void sk_set_rbsize(sock *s, uint val);      /* Resize RX buffer */
 void sk_set_tbsize(sock *s, uint val); /* Resize TX buffer, keeping content */
 void sk_set_tbuf(sock *s, void *tbuf); /* Switch TX buffer, NULL-> return to internal */
 void sk_dump_all(struct dump_request *);
+void sk_dump_ao_all(struct dump_request *);
 
 int sk_is_ipv4(sock *s);               /* True if socket is IPv4 */
 int sk_is_ipv6(sock *s);               /* True if socket is IPv6 */
@@ -136,15 +138,16 @@ 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);
+bool tcp_ao_alg_known(int algorithm);
+int sk_get_ao_info(sock *s, struct ao_info *val);
+int sk_get_active_ao_keys(sock *s, int *current_key, int *rnext_key);
+int sk_add_ao_key(sock *s, ip_addr prefix, int pxlen, struct iface *ifa, const struct ao_key *key, bool current, bool rnext);
+int sk_delete_ao_key(sock *s, ip_addr prefix, int pxlen, struct iface *ifa, const struct ao_key *key, const struct ao_key *current, const struct ao_key *rnext);
+int sk_set_rnext_ao_key(sock *s, const struct ao_key *key);
+int sk_check_ao_keys(sock *s, const struct ao_key **keys, int num, const char *name);
+void sk_dump_ao_info(sock *s, struct dump_request *dreq);
+void sk_dump_ao_keys(sock *s, struct dump_request *dreq);
+
 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);
index 4e5095bdc79f08ac5da73f40368b22f8982b87fe..36577227e275634685a28bced79277cf366ea66b 100644 (file)
@@ -869,6 +869,8 @@ CF_CLI(DUMP RESOURCES, text,, [[Dump all allocated resource]])
 { cmd_dump_file(this_cli, $3, "resources", resource_dump); } ;
 CF_CLI(DUMP SOCKETS, text,, [[Dump open sockets]])
 { cmd_dump_file(this_cli, $3, "sockets", sk_dump_all); } ;
+CF_CLI(DUMP AO KEYS, text,, [[Dump TCP-AO keys on sockets ]])
+{ cmd_dump_file(this_cli, $4, "AO keys", sk_dump_ao_all); } ;
 CF_CLI(DUMP EVENTS, text,, [[Dump event log]])
 { cmd_dump_file(this_cli, $3, "event log", io_log_dump); } ;
 CF_CLI(DUMP INTERFACES, text,, [[Dump interface information]])
index a4523ed5e0a45db1cc8a62092f871b3d0754215c..f1f8140d0aa28d3412a05f99db696dc2a9a0c56f 100644 (file)
@@ -87,6 +87,7 @@
  * RFC 5082 - Generalized TTL Security Mechanism
  * RFC 5492 - Capabilities Advertisement with BGP
  * RFC 5668 - 4-Octet AS Specific BGP Extended Community
+ * RFC 5925 - TCP Authentication Option
  * RFC 6286 - AS-Wide Unique BGP Identifier
  * RFC 6608 - Subcodes for BGP Finite State Machine Error
  * RFC 6793 - BGP Support for 4-Octet AS Numbers
@@ -140,6 +141,7 @@ static void bgp_setup_sk(struct bgp_conn *conn, sock *s);
 static void bgp_send_open(struct bgp_conn *conn);
 static void bgp_update_bfd(struct bgp_proto *p, const struct bfd_options *bfd);
 
+static int bgp_disable_ao_keys(struct bgp_proto *p);
 static int bgp_incoming_connection(sock *sk, uint dummy UNUSED);
 static void bgp_listen_sock_err(sock *sk UNUSED, int err);
 
@@ -232,10 +234,462 @@ bgp_close(struct bgp_proto *p)
   mb_free(bs);
 }
 
+
+/*
+ *     TCP-AO keys
+ */
+
+static struct bgp_ao_key *
+bgp_new_ao_key(struct bgp_proto *p, struct ao_config *cf)
+{
+  struct bgp_ao_key *key = mb_allocz(p->p.pool, sizeof(struct bgp_ao_key));
+
+  key->key = cf->key;
+  add_tail(&p->ao.keys, &key->n);
+
+  return key;
+}
+
+static struct bgp_ao_key *
+bgp_find_ao_key_(list *l, int send_id, int recv_id)
+{
+  WALK_LIST_(struct bgp_ao_key, key, *l)
+    if ((key->key.send_id == send_id) && (key->key.recv_id == recv_id))
+      return key;
+
+  return NULL;
+}
+
+static inline struct bgp_ao_key *
+bgp_find_ao_key(struct bgp_proto *p, int send_id, int recv_id)
+{ return bgp_find_ao_key_(&p->ao.keys, send_id, recv_id); }
+
+static int
+bgp_same_ao_key(struct ao_key *a, struct ao_key *b)
+{
+  return
+    (a->send_id == b->send_id) &&
+    (a->recv_id == b->recv_id) &&
+    (a->algorithm == b->algorithm) &&
+    (a->keylen == b->keylen) &&
+    !memcmp(a->key, b->key, a->keylen);
+}
+
 static inline int
+bgp_sk_add_ao_key(struct bgp_proto *p, sock *sk, struct bgp_ao_key *key)
+{
+  ip_addr prefix = p->cf->remote_ip;
+  int pxlen = -1;
+
+  int rv = sk_add_ao_key(sk, prefix, pxlen, p->cf->iface, &key->key, false, false);
+  if (rv < 0)
+  {
+    sk_log_error(sk, p->p.name);
+    log(L_ERR "%s: Cannot add TCP-AO key %d/%d to BGP %s socket",
+       p->p.name, key->key.send_id, key->key.recv_id,
+       ((sk == p->sock->sk) ? "listening" : "session"));
+  }
+
+  return rv;
+}
+
+static int
+bgp_enable_ao_key(struct bgp_proto *p, struct bgp_ao_key *key)
+{
+  ASSERT(!key->active);
+
+  BGP_TRACE(D_EVENTS, "Adding TCP-AO key %d/%d", key->key.send_id, key->key.recv_id);
+
+  /* Handle listening socket */
+  if (bgp_sk_add_ao_key(p, p->sock->sk, key) < 0)
+  {
+    key->failed = 1;
+    return -1;
+  }
+
+  key->active = 1;
+
+  /* Handle incoming socket */
+  if (p->incoming_conn.sk)
+    if (bgp_sk_add_ao_key(p, p->incoming_conn.sk, key) < 0)
+      return -1;
+
+  /* Handle outgoing socket */
+  if (p->outgoing_conn.sk)
+    if (bgp_sk_add_ao_key(p, p->outgoing_conn.sk, key) < 0)
+      return -1;
+
+  return 0;
+}
+
+struct bgp_active_keys {
+  int in_current, in_rnext;
+  int out_current, out_rnext;
+  struct bgp_ao_key *backup;
+};
+
+static int
+bgp_sk_delete_ao_key(struct bgp_proto *p, sock *sk, struct bgp_ao_key *key,
+                    struct bgp_ao_key *backup, int current_key_id, int rnext_key_id)
+{
+  struct ao_key *set_current = NULL, *set_rnext = NULL;
+
+  if ((key->key.send_id == current_key_id) && backup)
+  {
+    log(L_WARN "%s: Deleting TCP-AO Current key %d/%d, setting Current key to %d/%d",
+       p->p.name, key->key.send_id, key->key.recv_id, backup->key.send_id, backup->key.recv_id);
+
+    set_current = &backup->key;
+  }
+
+  if ((key->key.recv_id == rnext_key_id) && backup)
+  {
+    log(L_WARN "%s: Deleting TCP-AO RNext key %d/%d, setting RNext key to %d/%d",
+       p->p.name, key->key.send_id, key->key.recv_id, backup->key.send_id, backup->key.recv_id);
+
+    set_rnext = &backup->key;
+  }
+
+  ip_addr prefix = p->cf->remote_ip;
+  int pxlen = -1;
+
+  int rv = sk_delete_ao_key(sk, prefix, pxlen, p->cf->iface, &key->key, set_current, set_rnext);
+  if (rv < 0)
+  {
+    sk_log_error(sk, p->p.name);
+    log(L_ERR "%s: Cannot delete TCP-AO key %d/%d from BGP %s socket",
+       p->p.name, key->key.send_id, key->key.recv_id,
+       ((sk == p->sock->sk) ? "listening" : "session"));
+  }
+
+  return rv;
+}
+
+static int
+bgp_disable_ao_key(struct bgp_proto *p, struct bgp_ao_key *key, struct bgp_active_keys *info)
+{
+  ASSERT(key->active);
+
+  BGP_TRACE(D_EVENTS, "Deleting TCP-AO key %d/%d", key->key.send_id, key->key.recv_id);
+
+  /* Handle listening socket */
+  if (bgp_sk_delete_ao_key(p, p->sock->sk, key, NULL, -1, -1) < 0)
+    return -1;
+
+  key->active = 0;
+
+  /* Handle incoming socket */
+  if (p->incoming_conn.sk && info)
+    if (bgp_sk_delete_ao_key(p, p->incoming_conn.sk, key, info->backup, info->in_current, info->in_rnext) < 0)
+      return -1;
+
+  /* Handle outgoing socket */
+  if (p->outgoing_conn.sk && info)
+    if (bgp_sk_delete_ao_key(p, p->outgoing_conn.sk, key, info->backup, info->out_current, info->out_rnext) < 0)
+      return -1;
+
+  return 0;
+}
+
+static int
+bgp_remove_ao_key(struct bgp_proto *p, struct bgp_ao_key *key, struct bgp_active_keys *info)
+{
+  ASSERT(key != p->ao.best_key);
+
+  if (key->active)
+    if (bgp_disable_ao_key(p, key, info) < 0)
+      return -1;
+
+  rem_node(&key->n);
+  mb_free(key);
+  return 0;
+}
+
+
+static struct bgp_ao_key *
+bgp_select_best_ao_key(struct bgp_proto *p)
+{
+  struct bgp_ao_key *best = NULL;
+
+  WALK_LIST_(struct bgp_ao_key, key, p->ao.keys)
+  {
+    if (!key->active)
+      continue;
+
+    /* Never select deprecated keys */
+    if (key->key.preference < 0)
+      continue;
+
+    if (!best || (best->key.preference < key->key.preference))
+      best = key;
+  }
+
+  return best;
+}
+
+static int
+bgp_sk_set_rnext_ao_key(struct bgp_proto *p, sock *sk, struct bgp_ao_key *key)
+{
+  int rv = sk_set_rnext_ao_key(sk, &key->key);
+  if (rv < 0)
+  {
+    sk_log_error(sk, p->p.name);
+    log(L_ERR "%s: Cannot set TCP-AO key %d/%d as RNext key",
+       p->p.name, key->key.send_id, key->key.recv_id);
+  }
+
+  return rv;
+}
+
+static int
+bgp_update_rnext_ao_key(struct bgp_proto *p)
+{
+  struct bgp_ao_key *best = bgp_select_best_ao_key(p);
+
+  if (!best)
+  {
+    log(L_ERR "%s: No usable TCP-AO key", p->p.name);
+    return -1;
+  }
+
+  if (best == p->ao.best_key)
+    return 0;
+
+  BGP_TRACE(D_EVENTS, "Setting TCP-AO key %d/%d as RNext key", best->key.send_id, best->key.recv_id);
+
+  p->ao.best_key = best;
+
+  /* Handle incoming socket */
+  if (p->incoming_conn.sk)
+    if (bgp_sk_set_rnext_ao_key(p, p->incoming_conn.sk, best) < 0)
+      return -1;
+
+  /* Handle outgoing socket */
+  if (p->outgoing_conn.sk)
+    if (bgp_sk_set_rnext_ao_key(p, p->outgoing_conn.sk, best) < 0)
+      return -1;
+
+  /* Schedule Keepalive to trigger RNext ID exchange */
+  if (p->conn)
+    bgp_schedule_packet(p->conn, NULL, PKT_KEEPALIVE);
+
+  /* RFC 4271 4.4 says that Keepalive messages MUST NOT be sent more frequently
+     than one per second, but since key change is rare, this is harmless. */
+
+  return 0;
+}
+
+
+/**
+ * bgp_enable_ao_keys - Enable TCP-AO keys
+ * @p: BGP instance
+ *
+ * Enable all TCP-AO keys for the listening socket. We accept if some fail in
+ * non-fatal way (e.g. kernel does not support specific algorithm), but there
+ * must be at least one usable (non-deprecated) active key. In case of failure,
+ * we remove all keys, so there is no lasting effect on the listening socket.
+ * Returns: 0 for okay, -1 for failure.
+ */
+static int
+bgp_enable_ao_keys(struct bgp_proto *p)
+{
+  ASSERT(!p->incoming_conn.sk && !p->outgoing_conn.sk);
+
+  WALK_LIST_(struct bgp_ao_key, key, p->ao.keys)
+    if (bgp_enable_ao_key(p, key) < 0)
+      goto fail;
+
+  p->ao.best_key = bgp_select_best_ao_key(p);
+
+  if (!p->ao.best_key)
+  {
+    log(L_ERR "%s: No usable TCP-AO key", p->p.name);
+    goto fail;
+  }
+
+  return 0;
+
+fail:
+  bgp_disable_ao_keys(p);
+  return -1;
+}
+
+/**
+ * bgp_disable_ao_keys - Disable TCP-AO keys
+ * @p: BGP instance
+ *
+ * Disable all TCP-AO keys for the listening socket. We assume there are no
+ * active connection, so no issue with removal of the current key. Errors are
+ * ignored.
+ */
+static int
+bgp_disable_ao_keys(struct bgp_proto *p)
+{
+  ASSERT(!p->incoming_conn.sk && !p->outgoing_conn.sk);
+
+  WALK_LIST_(struct bgp_ao_key, key, p->ao.keys)
+    if (key->active)
+      bgp_disable_ao_key(p, key, NULL);
+
+  return 0;
+}
+
+static int
+bgp_reconfigure_ao_keys(struct bgp_proto *p, const struct bgp_config *cf)
+{
+  /* TCP-AO not used */
+  if (EMPTY_LIST(p->ao.keys) && !cf->ao_keys)
+    return 1;
+
+  /* Cannot enable/disable TCP-AO */
+  if (EMPTY_LIST(p->ao.keys) || !cf->ao_keys)
+    return 0;
+
+  /* Too early, TCP-AO not yet enabled */
+  if (p->start_state == BSS_PREPARE)
+    return 0;
+
+  /* Move existing keys to temporary list */
+  list old_keys;
+  init_list(&old_keys);
+  add_tail_list(&old_keys, &p->ao.keys);
+  init_list(&p->ao.keys);
+
+  /* Clean up the best key */
+  struct bgp_ao_key *old_best = p->ao.best_key;
+  p->ao.best_key = NULL;
+
+  /* Prepare new set of keys */
+  for (struct ao_config *key_cf = cf->ao_keys; key_cf; key_cf = key_cf->next)
+  {
+    struct bgp_ao_key *key = bgp_find_ao_key_(&old_keys, key_cf->key.send_id, key_cf->key.recv_id);
+
+    if (key && bgp_same_ao_key(&key->key, &key_cf->key))
+    {
+      /* Update key ptr and preference */
+      key->key = key_cf->key;
+
+      rem_node(&key->n);
+      add_tail(&p->ao.keys, &key->n);
+
+      if (key == old_best)
+       p->ao.best_key = key;
+
+      continue;
+    }
+
+    bgp_new_ao_key(p, key_cf);
+  }
+
+  /* Remove old keys */
+  if (!EMPTY_LIST(old_keys))
+  {
+    struct bgp_active_keys info = { -1, -1, -1, -1, NULL};
+
+    /* Find current/rnext keys on incoming connection */
+    if (p->incoming_conn.sk)
+      if (sk_get_active_ao_keys(p->incoming_conn.sk, &info.in_current, &info.in_rnext) < -1)
+       sk_log_error(p->incoming_conn.sk, p->p.name);
+
+    /* Find current/rnext keys on outgoing connection */
+    if (p->outgoing_conn.sk)
+      if (sk_get_active_ao_keys(p->outgoing_conn.sk, &info.out_current, &info.out_rnext) < -1)
+       sk_log_error(p->outgoing_conn.sk, p->p.name);
+
+    /*
+     * Select backup key in case of removal of current/rnext key.
+     *
+     * It is possible that we cannot select an intermediate best key (e.g. when
+     * the reconfiguration deprecates the old best key and adds the new one).
+     * That is not necessary bad, we may not even need the backup key anyways.
+     * In this case we use the old best key (ao.best_key) instead even if it may
+     * be deprecated (but not removed).
+     *
+     * If neither one is available, that means we are going to remove rnext key
+     * and we have no intermediate best key to switch to, therefore we fail
+     * later during bgp_remove_ao_key().
+     */
+    info.backup = bgp_select_best_ao_key(p) ?: p->ao.best_key;
+    if (!info.backup)
+      log(L_WARN "%s: No usable backup key", p->p.name);
+
+    struct bgp_ao_key *key, *key2;
+    WALK_LIST_DELSAFE(key, key2, old_keys)
+      bgp_remove_ao_key(p, key, &info);
+
+    /* If some key removals failed */
+    if (!EMPTY_LIST(old_keys))
+      return 0;
+  }
+
+  /* Enable new keys */
+  WALK_LIST_(struct bgp_ao_key, key, p->ao.keys)
+    if (!key->active && !key->failed)
+      if (bgp_enable_ao_key(p, key) < 0)
+       return 0;
+
+  /* Update RNext key */
+  if (bgp_update_rnext_ao_key(p) < 0)
+    return 0;
+
+  return 1;
+}
+
+/**
+ * bgp_list_ao_keys - List active TCP-AO keys
+ * @p: BGP instance
+ * @ao_keys: Returns array of keys
+ * @ao_keys_num: Returns number of keys
+ *
+ * Returns an array of pointers to active TCP-AO keys, for usage with socket
+ * functions. The best key is at the first position. The array is allocated from
+ * the temporary linpool. If there are no keys (or just no best key), the error
+ * is logged and the function fails. Returns: 0 for success, -1 for failure.
+ */
+static int
+bgp_list_ao_keys(struct bgp_proto *p, const struct ao_key ***ao_keys, int *ao_keys_num)
+{
+  int num = 0;
+  WALK_LIST_(const struct bgp_ao_key, key, p->ao.keys)
+    if (key->active)
+      num++;
+
+  const struct bgp_ao_key *best = p->ao.best_key;
+
+  if (!num || !best)
+  {
+    log(L_ERR "%s: No usable TCP-AO key", p->p.name);
+    return -1;
+  }
+
+  const struct ao_key **keys = tmp_alloc(num * sizeof(const struct ao_key *));
+  int i = 0;
+
+  keys[i++] = &best->key;
+  WALK_LIST_(const struct bgp_ao_key, key, p->ao.keys)
+    if (key->active && (key != best) && (i < num))
+      keys[i++] = &key->key;
+
+  *ao_keys = keys;
+  *ao_keys_num = i;
+  return 0;
+}
+
+
+
+
+static int
 bgp_setup_auth(struct bgp_proto *p, int enable)
 {
-  if (p->cf->password || p->cf->ao_key)
+  if (p->cf->auth_type == BGP_AUTH_AO)
+  {
+    if (enable)
+      return bgp_enable_ao_keys(p);
+    else
+      return bgp_disable_ao_keys(p);
+  }
+
+  if (p->cf->auth_type == BGP_AUTH_MD5)
   {
     ip_addr prefix = p->cf->remote_ip;
     int pxlen = -1;
@@ -245,45 +699,18 @@ 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 = 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,
-                            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,
+    int rv = sk_set_md5_auth(p->sock->sk,
                             p->cf->local_ip, prefix, pxlen, p->cf->iface,
-                            p->cf->password, p->cf->setkey);
+                            enable ? p->cf->password : NULL, p->cf->setkey);
 
     if (rv < 0)
       sk_log_error(p->sock->sk, p->p.name);
 
     return rv;
   }
-  else
-    return 0;
+
+  return 0;
 }
 
 static inline struct bgp_channel *
@@ -332,7 +759,7 @@ bgp_initiate(struct bgp_proto *p)
   { err_val = BEM_NO_SOCKET; goto err1; }
 
   if (bgp_setup_auth(p, 1) < 0)
-  { err_val = BEM_INVALID_MD5; goto err2; }
+  { err_val = BEM_INVALID_AUTH; goto err2; }
 
   if (p->cf->bfd)
     bgp_update_bfd(p, p->cf->bfd);
@@ -1174,8 +1601,6 @@ bgp_connect(struct bgp_proto *p)  /* Enter Connect state and start establishing c
   s->rbsize = p->cf->enable_extended_messages ? BGP_RX_BUFFER_EXT_SIZE : BGP_RX_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->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",
@@ -1185,6 +1610,13 @@ bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing c
   bgp_setup_sk(conn, s);
   bgp_conn_set_state(conn, BS_CONNECT);
 
+  if (p->cf->auth_type == BGP_AUTH_MD5)
+    s->password = p->cf->password;
+
+  if (p->cf->auth_type == BGP_AUTH_AO)
+    if (bgp_list_ao_keys(p, &s->ao_keys_init, &s->ao_keys_num) < 0)
+      goto err2;
+
   if (sk_open(s) < 0)
     goto err;
 
@@ -1193,12 +1625,16 @@ bgp_connect(struct bgp_proto *p)        /* Enter Connect state and start establishing c
     if (sk_set_min_ttl(s, 256 - hops) < 0)
       goto err;
 
+  s->ao_keys_num = 0;
+  s->ao_keys_init = NULL;
+
   DBG("BGP: Waiting for connect success\n");
   bgp_start_timer(conn->connect_timer, p->cf->connect_retry_time);
   return;
 
 err:
   sk_log_error(s, p->p.name);
+err2:
   bgp_sock_err(s, 0);
   return;
 }
@@ -1265,12 +1701,16 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
     rfree(sk);
     return 0;
   }
-  if (p->cf->ao_key)
+
+  if (!EMPTY_LIST(p->ao.keys))
   {
-    if (get_current_key_id(sk->fd) == -1)
+    int current = -1, rnext = -1;
+    sk_get_active_ao_keys(sk, &current, &rnext);
+
+    if (current < 0)
     {
-      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);
+      log(L_WARN "%s: Connection from address %I%J (port %d) has no TCP-AO key",
+          p->p.name, sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL, sk->dport);
       rfree(sk);
       return 0;
     }
@@ -1317,26 +1757,21 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
     if (sk_set_min_ttl(sk, 256 - hops) < 0)
       goto err;
 
-  if (p->ao_key)
+  if (!EMPTY_LIST(p->ao.keys))
   {
-    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;
-        }
-      }
-    }
+    const struct ao_key **ao_keys;
+    int ao_keys_num;
+
+    if (bgp_list_ao_keys(p, &ao_keys, &ao_keys_num) < 0)
+      goto err2;
+
+    if (sk_check_ao_keys(sk, ao_keys, ao_keys_num, p->p.name) < 0)
+      goto err2;
+
+    if (sk_set_rnext_ao_key(sk, ao_keys[0]) < 0)
+      goto err;
   }
+
   if (p->cf->enable_extended_messages)
   {
     sk->rbsize = BGP_RX_BUFFER_EXT_SIZE;
@@ -1361,6 +1796,7 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
 
 err:
   sk_log_error(sk, p->p.name);
+err2:
   log(L_ERR "%s: Incoming connection aborted", p->p.name);
   rfree(sk);
   return 0;
@@ -1666,6 +2102,12 @@ bgp_start(struct proto *P)
   p->remote_id = 0;
   p->link_addr = IPA_NONE;
 
+  /* Initialize TCP-AO keys */
+  init_list(&p->ao.keys);
+  if (cf->auth_type == BGP_AUTH_AO)
+    for (struct ao_config *key_cf = cf->ao_keys; key_cf; key_cf = key_cf->next)
+      bgp_new_ao_key(p, key_cf);
+
   proto_setup_mpls_map(P, RTS_BGP, 1);
 
   /* Lock all channels when in GR recovery mode */
@@ -1821,20 +2263,6 @@ 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;
@@ -2176,6 +2604,18 @@ bgp_postconfig(struct proto_config *CF)
     cf_error("Min long-lived stale time (%u) exceeds max long-lived stale time (%u)",
             cf->min_llgr_time, cf->max_llgr_time);
 
+  /* Legacy case: password option without authentication option */
+  if ((cf->auth_type == BGP_AUTH_NONE) && cf->password && !cf->ao_keys)
+  {
+    cf_warn("Missing authentication option, assuming MD5");
+    cf->auth_type = BGP_AUTH_MD5;
+  }
+
+  if ((cf->auth_type == BGP_AUTH_MD5) != !!cf->password)
+    cf_error("MD5 authentication and password option should be used together");
+
+  if ((cf->auth_type == BGP_AUTH_AO) != !!cf->ao_keys)
+    cf_error("AO authentication and keys option should be used together");
 
   struct bgp_channel_config *cc;
   BGP_CF_WALK_CHANNELS(cf, cc)
@@ -2279,146 +2719,6 @@ 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)
 {
@@ -2432,18 +2732,19 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF)
   if (bstrcmp(proto_get_hostname(CF), p->hostname))
     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)
     && (old->dynamic_name_digits == new->dynamic_name_digits);
 
+  /* Reconfigure TCP-AP */
+  same = same && bgp_reconfigure_ao_keys(p, new);
+
   /* FIXME: Move channel reconfiguration to generic protocol code ? */
   struct channel *C, *C2;
   struct bgp_channel_config *cc;
@@ -2630,7 +2931,7 @@ bgp_store_error(struct bgp_proto *p, struct bgp_conn *c, u8 class, u32 code)
 
 static char *bgp_state_names[] = { "Idle", "Connect", "Active", "OpenSent", "OpenConfirm", "Established", "Close" };
 static char *bgp_err_classes[] = { "", "Error: ", "Socket: ", "Received: ", "BGP Error: ", "Automatic shutdown: ", "" };
-static char *bgp_misc_errors[] = { "", "Neighbor lost", "Invalid next hop", "Kernel MD5 auth failed", "No listening socket", "Link down", "BFD session down", "Graceful restart" };
+static char *bgp_misc_errors[] = { "", "Neighbor lost", "Invalid next hop", "Authentication failed", "No listening socket", "Link down", "BFD session down", "Graceful restart" };
 static char *bgp_auto_errors[] = { "", "Route limit exceeded" };
 static char *bgp_gr_states[] = { "None", "Regular", "Long-lived" };
 
@@ -2905,15 +3206,16 @@ bgp_show_proto_info(struct proto *P)
     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)
+    if (!EMPTY_LIST(p->ao.keys))
     {
-      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]);
+      struct ao_info info;
+      sk_get_ao_info(p->conn->sk, &info);
+
+      cli_msg(-1006, "    TCP-AO:");
+      cli_msg(-1006, "      Current key:    %i", info.current_key);
+      cli_msg(-1006, "      RNext key:      %i", info.rnext_key);
+      cli_msg(-1006, "      Good packets:   %lu", info.pkt_good);
+      cli_msg(-1006, "      Bad packets:    %lu", info.pkt_bad);
     }
   }
 
index 30374626f7e9a9ec462e2bac268ec13531b5fd0f..515ef2754c74f26fdee6c84f0070f706775039ca 100644 (file)
@@ -114,6 +114,7 @@ struct bgp_config {
   int enforce_first_as;                        /* Enable check for neighbor AS as first AS in AS_PATH */
   int gr_mode;                         /* Graceful restart mode (BGP_GR_*) */
   int llgr_mode;                       /* Long-lived graceful restart mode (BGP_LLGR_*) */
+  int auth_type;                       /* Authentication type (BGP_AUTH_*) */
   int setkey;                          /* Set MD5 password to system SA/SP database */
   u8  local_role;                      /* Set peering role with neighbor [RFC 9234] */
   int require_roles;                   /* Require configured roles on both sides */
@@ -139,8 +140,9 @@ struct bgp_config {
   unsigned disable_after_error;                /* Disable the protocol when error is detected */
   u32 disable_after_cease;             /* Disable it when cease is received, bitfield */
 
+  /* Options below must be compared separately in bgp_reconfigure() */
   const char *password;                        /* Password used for MD5 authentication */
-  struct ao_config *ao_key;            /* Keys for tcp ao authentication */
+  struct ao_config *ao_keys;           /* 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 */
@@ -193,6 +195,10 @@ struct bgp_channel_config {
 #define BGP_PT_INTERNAL                1
 #define BGP_PT_EXTERNAL                2
 
+#define BGP_AUTH_NONE          0
+#define BGP_AUTH_MD5           1
+#define BGP_AUTH_AO            2
+
 #define BGP_ROLE_UNDEFINED     255
 #define BGP_ROLE_PROVIDER      0
 #define BGP_ROLE_RS_SERVER     1
@@ -289,6 +295,18 @@ struct bgp_caps {
   for (ac = caps->af_data; ac < &caps->af_data[caps->af_count]; ac++)
 
 
+struct bgp_ao_key {
+  node n;                              /* Node in bgp_ao_state.keys */
+  struct ao_key key;
+  u32 active:1;
+  u32 failed:1;
+};
+
+struct bgp_ao_state {
+  list keys;                           /* List of TCP-AO keys (struct bgp_ao_key) */
+  struct bgp_ao_key *best_key;
+};
+
 struct bgp_socket {
   node n;                              /* Node in global bgp_sockets */
   sock *sk;                            /* Real listening socket */
@@ -366,9 +384,9 @@ 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_ao_state ao;
   struct bgp_stats stats;              /* BGP statistics */
   btime last_established;              /* Last time of enter/leave of established state */
   btime last_rx_update;                        /* Last time of RX update */
@@ -807,7 +825,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
 
 #define BEM_NEIGHBOR_LOST      1
 #define BEM_INVALID_NEXT_HOP   2
-#define BEM_INVALID_MD5                3       /* MD5 authentication kernel request failed (possibly not supported) */
+#define BEM_INVALID_AUTH       3       /* Authentication kernel request failed (possibly not supported) */
 #define BEM_NO_SOCKET          4
 #define BEM_LINK_DOWN          5
 #define BEM_BFD_DOWN           6
index 8a2f0309c4d96558af808ed56d5ee6a94e7d8177..7cc02d52a102a0958ec53c7d2b94dfb2514b5e09 100644 (file)
@@ -15,6 +15,9 @@ CF_DEFINES
 #define BGP_CFG ((struct bgp_config *) this_proto)
 #define BGP_CC ((struct bgp_channel_config *) this_channel)
 
+static struct ao_config *this_ao_key;
+static struct ao_config **next_ao_key;
+
 CF_DECLS
 
 CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
@@ -32,18 +35,19 @@ 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, MIN, MAX,
-       AUTHENTICATE, MANUAL, KEY, MASTER, DEPRECATED, REQUIRED, CIPHER, REMOTE,
-       FORMAT, NATIVE, SINGLE, DOUBLE)
+       RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX,
+       AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE)
+
+CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128)
 
-%type <i> bgp_nh bgp_llnh
+%type <i> bgp_nh bgp_llnh bgp_auth_type bgp_role_name tcp_ao_algorithm
 %type <i32> bgp_afi
 
 CF_KEYWORDS(CEASE, PREFIX, LIMIT, HIT, ADMINISTRATIVE, SHUTDOWN, RESET, PEER,
        CONFIGURATION, CHANGE, DECONFIGURED, CONNECTION, REJECTED, COLLISION,
        OUT, OF, RESOURCES, ASPA_CHECK_UPSTREAM, ASPA_CHECK_DOWNSTREAM)
 
-%type<i> bgp_cease_mask bgp_cease_list bgp_cease_flag bgp_role_name
+%type<i> bgp_cease_mask bgp_cease_list bgp_cease_flag
 
 CF_GRAMMAR
 
@@ -124,6 +128,12 @@ bgp_cease_flag:
  | OUT OF RESOURCES            { $$ = 1 << 8; }
  ;
 
+bgp_auth_type:
+   NONE                { $$ = BGP_AUTH_NONE; }
+ | MD5         { $$ = BGP_AUTH_MD5; }
+ | AO          { $$ = BGP_AUTH_AO; }
+ ;
+
 bgp_role_name:
    PEER      { $$ = BGP_ROLE_PEER; }
  | PROVIDER  { $$ = BGP_ROLE_PROVIDER; }
@@ -207,8 +217,9 @@ 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 ';' { log("%s", $3); BGP_CFG->password = $3; }
- | bgp_proto AUTHENTICATE MANUAL '{' ao_keys '}' tcp_ao_end
+ | bgp_proto AUTHENTICATION bgp_auth_type ';' { BGP_CFG->auth_type = $3; }
+ | bgp_proto KEYS tcp_ao_key_list ';' tcp_ao_end
+ | bgp_proto PASSWORD text ';' { BGP_CFG->password = $3; }
  | 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; }
@@ -253,93 +264,116 @@ bgp_afi:
  | FLOW6               { $$ = BGP_AF_FLOW6; }
  ;
 
-ao_keys:
-   KEY '{' ao_first_item  ao_key '}'
- | KEY '{' ao_first_item ao_key '}' ao_keys
- ;
+tcp_ao_key_start: KEY {
+  this_ao_key = cfg_allocz(sizeof(struct ao_config));
+  *next_ao_key = this_ao_key;
 next_ao_key = &(this_ao_key->next);
 
-ao_key:
-  ao_item
- | ao_item ao_key
- ;
+  this_ao_key->key.send_id = -1;
+  this_ao_key->key.recv_id = -1;
+  this_ao_key->key.algorithm = ALG_HMAC_SHA1;
+}
 
-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;
+tcp_ao_key_opt:
+   ID expr ';' {
+     if ($2 > 255)
+       cf_error("Key ID must be in range 0-255");
+     this_ao_key->key.send_id = $2;
+     this_ao_key->key.recv_id = $2;
+   }
+ | SEND ID expr ';' {
+     if ($3 > 255)
+       cf_error("Send ID must be in range 0-255");
+     this_ao_key->key.send_id = $3;
+   }
+ | RECV ID expr ';' {
+     if ($3 > 255)
+       cf_error("Recv ID must be in range 0-255");
+     this_ao_key->key.recv_id = $3;
+   }
+ | ALGORITHM tcp_ao_algorithm ';' {
+     if (!tcp_ao_alg_known($2))
+       cf_error("Unknown algorithm for TCP-AO");
+     this_ao_key->key.algorithm = $2;
    }
- | MASTER KEY text ';' {
-     char *k = cfg_alloc(strlen($3)+1);
-     memcpy(k,  $3, strlen($3)+1);
-     BGP_CFG->ao_key->key.master_key = k;
+ | SECRET bytestring_text ';' {
+     if ($2.type == T_BYTESTRING)
+     {
+       this_ao_key->key.key = $2.val.bs->data;
+       this_ao_key->key.keylen = $2.val.bs->length;
+     }
+     else if ($2.type == T_STRING)
+     {
+       this_ao_key->key.key = $2.val.s;
+       this_ao_key->key.keylen = strlen($2.val.s);
+     }
+     else bug("Bad bytestring_text");
+
+     if (this_ao_key->key.keylen > AO_MAX_KEY_LENGTH)
+       cf_error("TCP-AO secret too long");
    }
  | 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);
+     if (this_ao_key->key.preference > 0)
+       cf_error("Key cannot be both preferred and deprecated");
+     this_ao_key->key.preference = -1;
    }
- | 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);
+ | PREFERRED ';' {
+     if (this_ao_key->key.preference < 0)
+       cf_error("Key cannot be both preferred and deprecated");
+     this_ao_key->key.preference = 1;
    }
  ;
 
+tcp_ao_algorithm:
+   password_algorithm { $$ = $1; }
+ | CMAC AES128 { $$ = ALG_CMAC_AES128_AO; }
+ ;
+
+tcp_ao_key_opts:
+   /* empty */
+ | tcp_ao_key_opts tcp_ao_key_opt
+ ;
+
+tcp_ao_key_end: {
+  struct ao_config *key = this_ao_key;
+  this_ao_key = NULL;
+
+  if (key->key.send_id == -1)
+    cf_error("No key Send ID");
+  if (key->key.recv_id == -1)
+    cf_error("No key Recv ID");
+  if (!key->key.key || !key->key.keylen)
+    cf_error("No secret defined for key %i/%i", key->key.send_id, key->key.recv_id);
+}
+
+tcp_ao_key: tcp_ao_key_start '{' tcp_ao_key_opts '}' tcp_ao_key_end;
+
+tcp_ao_keys:
+   /* empty */
+ | tcp_ao_keys tcp_ao_key ';' { }
+ ;
+
+tcp_ao_key_list:
+   '{' { next_ao_key = &(BGP_CFG->ao_keys); } tcp_ao_keys '}' { next_ao_key = NULL; };
+ ;
+
 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)
+  char used_aos_id_send[256];
+  char used_aos_id_recv[256];
+  memset(used_aos_id_send, 0, sizeof(char)*256);
+  memset(used_aos_id_recv, 0, sizeof(char)*256);
+
+  for (struct ao_config *key = BGP_CFG->ao_keys; key; key = key->next)
   {
-    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 (used_aos_id_send[key->key.send_id])
+      cf_error("Multiple keys with Send ID %i", key->key.send_id);
+    used_aos_id_send[key->key.send_id] = 1;
+
+    if (used_aos_id_recv[key->key.recv_id])
+      cf_error("Multiple keys with Recv ID %i", key->key.recv_id);
+    used_aos_id_recv[key->key.recv_id] = 1;
   }
-  if (required_found == 0)
-    cf_error("TCP AO: Missing 'REQUIRED'. Which key should be used?");
 }
 
 bgp_channel_start: bgp_afi
index 289b9c3e3d6158709eb1d3e55982c080c0d04e79..eb112c5be37d9b5fc715ab063b19df5061022a5e 100644 (file)
@@ -3507,32 +3507,6 @@ 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
@@ -3547,24 +3521,6 @@ 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;
index c20c7f99091ec112d0cd92cd092e560e1ba58d1f..be65630bc119fdfe5fe7f2d7b1fbe998cf8e889b 100644 (file)
@@ -224,6 +224,69 @@ sk_prepare_ip_header(sock *s, void *hdr, int dlen)
 }
 
 
+/*
+ *     TCP-AO (not supported)
+ */
+
+int
+sk_get_ao_info(sock *s, struct ao_info *val)
+{
+  ERR_MSG("TCP-AO not supported");
+}
+
+int
+sk_get_active_ao_keys(sock *s, int *current_key, int *rnext_key)
+{
+  ERR_MSG("TCP-AO not supported");
+}
+
+bool
+tcp_ao_alg_known(int algorithm)
+{
+  return 0;
+}
+
+int
+sk_add_ao_key(sock *s, ip_addr prefix, int pxlen, struct iface *ifa, const struct ao_key *key, bool current, bool rnext)
+{
+  ERR_MSG("TCP-AO not supported");
+}
+
+int
+sk_delete_ao_key(sock *s, ip_addr prefix, int pxlen, struct iface *ifa, const struct ao_key *key, const struct ao_key *current, const struct ao_key *rnext)
+{
+  ERR_MSG("TCP-AO not supported");
+}
+
+int
+sk_set_rnext_ao_key(sock *s, const struct ao_key *key)
+{
+  ERR_MSG("TCP-AO not supported");
+}
+
+int
+sk_check_ao_keys(sock *s, const struct ao_key **keys, int num, const char *name)
+{
+  errno = 0;
+  s->err = "TCP-AO not supported";
+  sk_log_error(s, name);
+
+  return -1;
+}
+
+void
+sk_dump_ao_info(sock *s, struct dump_request *dreq)
+{
+  RDUMP("TCP-AO not supported\n");
+}
+
+void
+sk_dump_ao_keys(sock *s, struct dump_request *dreq)
+{
+  RDUMP("TCP-AO not supported\n");
+}
+
+
 /*
  *     Miscellaneous BSD socket syscalls
  */
index ca4859518053f70d1f5c01452f76cb26d87e9c1f..ad5399a73a50be2d09bb5cdda59b0394fd3d31fe 100644 (file)
@@ -165,295 +165,386 @@ sk_prepare_cmsgs4(sock *s, struct msghdr *msg, void *cbuf, size_t cbuflen)
 
 
 /*
- *     Miscellaneous Linux socket syscalls
+ *     TCP-AO
  */
 
 int
-sk_set_md5_auth(sock *s, ip_addr local UNUSED, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int setkey UNUSED)
+sk_get_ao_info(sock *s, struct ao_info *val)
 {
-  struct tcp_md5sig_ext md5;
+  struct tcp_ao_info_opt_ext info = {};
+  socklen_t len = sizeof(info);
+
+  if (getsockopt(s->fd, IPPROTO_TCP, TCP_AO_INFO, &info, &len) < 0)
+    ERR("TCP_AO_INFO");
+
+  *val = (struct ao_info) {
+    .current_key = info.set_current ? info.current_key : -1,
+    .rnext_key = info.set_rnext ? info.rnext : -1,
+    .pkt_good = info.pkt_good,
+    .pkt_bad = info.pkt_bad,
+  };
+  return 0;
+}
 
-  memset(&md5, 0, sizeof(md5));
-  sockaddr_fill((sockaddr *) &md5.tcpm_addr, s->af, remote, ifa, 0);
+int
+sk_get_active_ao_keys(sock *s, int *current_key, int *rnext_key)
+{
+  struct tcp_ao_info_opt_ext info = {};
+  socklen_t len = sizeof(info);
 
-  if (passwd)
-  {
-    int len = strlen(passwd);
+  if (getsockopt(s->fd, IPPROTO_TCP, TCP_AO_INFO, &info, &len) < 0)
+    ERR("TCP_AO_INFO");
 
-    if (len > TCP_MD5SIG_MAXKEYLEN)
-      ERR_MSG("The password for TCP MD5 Signature is too long");
+  *current_key = info.set_current ? info.current_key : -1;
+  *rnext_key = info.set_rnext ? info.rnext : -1;
+  return 0;
+}
 
-    md5.tcpm_keylen = len;
-    memcpy(&md5.tcpm_key, passwd, len);
-  }
+static int
+sk_get_ao_keys_num(sock *s)
+{
+  struct tcp_ao_getsockopt_ext keys = {
+    .nkeys = 1,
+    .get_all = 1,
+  };
+  socklen_t len = sizeof(keys);
 
-  if (pxlen < 0)
-  {
-    if (setsockopt(s->fd, SOL_TCP, TCP_MD5SIG, &md5, sizeof(md5)) < 0)
-      if (errno == ENOPROTOOPT)
-       ERR_MSG("Kernel does not support TCP MD5 signatures");
-      else
-       ERR("TCP_MD5SIG");
-  }
-  else
-  {
-    md5.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX;
-    md5.tcpm_prefixlen = pxlen;
+  if (getsockopt(s->fd, IPPROTO_TCP, TCP_AO_GET_KEYS, &keys, &len) < 0)
+    ERR("TCP_AO_GET_KEYS");
 
-    if (setsockopt(s->fd, SOL_TCP, TCP_MD5SIG_EXT, &md5, sizeof(md5)) < 0)
-    {
-      if (errno == ENOPROTOOPT)
-       ERR_MSG("Kernel does not support extended TCP MD5 signatures");
-      else
-       ERR("TCP_MD5SIG_EXT");
-    }
-  }
+  return keys.nkeys;
+}
+
+static int
+sk_get_ao_keys(sock *s, struct tcp_ao_getsockopt_ext **keys, int *keys_num)
+{
+  *keys = NULL;
+  *keys_num = 0;
 
+  int num = sk_get_ao_keys_num(s);
+  if (num < 1)
+    return num;
+
+  *keys = tmp_allocz(num * sizeof(struct tcp_ao_getsockopt_ext));
+  (*keys)[0].nkeys = num;
+  (*keys)[0].get_all = 1;
+
+  /* len should be just size of one struct. See kernel net/ipv4/tcp_ao.c line 2165 */
+  socklen_t len = sizeof(struct tcp_ao_getsockopt_ext);
+
+  if (getsockopt(s->fd, IPPROTO_TCP, TCP_AO_GET_KEYS, *keys, &len) < 0)
+    ERR("TCP_AO_GET_KEYS");
+
+  *keys_num = (*keys)[0].nkeys;
   return 0;
 }
 
-void log_tcp_ao_info(int sock_fd)
+static const char * const tcp_ao_alg_names[] = {
+  [ALG_CMAC_AES128_AO] = "cmac(aes128)",
+  [ALG_HMAC_MD5]       = "hmac(md5)",
+  [ALG_HMAC_SHA1]      = "hmac(sha1)",
+  [ALG_HMAC_SHA224]    = "hmac(sha224)",
+  [ALG_HMAC_SHA256]    = "hmac(sha256)",
+  [ALG_HMAC_SHA384]    = "hmac(sha384)",
+  [ALG_HMAC_SHA512]    = "hmac(sha512)",
+};
+
+bool
+tcp_ao_alg_known(int algorithm)
+{
+  return (algorithm > 0) && (algorithm < (int) ARRAY_SIZE(tcp_ao_alg_names)) && tcp_ao_alg_names[algorithm];
+}
+
+
+/**
+ * sk_add_ao_key - Add TCP-AO key to the socket
+ * @s: Socket
+ * @prefix: Prefix
+ * @pxlen: Prefix length (or -1 for max)
+ * @ifa: Interface (for IPv6 link-local prefix)
+ * @key: TCP-AO key to be added
+ * @current: Set the new key as current key
+ * @rnext: Set the new key as rnext key
+ *
+ * The function adds the TCP-AO key @key to the kernel list of TCP-AO keys
+ * associated with the socket. There can be multiple sets of keys for different
+ * peers (for a listening socket), therefore the key is accompanied with the
+ * relevant prefix (consists of @prefix, @pxlen, and @ifa).
+ *
+ * Result: 0 when successful, -1 for an error.
+ */
+int
+sk_add_ao_key(sock *s, ip_addr prefix, int pxlen, struct iface *ifa, const struct ao_key *key, bool current, bool rnext)
 {
-  struct tcp_ao_info_opt_ext tmp;
-  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
-  socklen_t len = sizeof(tmp);
+  if (pxlen < 0)
+    pxlen = (s->af == AF_INET) ? IP4_MAX_PREFIX_LENGTH : IP6_MAX_PREFIX_LENGTH;
+
+  struct tcp_ao_add_ext ao = {};
+  sockaddr_fill((sockaddr *) &ao.addr, s->af, prefix, ifa, 0);
+  ao.prefix = pxlen;
+  ao.sndid = key->send_id;
+  ao.rcvid = key->recv_id;
+  ao.set_current = current;
+  ao.set_rnext = rnext;
+
+  if (!tcp_ao_alg_known(key->algorithm))
+    ERR_MSG("Unknown TCP-AO algorithm");
 
-  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
+  strncpy(ao.alg_name, tcp_ao_alg_names[key->algorithm], sizeof(ao.alg_name));
+  ao.keylen = key->keylen;
+  memcpy(ao.key, key->key, MIN(key->keylen, TCP_AO_MAXKEYLEN_));
+
+  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_ADD_KEY, &ao, sizeof(ao)) < 0)
   {
-    log(L_WARN "TCP AO: log tcp ao info failed with err code %i", errno);
-    return;
+    if (errno == ENOPROTOOPT)
+      ERR_MSG("Kernel does not support TCP-AO signatures");
+    else
+      ERR("TCP_AO_ADD_KEY");
   }
-  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);
+
+  return 0;
 }
 
-int get_current_key_id(int sock_fd)
+/**
+ * sk_delete_ao_key - Delete TCP-AO key from the socket
+ * @s: Socket
+ * @prefix: Prefix
+ * @pxlen: Prefix length (or -1 for max)
+ * @ifa: Interface (for IPv6 link-local prefix)
+ * @key: TCP-AO key to be deleted
+ * @current: Optionally set current key
+ * @rnext: Optionally set rnext key
+ *
+ * The function removes the TCP-AO key @key from the kernel list of TCP-AO keys
+ * associated with the socket. There can be multiple sets of keys for different
+ * peers, therefore the key for deletion is identified not only by its send/recv
+ * ID, but also by the relevant prefix (consists of @prefix, @pxlen, and @ifa).
+ * Keys on incoming sockets that were cloned from the listening socket use the
+ * same prefix as on the listening socket.
+ *
+ * Optionally, the current key and the rnext key can be set atomically together
+ * with the deletion, avoiding failure when the deleted key is current / rnext.
+ *
+ * Result: 0 when successful, -1 for an error.
+ */
+int
+sk_delete_ao_key(sock *s, ip_addr prefix, int pxlen, struct iface *ifa, const struct ao_key *key, const struct ao_key *current, const struct ao_key *rnext)
 {
-  struct tcp_ao_info_opt_ext tmp;
-  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
-  socklen_t len = sizeof(tmp);
+  if (pxlen < 0)
+    pxlen = (s->af == AF_INET) ? IP4_MAX_PREFIX_LENGTH : IP6_MAX_PREFIX_LENGTH;
+
+  struct tcp_ao_del_ext ao = {};
+  sockaddr_fill((sockaddr *) &ao.addr, s->af, prefix, ifa, 0);
+  ao.prefix = pxlen;
+  ao.sndid = key->send_id;
+  ao.rcvid = key->recv_id;
 
-  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
+  if (current)
   {
-    log(L_WARN "TCP AO: Getting current ao key for socket file descriptor %i failed with errno %i", sock_fd, errno);
-    return -1;
+    ao.set_current = 1;
+    ao.current_key = current->send_id;
   }
-  else
-    return tmp.current_key;
+
+  if (rnext)
+  {
+    ao.set_rnext = 1;
+    ao.rnext = rnext->recv_id;
+  }
+
+  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_DEL_KEY, &ao, sizeof(ao)) < 0)
+    ERR("TCP_AO_DEL_KEY");
+
+  return 0;
 }
 
-int get_rnext_key_id(int sock_fd)
+/**
+ * sk_set_rnext_ao_key - Set RNext TCP-AO key from the socket
+ * @s: Socket
+ * @key: TCP-AO key to be set as RNext
+ *
+ * Change RNext key of the socket @s to (already added) @key. Cannot be used on
+ * listening sockets.
+ *
+ * Result: 0 when successful, -1 for an error.
+ */
+int
+sk_set_rnext_ao_key(sock *s, const struct ao_key *key)
 {
-  struct tcp_ao_info_opt_ext tmp;
-  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
-  socklen_t len = sizeof(tmp);
+  struct tcp_ao_info_opt_ext ao = {
+    .set_rnext = 1,
+    .rnext = key->recv_id,
+  };
 
-  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;
+  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_INFO, &ao, sizeof(ao)) < 0)
+    ERR("TCP_AO_INFO");
+
+  return 0;
 }
 
-int get_num_ao_keys(int sock_fd)
+/**
+ * sk_check_ao_keys - Check TCP-AO keys on socket against a list of expected keys
+ * @s: Socket
+ * @keys: Array of keys
+ * @num: Number of keys
+ * @name: Name of caller, prefix for error messages
+ *
+ * The function reads TCP-AO keys from the kernel and compares them to the
+ * provided set of keys. When inconsistencies are found, they are logged and the
+ * function reports the error. This is useful for fds received from accept(), to
+ * avoid race conditions when keys are cloned from the listening fd.
+ *
+ * Result: 0 when consistent, -1 for an error.
+ */
+int
+sk_check_ao_keys(sock *s, const struct ao_key **keys, int num, const char *name)
 {
-  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;
+  u32 expected_keys[256 / 32];
+  u8 key_pos[256];
+  int errors = 0;
 
-  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, &tmp, &len))
+  BIT32_ZERO(expected_keys, 256);
+  for (int i = 0; i < num; i++)
   {
-    log(L_WARN "TCP AO: get keys on socket fd %i failed with err code %i", sock_fd, errno);
+    int id = keys[i]->send_id;
+    BIT32_SET(expected_keys, id);
+    key_pos[id] = i;
+  }
+
+  struct tcp_ao_getsockopt_ext *sk_keys;
+  int sk_keys_num;
+
+  if (sk_get_ao_keys(s, &sk_keys, &sk_keys_num) < 0)
+  {
+    sk_log_error(s, name);
     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
+  for (int i = 0; i < sk_keys_num; i++)
   {
-    log(L_WARN "TCP AO: getting keys on socket fd %i failed with err code %i", sock_fd, errno);
-    return;
+    const struct tcp_ao_getsockopt_ext *key = &sk_keys[i];
+
+    if (!BIT32_TEST(expected_keys, key->sndid) ||
+       (key->rcvid != keys[key_pos[key->sndid]]->recv_id))
+    {
+      log(L_WARN "%s: Unexpected TCP-AO key %i/%i found on socket",
+         name, key->sndid, key->rcvid);
+      errors++;
+      continue;
+    }
+
+    BIT32_CLR(expected_keys, key->sndid);
   }
-  log(L_INFO "TCP AO on socket fd %i has %i keys", tm_all[0].nkeys);
-  for (int i = 0; i < nkeys; i++)
+
+  if (!errors && (sk_keys_num == num))
+    return 0;
+
+  for (int i = 0; i < 256; 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);
+    if (BIT32_TEST(expected_keys, i))
+    {
+      const struct ao_key *key = keys[key_pos[i]];
+      log(L_WARN "%s: Expected TCP-AO key %i/%i not found on socket",
+         name, key->send_id, key->recv_id);
+      errors++;
+    }
   }
+
+  return errors ? -1 : 0;
 }
 
 void
-tcp_ao_get_info(int sock_fd, int key_info[4])
+sk_dump_ao_info(sock *s, struct dump_request *dreq)
 {
-  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))
+  struct ao_info info;
+  if (sk_get_ao_info(s, &info) < 0)
   {
-    log(L_WARN "TCP AO: log tcp ao info failed with err code %i", errno);
+    RDUMP("Socket error: %s%#m\n", s->err);
     return;
   }
-  key_info[0] = tmp.current_key;
-  key_info[1] = tmp.rnext;
-  key_info[2] = tmp.pkt_good;
-  key_info[3] = tmp.pkt_bad;
+
+  RDUMP("TCP-AO on socket fd %i: current key %i, rnext key %i, good packets %lu, bad packets %lu\n",
+       s->fd, info.current_key, info.rnext_key, info.pkt_good, info.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)
+void
+sk_dump_ao_keys(sock *s, struct dump_request *dreq)
 {
-  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);
+  struct tcp_ao_getsockopt_ext *keys;
+  int keys_num;
 
-  sockaddr_fill((sockaddr *) &ao.addr, s->af, remote, ifa, 0);
-  if (set_current)
+  if (sk_get_ao_keys(s, &keys, &keys_num) < 0)
   {
-    ao.set_rnext = 1;
-    ao.set_current = 1;
+    RDUMP("Socket error: %s%#m\n", s->err);
+    return;
   }
-  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)
+  RDUMP("TCP-AO on socket fd %i has %i keys\n", s->fd, keys_num);
+  for (int i = 0; i < keys_num; i++)
   {
-    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;
-}
+    const struct tcp_ao_getsockopt_ext *key = &keys[i];
 
-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;
+    ip_addr prefix; uint unused;
+    sockaddr_read((sockaddr *) &key->addr, s->af, &prefix, NULL, &unused);
 
-  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;
+    net_addr net;
+    net_fill_ipa(&net, prefix, key->prefix);
+
+    char secret[TCP_AO_MAXKEYLEN_ * 3];
+    bstrbintohex(key->key, MIN(key->keylen, TCP_AO_MAXKEYLEN_), secret, sizeof(secret), ':');
+
+    RDUMP("Key %i/%i for %N: %s%salgo %s, secret %s\n",
+       key->sndid, key->rcvid, &net, key->is_current ? "current, " : "",
+       key->is_rnext ? "rnext, " : "", key->alg_name, secret);
   }
-  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)
+
+/*
+ *     Miscellaneous Linux socket syscalls
+ */
+
+int
+sk_set_md5_auth(sock *s, ip_addr local UNUSED, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int setkey UNUSED)
 {
-  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;
+  struct tcp_md5sig_ext md5;
+
+  memset(&md5, 0, sizeof(md5));
+  sockaddr_fill((sockaddr *) &md5.tcpm_addr, s->af, remote, ifa, 0);
 
-  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, sizeof(tmp)))
+  if (passwd)
   {
-     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 len = strlen(passwd);
 
-}
+    if (len > TCP_MD5SIG_MAXKEYLEN)
+      ERR_MSG("The password for TCP MD5 Signature is too long");
 
-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;
+    md5.tcpm_keylen = len;
+    memcpy(&md5.tcpm_key, passwd, len);
   }
-  for (int i = 0; i< nkeys; i++)
+
+  if (pxlen < 0)
   {
-    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);
+    if (setsockopt(s->fd, SOL_TCP, TCP_MD5SIG, &md5, sizeof(md5)) < 0)
+      if (errno == ENOPROTOOPT)
+       ERR_MSG("Kernel does not support TCP MD5 signatures");
       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;
+       ERR("TCP_MD5SIG");
   }
-  for (int i = 0; i < 256; i++)
+  else
   {
-    if (expected_keys[i] != 0)
+    md5.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX;
+    md5.tcpm_prefixlen = pxlen;
+
+    if (setsockopt(s->fd, SOL_TCP, TCP_MD5SIG_EXT, &md5, sizeof(md5)) < 0)
     {
-      log(L_WARN "TCP AO: key %i %i is not in socket", i, expected_keys - 1);
-      errors++;
+      if (errno == ENOPROTOOPT)
+       ERR_MSG("Kernel does not support extended TCP MD5 signatures");
+      else
+       ERR("TCP_MD5SIG_EXT");
     }
   }
-  return errors;
+
+  return 0;
 }
 
 static inline int
index 6cb9274005eff6180be6a1242a175439b479686b..1abbdb2b2308ea0c0e47e409b9c6e1326e8e8906 100644 (file)
@@ -1,3 +1,15 @@
+/*
+ *     BIRD Internet Routing Daemon -- Linux TCP-AO API
+ *
+ *     Based on Linux kernel header include/uapi/linux/tcp.h
+ *
+ *     Author: Fred N. van Kempen <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_LINUX_TCP_AO_H_
+#define _BIRD_LINUX_TCP_AO_H_
 
 #ifndef TCP_AO_ADD_KEY
 #define TCP_AO_ADD_KEY         38      /* Add/Set MKT */
 #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 */
+       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 */
@@ -35,7 +41,7 @@ struct tcp_ao_add_ext { /* setsockopt(TCP_AO_ADD_KEY) */
 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 */
+       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 */
@@ -50,7 +56,7 @@ struct tcp_ao_del_ext { /* setsockopt(TCP_AO_DEL_KEY) */
 
 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 */
+       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 */
@@ -77,7 +83,7 @@ struct tcp_ao_getsockopt_ext { /* getsockopt(TCP_AO_GET_KEYS) */
                                         * sizeof(struct tcp_ao_getsockopt)
                                         * out: number of keys that matched
                                         */
-       u16   is_current        :1,     /* in: match and dump Current_key,
+       u16     is_current      :1,     /* in: match and dump Current_key,
                                         * out: the dumped key is Current_key
                                         */
 
@@ -106,4 +112,4 @@ struct tcp_ao_repair_ext { /* {s,g}etsockopt(TCP_AO_REPAIR) */
        u32                     rcv_sne;
 } __attribute__((aligned(8)));
 
-#endif /* TCP_AO_STRUCTS*/
+#endif
index d0910e209c5eee5ff6ba9f0043d450d79ee812dc..cc422ee1c2648238e6fe00dcef5cc71685b8c0fa 100644 (file)
@@ -40,6 +40,7 @@
 #include "lib/event.h"
 #include "lib/timer.h"
 #include "lib/string.h"
+#include "lib/mac.h"
 #include "nest/iface.h"
 #include "nest/cli.h"
 #include "conf/conf.h"
@@ -1668,18 +1669,14 @@ sk_open(sock *s)
       ERR2("bind");
   }
 
-  if (s->ao_key_init)
+  if (s->ao_keys_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)
+    for (int i = 0; i < s->ao_keys_num; i++)
+    {
+      const struct ao_key *key = s->ao_keys_init[i];
+      if (sk_add_ao_key(s, s->daddr, -1, s->iface, key, !i, !i) < 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)
   {
@@ -2261,6 +2258,25 @@ sk_dump_all(struct dump_request *dreq)
   RDUMP("\n");
 }
 
+void
+sk_dump_ao_all(struct dump_request *dreq)
+{
+  RDUMP("TCP-AO sockets:\n");
+  WALK_LIST_(node, n, sock_list)
+  {
+    sock *s = SKIP_BACK(sock, n, n);
+
+    /* Skip non TCP-AO sockets / not supported */
+    if (sk_get_ao_info(s, &(struct ao_info){}) < 0)
+      continue;
+
+    RDUMP("\n%p", s);
+    sk_dump(dreq, &s->r);
+    sk_dump_ao_info(s, dreq);
+    sk_dump_ao_keys(s, dreq);
+  }
+}
+
 
 /*
  *     Internal event log and watchdog