]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit 'origin/master^' into thread-next
authorMaria Matejka <mq@ucw.cz>
Tue, 1 Apr 2025 13:25:30 +0000 (15:25 +0200)
committerMaria Matejka <mq@ucw.cz>
Tue, 1 Apr 2025 13:25:30 +0000 (15:25 +0200)
The AO socket dumping is bonkers but we'll fix it later.

1  2 
doc/bird.sgml
lib/lists.h
lib/socket.h
nest/config.Y
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y
sysdep/unix/io.c

diff --cc doc/bird.sgml
Simple merge
diff --cc lib/lists.h
Simple merge
diff --cc lib/socket.h
index 013f8c3ca1257e25f86b0e4e9f16d301ce195f6a,91a4f12cfc4e906add2496cff48b5f8cf383227b..f46324a4baa34e1f5d771b8ddb07fe4daf4e47a1
@@@ -77,10 -102,12 +103,13 @@@ 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 */
+   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 */
 +  struct birdloop *loop;              /* BIRDLoop owning this socket */
  } sock;
  
  sock *sock_new(pool *);                       /* Allocate new socket */
diff --cc nest/config.Y
Simple merge
diff --cc proto/bgp/bgp.c
index adb6d3d1d58cb9807594628ba535bc625df03bc8,f5008f6cf86a002fd89e54258c388fd1f2394169..1a977d2b32e31cb9149825c8e10b134017839aeb
@@@ -148,24 -141,555 +149,469 @@@ static void bgp_setup_sk(struct bgp_con
  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);
 +static void bgp_initiate_disable(struct bgp_proto *p, int err_val);
  
 -/**
 - * bgp_open - open a BGP instance
 - * @p: BGP instance
 - *
 - * This function allocates and configures shared BGP resources, mainly listening
 - * sockets. Should be called as the last step during initialization (when lock
 - * is acquired and neighbor is ready). When error, caller should change state to
 - * PS_DOWN and return immediately.
 - */
 -static int
 -bgp_open(struct bgp_proto *p)
 -{
 -  struct bgp_socket *bs = NULL;
 -  struct iface *ifa = p->cf->strict_bind ? p->cf->iface : NULL;
 -  ip_addr addr = p->cf->strict_bind ? p->cf->local_ip :
 -    (p->ipv4 ? IPA_NONE4 : IPA_NONE6);
 -  uint port = p->cf->local_port;
 -  uint flags = p->cf->free_bind ? SKF_FREEBIND : 0;
 -  uint flag_mask = SKF_FREEBIND;
 -
 -  /* We assume that cf->iface is defined iff cf->local_ip is link-local */
 -
 -  WALK_LIST(bs, bgp_sockets)
 -    if (ipa_equal(bs->sk->saddr, addr) &&
 -      (bs->sk->sport == port) &&
 -      (bs->sk->iface == ifa) &&
 -      (bs->sk->vrf == p->p.vrf) &&
 -      ((bs->sk->flags & flag_mask) == flags))
 -    {
 -      bs->uc++;
 -      p->sock = bs;
 -      return 0;
 -    }
 -
 -  sock *sk = sk_new(proto_pool);
 -  sk->type = SK_TCP_PASSIVE;
 -  sk->ttl = 255;
 -  sk->saddr = addr;
 -  sk->sport = port;
 -  sk->iface = ifa;
 -  sk->vrf = p->p.vrf;
 -  sk->flags = flags;
 -  sk->tos = IP_PREC_INTERNET_CONTROL;
 -  sk->rbsize = BGP_RX_BUFFER_SIZE;
 -  sk->tbsize = BGP_TX_BUFFER_SIZE;
 -  sk->rx_hook = bgp_incoming_connection;
 -  sk->err_hook = bgp_listen_sock_err;
 -
 -  if (sk_open(sk) < 0)
 -    goto err;
 -
 -  bs = mb_allocz(proto_pool, sizeof(struct bgp_socket));
 -  bs->sk = sk;
 -  bs->uc = 1;
 -  p->sock = bs;
 -  sk->data = bs;
 -
 -  add_tail(&bgp_sockets, &bs->n);
 -
 -  return 0;
 -
 -err:
 -  sk_log_error(sk, p->p.name);
 -  log(L_ERR "%s: Cannot open listening socket", p->p.name);
 -  rfree(sk);
 -  return -1;
 -}
 -
 -/**
 - * bgp_close - close a BGP instance
 - * @p: BGP instance
 - *
 - * This function frees and deconfigures shared BGP resources.
 - */
 -static void
 -bgp_close(struct bgp_proto *p)
 -{
 -  struct bgp_socket *bs = p->sock;
 -
 -  ASSERT(bs && bs->uc);
 -
 -  if (--bs->uc)
 -    return;
 -
 -  rfree(bs->sk);
 -  rem_node(&bs->n);
 -  mb_free(bs);
 -}
 +static void bgp_graceful_restart_feed(struct bgp_channel *c);
  
  
+ /*
+  *    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
 -      ((sk == p->sock->sk) ? "listening" : "session"));
+ 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,
 -  if (bgp_sk_add_ao_key(p, p->sock->sk, key) < 0)
++      ((sk == p->listen.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 */
 -      ((sk == p->sock->sk) ? "listening" : "session"));
++  if (bgp_sk_add_ao_key(p, p->listen.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,
 -  if (bgp_sk_delete_ao_key(p, p->sock->sk, key, NULL, -1, -1) < 0)
++      ((sk == p->listen.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 (p->start_state == BSS_PREPARE)
++  if (bgp_sk_delete_ao_key(p, p->listen.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 (bgp_start_state(p) == 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)
  {
-   /* Beware. This is done from main_birdloop and protocol birdloop is NOT ENTERED.
-    * Anyway, we are only accessing:
-    *  - protocol config which can be changed only from main_birdloop (reconfig)
-    *  - protocol listen socket which is always driven by main_birdloop
-    *  - protocol name which is set on reconfig
-    */
+   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->password && p->listen.sock)
+   if (p->cf->auth_type == BGP_AUTH_MD5)
    {
      ip_addr prefix = p->cf->remote_ip;
      int pxlen = -1;
  
      return rv;
    }
-   else
-     return 0;
+   return 0;
  }
  
-       bgp_initiate_disable(p, BEM_INVALID_MD5);
++/* 
++ * BGP Instance Management
++ */
++
 +/**
 + * bgp_close - close a BGP instance
 + * @p: BGP instance
 + *
 + * This function frees and deconfigures shared BGP resources.
 + */
 +static void
 +bgp_close(struct bgp_proto *p)
 +{
 +  LOCK_DOMAIN(rtable, bgp_listen_domain);
 +
 +  struct bgp_listen_request *req = &p->listen;
 +  struct bgp_socket *bs = req->sock;
 +
 +  if (enlisted(&req->n))
 +  {
 +    /* Remove listen request from listen socket or pending list */
 +    rem_node(&req->n);
 +
 +    if (bs)
 +    {
 +      /* Already had a socket. */
 +      req->sock = NULL;
 +
 +      /* Request listen socket cleanup */
 +      if (bs && EMPTY_LIST(bs->requests))
 +      ev_send(&global_event_list, &bgp_listen_event);
 +    }
 +  }
 +
 +  UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +}
 +
 +/**
 + * bgp_open - open a BGP instance
 + * @p: BGP instance
 + *
 + * This function allocates and configures shared BGP resources, mainly listening
 + * sockets. Should be called as the last step during initialization (when lock
 + * is acquired and neighbor is ready). When error, caller should change state to
 + * PS_DOWN and return immediately.
 + */
 +static void
 +bgp_open(struct bgp_proto *p)
 +{
 +  LOCK_DOMAIN(rtable, bgp_listen_domain);
 +
 +  struct bgp_listen_request *req = &p->listen;
 +  /* We assume that cf->iface is defined iff cf->local_ip is link-local */
 +  req->iface = p->cf->strict_bind ? p->cf->iface : NULL;
 +  req->vrf = p->p.vrf;
 +  req->addr = p->cf->strict_bind ? p->cf->local_ip :
 +    (p->ipv4 ? IPA_NONE4 : IPA_NONE6);
 +  req->port = p->cf->local_port;
 +  req->flags = p->cf->free_bind ? SKF_FREEBIND : 0;
 +
 +  BGP_TRACE(D_EVENTS, "Requesting listen socket at %I%J port %u", req->addr, req->iface, req->port);
 +
 +  add_tail(&bgp_listen_pending, &req->n);
 +  ev_send(&global_event_list, &bgp_listen_event);
 +
 +  UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +}
 +
 +static void
 +bgp_listen_create(void *_ UNUSED)
 +{
 +  ASSERT_DIE(birdloop_inside(&main_birdloop));
 +  uint flag_mask = SKF_FREEBIND;
 +
 +  while (1) {
 +    LOCK_DOMAIN(rtable, bgp_listen_domain);
 +
 +    if (EMPTY_LIST(bgp_listen_pending))
 +    {
 +      UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +      break;
 +    }
 +
 +    /* Get the first request to match */
 +    struct bgp_listen_request *req = HEAD(bgp_listen_pending);
 +    SKIP_BACK_DECLARE(struct bgp_proto, p, listen, req);
 +    rem_node(&req->n);
 +
 +    /* First try to find existing socket */
 +    struct bgp_socket *bs;
 +    WALK_LIST(bs, bgp_sockets)
 +      if (ipa_equal(bs->sk->saddr, req->addr) &&
 +        (bs->sk->sport == req->port) &&
 +        (bs->sk->iface == req->iface) &&
 +        (bs->sk->vrf == req->vrf) &&
 +        ((bs->sk->flags & flag_mask) == req->flags))
 +      break;
 +
 +    /* Not found any */
 +    if (NODE_VALID(bs))
 +      BGP_TRACE(D_EVENTS, "Found a listening socket: %p", bs);
 +    else
 +    {
 +      /* Allocating new socket from global protocol pool.
 +       * We can do this in main_birdloop. */
 +      sock *sk = sk_new(bgp_listen_pool);
 +      sk->type = SK_TCP_PASSIVE;
 +      sk->ttl = 255;
 +      sk->saddr = req->addr;
 +      sk->sport = req->port;
 +      sk->iface = req->iface;
 +      sk->vrf = req->vrf;
 +      sk->flags = req->flags;
 +      sk->tos = IP_PREC_INTERNET_CONTROL;
 +      sk->rbsize = BGP_RX_BUFFER_SIZE;
 +      sk->tbsize = BGP_TX_BUFFER_SIZE;
 +      sk->rx_hook = bgp_incoming_connection;
 +      sk->err_hook = bgp_listen_sock_err;
 +
 +      if (sk_open(sk, &main_birdloop) < 0)
 +      {
 +      sk_log_error(sk, p->p.name);
 +      log(L_ERR "%s: Cannot open listening socket", p->p.name);
 +      sk_close(sk);
 +      UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +
 +      bgp_initiate_disable(p, BEM_NO_SOCKET);
 +      continue;
 +      }
 +
 +      bs = mb_allocz(bgp_listen_pool, sizeof(struct bgp_socket));
 +      bs->sk = sk;
 +      sk->data = bs;
 +
 +      init_list(&bs->requests);
 +      add_tail(&bgp_sockets, &bs->n);
 +
 +      BGP_TRACE(D_EVENTS, "Created new listening socket: %p", bs);
 +    }
 +
 +    req->sock = bs;
 +    add_tail(&bs->requests, &req->n);
 +
 +    if (bgp_setup_auth(p, 1) < 0)
 +    {
 +      rem_node(&req->n);
 +      req->sock = NULL;
 +
 +      UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +
++      bgp_initiate_disable(p, BEM_INVALID_AUTH);
 +      continue;
 +    }
 +
 +    UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +  }
 +
 +  /* Cleanup leftover listening sockets */
 +  LOCK_DOMAIN(rtable, bgp_listen_domain);
 +  struct bgp_socket *bs;
 +  node *nxt;
 +  WALK_LIST_DELSAFE(bs, nxt, bgp_sockets)
 +    if (EMPTY_LIST(bs->requests))
 +    {
 +      sk_close(bs->sk);
 +      rem_node(&bs->n);
 +      mb_free(bs);
 +    }
 +  UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +}
 +
  static inline struct bgp_channel *
  bgp_find_channel(struct bgp_proto *p, u32 afi)
  {
@@@ -1357,7 -1610,14 +1806,14 @@@ bgp_connect(struct bgp_proto *p)      /* Ent
    bgp_setup_sk(conn, s);
    bgp_conn_set_state(conn, BS_CONNECT);
  
 -  if (sk_open(s) < 0)
+   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, p->p.loop) < 0)
      goto err;
  
    /* Set minimal receive TTL if needed */
      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);
 +  bgp_start_timer(p, conn->connect_timer, p->cf->connect_retry_time);
    return;
  
  err:
@@@ -1449,8 -1702,20 +1909,22 @@@ bgp_incoming_connection(sock *sk, uint 
      return 0;
    }
  
 -      rfree(sk);
 -      return 0;
 +  birdloop_enter(p->p.loop);
 +
+   if (!EMPTY_LIST(p->ao.keys))
+   {
+     int current = -1, rnext = -1;
+     sk_get_active_ao_keys(sk, &current, &rnext);
+     if (current < 0)
+     {
+       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);
++      sk_close(sk);
++      goto leave;
+     }
+   }
    /*
     * BIRD should keep multiple incoming connections in OpenSent state (for
     * details RFC 4271 8.2.1 par 3), but it keeps just one. Duplicate incoming
  
  err:
    sk_log_error(sk, p->p.name);
+ err2:
    log(L_ERR "%s: Incoming connection aborted", p->p.name);
 -  rfree(sk);
 +  sk_close(sk);
 +
 +leave:
 +  UNLOCK_DOMAIN(rtable, bgp_listen_domain);
 +
 +  /* We need to announce possible state changes immediately before
 +   * leaving the protocol's loop, otherwise we're gonna access the protocol
 +   * without having it locked from proto_announce_state_later(). */
 +  proto_announce_state(&p->p, p->p.ea_state);
 +  birdloop_leave(p->p.loop);
    return 0;
  }
  
@@@ -1855,16 -2102,13 +2345,22 @@@ bgp_start(struct proto *P
    p->remote_id = 0;
    p->link_addr = IPA_NONE;
  
 -  proto_setup_mpls_map(P, RTS_BGP, 1);
+   /* 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);
 +  ea_list *eal = p->p.ea_state;
 +  ea_set_attr(&eal, EA_LITERAL_EMBEDDED(&ea_bgp_rem_id, 0, p->remote_id));
 +  ea_set_attr(&eal, EA_LITERAL_EMBEDDED(&ea_bgp_loc_as, 0, p->local_as));
 +  ea_set_attr(&eal, EA_LITERAL_EMBEDDED(&ea_bgp_rem_as, 0, p->remote_as));
 +  ea_set_attr(&eal, EA_LITERAL_STORE_ADATA(&ea_bgp_rem_ip, 0, &p->remote_ip, sizeof(ip_addr)));
 +
 +  ea_set_attr(&eal, EA_LITERAL_EMBEDDED(&ea_bgp_out_conn_state, 0, BS_IDLE));
 +  ea_set_attr(&eal, EA_LITERAL_EMBEDDED(&ea_bgp_in_conn_state, 0, BS_IDLE));
 +
 +  proto_announce_state(&p->p, eal);
  
    /* Lock all channels when in GR recovery mode */
    if (p->p.gr_recovery && p->cf->gr_mode)
@@@ -2981,11 -3206,21 +3495,23 @@@ bgp_show_proto_info(struct proto *P
            tm_remains(p->conn->hold_timer), p->conn->hold_time);
      cli_msg(-1006, "    Keepalive timer:  %t/%u",
            tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time);
 +    cli_msg(-1006, "    TX pending:       %d bytes",
 +          p->conn->sk->tpos - p->conn->sk->ttx);
      cli_msg(-1006, "    Send hold timer:  %t/%u",
            tm_remains(p->conn->send_hold_timer), p->conn->send_hold_time);
- }
+     if (!EMPTY_LIST(p->ao.keys))
+     {
+       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);
+     }
+   }
  
  #if 0
    struct bgp_stats *s = &p->stats;
diff --cc proto/bgp/bgp.h
index 083d754262ac61a196255ab8e7715e90120870e9,515ef2754c74f26fdee6c84f0070f706775039ca..39a2fb55c6d3be6c1ecd8bf6fc3d175b28974a83
@@@ -288,10 -295,22 +295,22 @@@ 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 */
 +  list requests;                      /* Listen requests */
    sock *sk;                           /* Real listening socket */
 -  u32 uc;                             /* Use count */
  };
  
  struct bgp_stats {
@@@ -388,11 -383,10 +407,12 @@@ struct bgp_proto 
    struct bgp_conn incoming_conn;      /* Incoming connection we have neither accepted nor rejected yet */
    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 bfd_request *bfd_req;                /* BFD request, if BFD is used */
 +  struct bgp_listen_request listen;   /* Shared listening socket */
 +  struct bfd_request_ref *bfd_req;    /* BFD request, if BFD is used */
 +  callback bfd_notify;                        /* BFD notification callback */
    struct birdsock *postponed_sk;      /* Postponed incoming socket for dynamic BGP */
+   struct bgp_ao_state ao;
 +  struct rt_uncork_callback uncork;   /* Uncork hook */
    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 */
index 17906d75ab3a2a5c99f2a041abf1242bcbfc58e6,7cc02d52a102a0958ec53c7d2b94dfb2514b5e09..af3529b5a9faca7818084a05b85c30bc246425df
@@@ -29,14 -33,14 +32,17 @@@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, H
        GRACEFUL, RESTART, AWARE, CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY,
        STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG,
        LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS,
 -      DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
 +      DYNAMIC, RANGE, NAME, DIGITS, AIGP, ORIGINATE, COST, ENFORCE,
        FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
-       RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, TX, SIZE, WARNING,
-       MIN, MAX, FORMAT, NATIVE, SINGLE, DOUBLE)
+       RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX,
++      TX, SIZE, WARNING,
+       AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE)
+ CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128)
  
- %type <i> bgp_nh bgp_llnh
 +CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE)
 +
+ %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,
index f9785c074e834896c477983fd722d8ee62066f5c,cc422ee1c2648238e6fe00dcef5cc71685b8c0fa..5b9c2e0e636d8dcbbba93f00887f288cffb99b38
  #include "lib/resource.h"
  #include "lib/socket.h"
  #include "lib/event.h"
 +#include "lib/locking.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"
@@@ -2395,6 -2242,6 +2407,10 @@@ sk_err(sock *s, int revents
    tmp_flush();
  }
  
++
++/* FIXME: these two functions should actually call bird_thread_sync_all()
++ * to get threads from all loops. Now they dump just mainloop. */
++
  void
  sk_dump_all(struct dump_request *dreq)
  {
    RDUMP("\n");
  }
  
 -  RDUMP("TCP-AO sockets:\n");
 -  WALK_LIST_(node, n, sock_list)
+ void
+ sk_dump_ao_all(struct dump_request *dreq)
+ {
++  RDUMP("TCP-AO listening sockets:\n");
++  WALK_LIST_(node, n, main_birdloop.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