]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
TCP-AO implementation based on RFC 5925
authorKateřina Kubecová <katerina.kubecova@nic.cz>
Wed, 26 Jun 2024 07:23:12 +0000 (09:23 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Mon, 31 Mar 2025 01:44:34 +0000 (03:44 +0200)
Uses Linux TCP-AO. Tested against JUNIPER.

doc/bird.sgml
lib/socket.h
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y
proto/bgp/packets.c
sysdep/linux/sysio.h
sysdep/linux/tcp-ao.h [new file with mode: 0644]
sysdep/unix/io.c

index 1a94739023b2244df538ff8289947ae12ddb0f98..4d76d74dfcf837b5a8e5e84943f8e561d50ec745 100644 (file)
@@ -3136,6 +3136,38 @@ 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.
+
+       Deleting a currently used key in config causes restart of the protocol.
+       The problem of directly deleting current key is, that we could treat such deleted key as deprecated,
+       but only until the protocol restarts. If it restarts, key is lost.
+       For example, connection is established. Then one side decides to remove current key and requires a newly added key.
+       The other side does not know the new key yet. For now, this is not problem, they still use the old key.
+       But, at this moment, some unexpected error occures at the first site.
+       It restarts, but it does not have the old key in config. One site does not have old key,
+       the other new key and trying other keys than required ones is not supported.
+
+       Editing existing keys (except of marking them "required" or "deprecated")
+       is not recommended and leads to restarting protocol.
+
        <tag><label id="bgp-passive">passive <m/switch/</tag>
        Standard BGP behavior is both initiating outgoing connections and
        accepting incoming connections. In passive mode, outgoing connections
@@ -4017,6 +4049,20 @@ protocol bgp {
        local 198.51.100.14 as 65000;        # Use a private AS number
        neighbor 198.51.100.130 as 64496;    # Our neighbor ...
        multihop;                            # ... which is connected indirectly
+       authenticate manual {
+               key {
+                       local id 2;
+                       remote id 1;
+                       cipher "cmac(aes128)";
+                       master key "hello321";
+                       required;
+               }key {
+                       local id 3;
+                       remote id 3;
+                       cipher "cmac(aes128)";
+                       master key "bye123";
+               }
+       }
        ipv4 {
                export filter {                      # We use non-trivial export rules
                        if source = RTS_STATIC then { # Export only static routes
index edd90b9f3953d1b49c292573ba17bbb498c68097..2a510a9fcb8c827af6d3b6b93dfc890853db4a07 100644 (file)
@@ -36,6 +36,29 @@ struct ssh_sock {
 };
 #endif
 
+struct ao_key
+{
+  int local_id;
+  int remote_id;
+  const char *cipher;
+  const char *master_key;
+  int required;
+};
+
+struct ao_config
+{
+  struct ao_key key;
+  struct ao_config *next_key;
+};
+
+struct bgp_ao_key {
+  struct ao_key key;
+  int activ_alive;                     /* this ao key is in activ socket */
+  int passiv_alive;                    /* this ao key is in passiv socket */
+  int to_delete;                       /* flag for reconfig */
+  struct bgp_ao_key *next_key;
+};
+
 typedef struct birdsock {
   resource r;
   pool *pool;                          /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */
@@ -77,6 +100,11 @@ typedef struct birdsock {
   node n;
   void *rbuf_alloc, *tbuf_alloc;
   const char *password;                        /* Password for MD5 authentication */
+  struct bgp_ao_key *ao_key_init;      /* Key for tcp ao authentication icialization. */
+  struct bgp_proto *proto_del_ao_key;  /* For deletion of the currently used deprecated ao key */
+  char use_ao;                         /* This is the only reliable flag saying if the socket use ao or not */
+  int last_used_ao_key;                /* Last ID the other site requested */
+  int desired_ao_key;                  /* ID of requested ao key */
   const char *err;                     /* Error message */
   struct ssh_sock *ssh;                        /* Used in SK_SSH */
 } sock;
@@ -107,6 +135,16 @@ int sk_setup_broadcast(sock *s);
 int sk_set_ttl(sock *s, int ttl);      /* Set transmit TTL for given socket */
 int sk_set_min_ttl(sock *s, int ttl);  /* Set minimal accepted TTL for given socket */
 int sk_set_md5_auth(sock *s, ip_addr local, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int setkey);
+
+int get_current_key_id(int sock_fd);
+int get_rnext_key_id(int sock_fd);
+int sk_set_ao_auth(sock *s, ip_addr local, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int passwd_id_loc, int passwd_id_rem, const char *cipher, int set_current);
+int ao_delete_key(sock *s, ip_addr remote, int pxlen, struct iface *ifa, int passwd_id_rem, int passwd_id_loc);
+void log_tcp_ao_info(int sock_fd);
+void log_tcp_ao_get_key(int sock_fd);
+void tcp_ao_get_info(int sock_fd, int key_info[4]);
+int check_ao_keys_id(int sock_fd, struct bgp_ao_key *key);
+void ao_try_change_master(sock *s, int next_key_id_loc, int next_id_rem);
 int sk_set_ipv6_checksum(sock *s, int offset);
 int sk_set_icmp6_filter(sock *s, int p1, int p2);
 void sk_log_error(sock *s, const char *p);
index b5327a6937cb555eca96fe51ce750a4e2a5a06f3..a4523ed5e0a45db1cc8a62092f871b3d0754215c 100644 (file)
@@ -235,7 +235,7 @@ bgp_close(struct bgp_proto *p)
 static inline int
 bgp_setup_auth(struct bgp_proto *p, int enable)
 {
-  if (p->cf->password)
+  if (p->cf->password || p->cf->ao_key)
   {
     ip_addr prefix = p->cf->remote_ip;
     int pxlen = -1;
@@ -245,10 +245,37 @@ bgp_setup_auth(struct bgp_proto *p, int enable)
       prefix = net_prefix(p->cf->remote_range);
       pxlen = net_pxlen(p->cf->remote_range);
     }
+    int rv = 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;
 
-    int rv = sk_set_md5_auth(p->sock->sk,
+        } while(key);
+      }
+      else
+      {
+        struct bgp_ao_key *key = p->ao_key;
+        while (key)
+        {
+         if (key->passiv_alive)
+            ao_delete_key(p->sock->sk, p->remote_ip, -1, p->sock->sk->iface, key->key.local_id, key->key.remote_id);
+          key = key->next_key;
+        }
+      }
+    }
+    else if (enable)
+      rv = sk_set_md5_auth(p->sock->sk,
                             p->cf->local_ip, prefix, pxlen, p->cf->iface,
-                            enable ? p->cf->password : NULL, p->cf->setkey);
+                            p->cf->password, p->cf->setkey);
 
     if (rv < 0)
       sk_log_error(p->sock->sk, p->p.name);
@@ -1148,6 +1175,7 @@ bgp_connect(struct bgp_proto *p)  /* Enter Connect state and start establishing c
   s->tbsize = p->cf->enable_extended_messages ? BGP_TX_BUFFER_EXT_SIZE : BGP_TX_BUFFER_SIZE;
   s->tos = IP_PREC_INTERNET_CONTROL;
   s->password = p->cf->password;
+  s->ao_key_init = p->ao_key;
   s->tx_hook = bgp_connected;
   s->flags = p->cf->free_bind ? SKF_FREEBIND : 0;
   BGP_TRACE(D_EVENTS, "Connecting to %I%J from local address %I%J",
@@ -1237,6 +1265,16 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
     rfree(sk);
     return 0;
   }
+  if (p->cf->ao_key)
+  {
+    if (get_current_key_id(sk->fd) == -1)
+    {
+      log(L_WARN "BGP: Connection from address %I%J (port %d) has no TCP AO key",
+          sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL, sk->dport);
+      rfree(sk);
+      return 0;
+    }
+  }
 
   /*
    * BIRD should keep multiple incoming connections in OpenSent state (for
@@ -1279,6 +1317,26 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
     if (sk_set_min_ttl(sk, 256 - hops) < 0)
       goto err;
 
+  if (p->ao_key)
+  {
+    if (check_ao_keys_id(sk->fd, p->ao_key) == 0)
+    {
+      sk->use_ao = 1;
+      for (struct bgp_ao_key *key = p->ao_key; key; key = key->next_key)
+      {
+        key->activ_alive = key->passiv_alive;
+        if (key->key.required == 1)
+        {
+          sk->desired_ao_key = key->key.local_id;
+          ao_try_change_master(sk, key->key.local_id, key->key.remote_id);
+        }
+        else if (key->key.required == -1)
+        {
+          sk->proto_del_ao_key = p;
+        }
+      }
+    }
+  }
   if (p->cf->enable_extended_messages)
   {
     sk->rbsize = BGP_RX_BUFFER_EXT_SIZE;
@@ -1763,6 +1821,20 @@ bgp_init(struct proto_config *CF)
   p->remote_ip = cf->remote_ip;
   p->remote_as = cf->remote_as;
 
+  if (cf->ao_key)
+  {
+    struct ao_config *cf_key = cf->ao_key;
+    do {
+      struct bgp_ao_key *key = mb_alloc(proto_pool, sizeof(struct bgp_ao_key));
+      key->key = cf_key->key;
+      key->activ_alive = 0;
+      key->passiv_alive = 0;
+      key->next_key = p->ao_key;
+      p->ao_key = key;
+      cf_key = cf_key->next_key;
+    } while (cf_key);
+  }
+
   /* Hack: We use cf->remote_ip just to pass remote_ip from bgp_spawn() */
   if (cf->c.parent)
     cf->remote_ip = IPA_NONE;
@@ -2207,6 +2279,146 @@ bgp_postconfig(struct proto_config *CF)
   }
 }
 
+int compare_aos(struct ao_key *a, struct ao_key *b)
+{
+  if (a->local_id != b->local_id)
+    return 1;
+  if (a->remote_id != b->remote_id)
+    return 1;
+  if (strcmp(a->cipher, b->cipher))
+    return 1;
+  return strcmp(a->master_key, b->master_key);
+}
+
+int reconfigure_tcp_ao(struct bgp_proto *old_proto, struct bgp_config new)
+{
+  log("reconfiguring proto");
+  if (old_proto->cf->ao_key == NULL && new.ao_key == NULL)
+    return 1;   // tcp ao not used
+  if (old_proto->cf->ao_key == NULL || new.ao_key == NULL)
+    return 0;   // connection is changing from ao to no ao or no ao to ao
+
+  if (!old_proto->conn)
+  {
+    log("tcp ao: reconfigure nonestablished connection");
+    return 0;  // Connection was not (re)established, so we can not change it.
+  }
+  sock *s_passiv = old_proto->sock->sk;
+  sock *s_activ = old_proto->conn->sk;
+
+  int key_in_use_loc = get_current_key_id(s_activ->fd);
+
+  if (key_in_use_loc == -1)
+  {
+      log(L_WARN "TCP AO: Unable to detect currently used key");
+      return 0;
+  }
+
+  for (struct bgp_ao_key *ao_key = old_proto->ao_key; ao_key; ao_key = ao_key->next_key)
+    ao_key->to_delete = 1;
+
+  struct bgp_ao_key *first = old_proto->ao_key;
+  for (struct ao_config *cf_ao = new.ao_key; cf_ao; cf_ao = cf_ao->next_key)
+  {
+    if (cf_ao->key.required == -1 && cf_ao->key.local_id != key_in_use_loc)
+      continue;
+
+    struct bgp_ao_key *found = NULL;
+    for (struct bgp_ao_key *old_ao = first; old_ao && !found; old_ao = old_ao->next_key)
+    {
+      if (old_ao->key.local_id == cf_ao->key.local_id || old_ao->key.remote_id == cf_ao->key.remote_id)
+      {
+        if (compare_aos(&old_ao->key, &cf_ao->key))
+          return 0;
+        if (old_ao->activ_alive == 0 && cf_ao->key.required >= 0)
+        {
+          if (sk_set_ao_auth(s_activ, old_proto->local_ip, old_proto->remote_ip, -1, s_activ->iface,
+                                old_ao->key.master_key, old_ao->key.local_id, old_ao->key.remote_id, old_ao->key.cipher, 0))
+            return 0;
+         old_ao->activ_alive = 1;
+        }
+        if (old_ao->passiv_alive == 0 && cf_ao->key.required >= 0)
+       {
+          if (sk_set_ao_auth(s_passiv, old_proto->local_ip, old_proto->remote_ip, -1, s_passiv->iface,
+                                 old_ao->key.master_key, old_ao->key.local_id, old_ao->key.remote_id, old_ao->key.cipher, 0))
+            return 0;
+         old_ao->passiv_alive = 1;
+       }
+
+        if (cf_ao->key.required == 1 && old_ao->key.required != 1)
+        {
+          s_activ->desired_ao_key = old_ao->key.local_id;
+          s_passiv->desired_ao_key = old_ao->key.local_id;
+          ao_try_change_master(s_activ, old_ao->key.local_id, old_ao->key.remote_id);
+          if (old_proto->conn->hold_timer->expires != 0)
+            bgp_schedule_packet(old_proto->conn, NULL, PKT_KEEPALIVE); // We might send this keepalive shortly after another.
+                                                                      // RFC says we should wait, but since reconfiguration is rare, this is harmless.
+        }
+        old_ao->key = cf_ao->key;
+        old_ao->to_delete = 0;
+        found = old_ao;
+      }
+    }
+    if (!found)
+    {
+      struct bgp_ao_key *key = mb_alloc(old_proto->p.pool, sizeof(struct bgp_ao_key));
+      key->key = cf_ao->key;
+      key->activ_alive = 0;
+      key->passiv_alive = 0;
+      key->next_key = first;
+      old_proto->ao_key = key;
+      if (sk_set_ao_auth(s_passiv, old_proto->local_ip, old_proto->remote_ip, -1, s_passiv->iface,
+                             cf_ao->key.master_key, cf_ao->key.local_id, cf_ao->key.remote_id, cf_ao->key.cipher, 0))
+        return 0;
+      key->passiv_alive = 1;
+      if (sk_set_ao_auth(s_activ, old_proto->local_ip, old_proto->remote_ip, -1, s_passiv->iface,
+                             cf_ao->key.master_key, cf_ao->key.local_id, cf_ao->key.remote_id, cf_ao->key.cipher, 0))
+        return 0;
+      key->activ_alive = 1;
+      key->to_delete = 0;
+      found = key;
+
+      if (found->key.required == 1)
+      {
+        s_activ->desired_ao_key = found->key.local_id;
+        s_passiv->desired_ao_key = found->key.local_id;
+        ao_try_change_master(s_activ, found->key.local_id, found->key.remote_id);
+        if (old_proto->conn->hold_timer->expires != 0)
+          bgp_schedule_packet(old_proto->conn, NULL, PKT_KEEPALIVE); // We might send this keepalive shortly after another.
+                                                                    // RFC says we should wait, but since reconfiguration is rare, this is harmless.
+      }
+    }
+  }
+
+  key_in_use_loc = get_current_key_id(s_activ->fd);
+  struct bgp_ao_key *previous = NULL;
+  for (struct bgp_ao_key *old_ao = old_proto->ao_key; old_ao; old_ao = old_ao->next_key)
+  {
+    if (old_ao->to_delete)
+    {
+      if (old_ao->key.local_id == key_in_use_loc)
+      {
+        log(L_WARN "TCP AO: deleting currently used key");
+        return 0;
+      }
+      if (ao_delete_key(s_activ, old_proto->remote_ip, -1, s_activ->iface, old_ao->key.local_id, old_ao->key.remote_id))
+        return 0;
+      old_ao->activ_alive = 0;
+
+      if (ao_delete_key(s_passiv, old_proto->remote_ip, -1, s_passiv->iface, old_ao->key.local_id, old_ao->key.remote_id))
+        return 0;
+      old_ao->passiv_alive = 0;
+      if (previous)
+        previous->next_key = old_ao->next_key;
+      else
+        old_proto->ao_key = old_ao->next_key;
+    }
+    else
+      previous = old_ao;
+  }
+  return 1;
+}
+
 static int
 bgp_reconfigure(struct proto *P, struct proto_config *CF)
 {
@@ -2220,11 +2432,13 @@ 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)
@@ -2690,7 +2904,18 @@ bgp_show_proto_info(struct proto *P)
            tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time);
     cli_msg(-1006, "    Send hold timer:  %t/%u",
            tm_remains(p->conn->send_hold_timer), p->conn->send_hold_time);
-}
+
+    if (p->cf->ao_key)
+    {
+      int tmp[4];
+      tcp_ao_get_info(p->conn->sk->fd, tmp);
+      cli_msg(-1006, "    TCP AO:");
+      cli_msg(-1006, "      current key remote id %i", tmp[0]);
+      cli_msg(-1006, "      rnext key local id %i", tmp[1]);
+      cli_msg(-1006, "      good packets %i", tmp[2]);
+      cli_msg(-1006, "      bad packets  %i", tmp[3]);
+    }
+  }
 
 #if 0
   struct bgp_stats *s = &p->stats;
index 5ec7a9f9460270f3744a58f43095478289c693ca..30374626f7e9a9ec462e2bac268ec13531b5fd0f 100644 (file)
@@ -140,6 +140,7 @@ struct bgp_config {
   u32 disable_after_cease;             /* Disable it when cease is received, bitfield */
 
   const char *password;                        /* Password used for MD5 authentication */
+  struct ao_config *ao_key;            /* Keys for tcp ao authentication */
   net_addr *remote_range;              /* Allowed neighbor range for dynamic BGP */
   const char *dynamic_name;            /* Name pattern for dynamic BGP */
   int dynamic_name_digits;             /* Minimum number of digits for dynamic names */
@@ -365,6 +366,7 @@ struct bgp_proto {
   struct object_lock *lock;            /* Lock for neighbor connection */
   struct neighbor *neigh;              /* Neighbor entry corresponding to remote ip, NULL if multihop */
   struct bgp_socket *sock;             /* Shared listening socket */
+  struct bgp_ao_key *ao_key;           /* Linked list for ao keys */
   struct bfd_request *bfd_req;         /* BFD request, if BFD is used */
   struct birdsock *postponed_sk;       /* Postponed incoming socket for dynamic BGP */
   struct bgp_stats stats;              /* BGP statistics */
index 11003d4e904ff850ce6671f510d710d6f0c117c1..8a2f0309c4d96558af808ed56d5ee6a94e7d8177 100644 (file)
@@ -33,6 +33,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
        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)
 
 %type <i> bgp_nh bgp_llnh
@@ -206,7 +207,8 @@ bgp_proto:
  | bgp_proto REQUIRE GRACEFUL RESTART bool ';' { BGP_CFG->require_gr = $5; }
  | bgp_proto REQUIRE LONG LIVED GRACEFUL RESTART bool ';' { BGP_CFG->require_llgr = $7; }
  | bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; }
- | bgp_proto PASSWORD text ';' { BGP_CFG->password = $3; }
+ | bgp_proto PASSWORD text ';' { log("%s", $3); BGP_CFG->password = $3; }
+ | bgp_proto AUTHENTICATE MANUAL '{' ao_keys '}' tcp_ao_end
  | bgp_proto SETKEY bool ';' { BGP_CFG->setkey = $3; }
  | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; }
  | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; }
@@ -251,6 +253,95 @@ bgp_afi:
  | FLOW6               { $$ = BGP_AF_FLOW6; }
  ;
 
+ao_keys:
+   KEY '{' ao_first_item  ao_key '}'
+ | KEY '{' ao_first_item ao_key '}' ao_keys
+ ;
+
+ao_key:
+  ao_item
+ | ao_item ao_key
+ ;
+
+ao_first_item:
+  LOCAL ID expr ';' {
+    if ($3 >= 256)
+      cf_error("Key ids ust be in range 0 - 255");
+    struct ao_config *new_key = cfg_alloc(sizeof(struct ao_config));
+    new_key->next_key = BGP_CFG->ao_key;
+    BGP_CFG->ao_key = new_key;
+    BGP_CFG->ao_key->key.required = 0;
+    BGP_CFG->ao_key->key.local_id = $3;
+    BGP_CFG->ao_key->key.remote_id = -1;
+  }
+;
+
+ao_item:
+ REMOTE ID expr ';' {
+   if ($3 > 255)
+     cf_error("TCP AO: Key id must be in range 0 - 255");
+     BGP_CFG->ao_key->key.remote_id = $3; }
+ | CIPHER text ';' {
+     if (strcmp($2, "cmac(aes128)") & strcmp($2, "hmac(sha1)") & strcmp($2, "hmac(sha224)") & strcmp($2, "hmac(sha256)") & strcmp($2, "hmac(sha384)") & strcmp($2, "hmac(sha512)")& strcmp($2, "hmac(md5)"))
+       cf_error("TCP AO: Here are ciphers 'cmac(aes128)', 'hmac(md5)', 'hmac(sha1)', 'hmac(sha224)', 'hmac(sha256)', 'hmac(sha384)' and 'hmac(sha512)' hardcoded. If there is another cipher available in kernel, please contact BIRD developers.");
+     char *c = cfg_alloc(strlen($2)+1);
+     memcpy(c, $2, strlen($2)+1);
+     BGP_CFG->ao_key->key.cipher = c;
+   }
+ | MASTER KEY text ';' {
+     char *k = cfg_alloc(strlen($3)+1);
+     memcpy(k,  $3, strlen($3)+1);
+     BGP_CFG->ao_key->key.master_key = k;
+   }
+ | DEPRECATED ';' {
+     if (BGP_CFG->ao_key->key.required == 0)
+       BGP_CFG->ao_key->key.required = -1;
+     else
+       cf_error("TCP AO: Key can be only once deprecated or once required, key id %i", BGP_CFG->ao_key->key.local_id);
+   }
+ | REQUIRED ';' {
+     if (BGP_CFG->ao_key->key.required == 0)
+       BGP_CFG->ao_key->key.required = 1;
+     else
+       cf_error("TCP AO: Key can be only once deprecated or once required, key id %i", BGP_CFG->ao_key->key.local_id);
+   }
+ ;
+
+tcp_ao_end:
+{
+  char used_aos_id_loc[256];
+  char used_aos_id_rem[256];
+  memset(used_aos_id_loc, 0, sizeof(char)*256);
+  memset(used_aos_id_rem, 0, sizeof(char)*256);
+  int required_found = 0;
+
+  struct ao_config *key = BGP_CFG->ao_key;
+  while (key)
+  {
+    if (used_aos_id_loc[key->key.local_id])
+      cf_error("TCP AO: Reused local key id %i", key->key.local_id);
+    used_aos_id_loc[key->key.local_id] = 1;
+    if (key->key.remote_id == -1)
+      cf_error("TCP AO: No remote key id for local id %i", key->key.local_id);
+    if (used_aos_id_rem[key->key.remote_id])
+      cf_error("TCP AO: Reused remote key id %i", key->key.remote_id);
+    used_aos_id_rem[key->key.remote_id] = 1;
+    if (!key->key.cipher)
+      cf_error("TCP AO: No cipher given for key id %i.", key->key.local_id);
+    if (!key->key.master_key)
+      cf_error("TCP AO: No master key given for key id %i.", key->key.local_id);
+    if (key->key.required == 1)
+    {
+      if (required_found)
+        cf_error("TCP AO: How do you want to use two keys at once? Check 'REQUIRED'");
+      required_found = 1;
+    }
+    key = key->next_key;
+  }
+  if (required_found == 0)
+    cf_error("TCP AO: Missing 'REQUIRED'. Which key should be used?");
+}
+
 bgp_channel_start: bgp_afi
 {
   const struct bgp_af_desc *desc = bgp_get_af_desc($1);
index 1c846cd505044d8844db1b9a1831c9f68467ec5f..289b9c3e3d6158709eb1d3e55982c080c0d04e79 100644 (file)
@@ -3507,6 +3507,32 @@ bgp_rx_packet(struct bgp_conn *conn, byte *pkt, uint len)
   }
 }
 
+int
+delete_deprecated_keys(sock *sk, struct bgp_proto *p, int new_lnext)
+{
+  struct bgp_ao_key *key = p->ao_key;
+  int ret  = 1;
+  while (key)
+  {
+    if (key->key.required == -1)
+    {
+      if (new_lnext == key->key.local_id)
+        ret = 0;
+      else
+      {
+        if (ao_delete_key(sk, p->remote_ip, -1, sk->iface, key->key.local_id, key->key.remote_id))
+          bug("TCP AO: Can not delete deprecated key %i %i on socket %i", key->key.local_id, key->key.remote_id, sk->fd);
+        key->activ_alive = 0;
+        if (ao_delete_key(p->sock->sk, p->remote_ip, -1, p->sock->sk->iface, key->key.local_id, key->key.remote_id))
+          bug("TCP AO: Can not delete deprecated key %i %i on socket %i", key->key.local_id, key->key.remote_id, p->sock->sk->fd);
+        key->passiv_alive = 0;
+      }
+    }
+    key = key->next_key;
+  }
+  return ret;
+}
+
 /**
  * bgp_rx - handle received data
  * @sk: socket
@@ -3521,6 +3547,25 @@ int
 bgp_rx(sock *sk, uint size)
 {
   struct bgp_conn *conn = sk->data;
+  if (sk->use_ao && sk->desired_ao_key != sk->last_used_ao_key)
+  {
+    int new_lnext = get_current_key_id(sk->fd);
+    if (new_lnext != sk->last_used_ao_key)
+    {
+      if (conn->hold_timer->expires != 0)
+        bgp_schedule_packet(conn, NULL, PKT_KEEPALIVE); // We might send this keepalive shortly after another. RFC says we should wait, but since reconfiguration is rare, this is harmless.
+      log(L_INFO "TCP AO: Expected key rotation: desired lnext %i, received %i", sk->desired_ao_key, new_lnext);
+      log_tcp_ao_info(sk->fd);
+
+      if (sk->proto_del_ao_key && sk->desired_ao_key == new_lnext)
+      {
+        if (delete_deprecated_keys(sk, sk->proto_del_ao_key, new_lnext))
+          sk->proto_del_ao_key = NULL;
+      }
+      sk->last_used_ao_key = new_lnext;
+    }
+  }
+
   byte *pkt_start = sk->rbuf;
   byte *end = pkt_start + size;
   uint i, len;
index 49495e13d3d6f2f580244db846220e6c73360ae7..ca4859518053f70d1f5c01452f76cb26d87e9c1f 100644 (file)
@@ -6,6 +6,8 @@
  *     Can be freely distributed and used under the terms of the GNU GPL.
  */
 
+#include "sysdep/linux/tcp-ao.h"
+
 #ifndef IPV6_MINHOPCOUNT
 #define IPV6_MINHOPCOUNT 73
 #endif
@@ -22,6 +24,7 @@
 #define TCP_MD5SIG_FLAG_PREFIX 1
 #endif
 
+
 /* We redefine the tcp_md5sig structure with different name to avoid collision with older headers */
 struct tcp_md5sig_ext {
   struct  sockaddr_storage tcpm_addr;          /* Address associated */
@@ -209,6 +212,250 @@ sk_set_md5_auth(sock *s, ip_addr local UNUSED, ip_addr remote, int pxlen, struct
   return 0;
 }
 
+void log_tcp_ao_info(int sock_fd)
+{
+  struct tcp_ao_info_opt_ext tmp;
+  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
+  socklen_t len = sizeof(tmp);
+
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
+  {
+    log(L_WARN "TCP AO: log tcp ao info failed with err code %i", errno);
+    return;
+  }
+  else
+    log(L_INFO "TCP AO on socket %i:\ncurrent key id %i (loc), next key %i (rem),\n set current %i, is ao required %i\n good packets %i, bad packets %i",
+                   sock_fd, tmp.current_key, tmp.rnext, tmp.set_current, tmp.ao_required, tmp.pkt_good, tmp.pkt_bad);
+}
+
+int get_current_key_id(int sock_fd)
+{
+  struct tcp_ao_info_opt_ext tmp;
+  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
+  socklen_t len = sizeof(tmp);
+
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
+  {
+    log(L_WARN "TCP AO: Getting current ao key for socket file descriptor %i failed with errno %i", sock_fd, errno);
+    return -1;
+  }
+  else
+    return tmp.current_key;
+}
+
+int get_rnext_key_id(int sock_fd)
+{
+  struct tcp_ao_info_opt_ext tmp;
+  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
+  socklen_t len = sizeof(tmp);
+
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
+  {
+    log(L_WARN "TCP AO: Getting rnext ao key for socket file descriptor %i failed with errno %i", sock_fd, errno);
+    return -1;
+  }
+  else
+    return tmp.rnext;
+}
+
+int get_num_ao_keys(int sock_fd)
+{
+  struct tcp_ao_getsockopt_ext tmp;
+  memset(&tmp, 0, sizeof(struct tcp_ao_getsockopt_ext));
+  socklen_t len = sizeof(tmp);
+  tmp.nkeys = 1;
+  tmp.get_all = 1;
+
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, &tmp, &len))
+  {
+    log(L_WARN "TCP AO: get keys on socket fd %i failed with err code %i", sock_fd, errno);
+    return -1;
+  }
+  return tmp.nkeys;
+}
+
+void
+log_tcp_ao_get_key(int sock_fd)
+{
+  int nkeys = get_num_ao_keys(sock_fd);
+  if (nkeys < 0)
+    return;
+  struct tcp_ao_getsockopt_ext tm_all[nkeys];
+  socklen_t len = sizeof(struct tcp_ao_getsockopt_ext);
+  memset(tm_all, 0, sizeof(struct tcp_ao_getsockopt_ext)*nkeys);
+  tm_all[0].nkeys = nkeys;
+  tm_all[0].get_all = 1;
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, tm_all, &len))  // len should be still size of one struct. Because kernel net/ipv4/tcp_ao.c line 2165
+  {
+    log(L_WARN "TCP AO: getting keys on socket fd %i failed with err code %i", sock_fd, errno);
+    return;
+  }
+  log(L_INFO "TCP AO on socket fd %i has %i keys", tm_all[0].nkeys);
+  for (int i = 0; i < nkeys; i++)
+  {
+  
+    char key_val[TCP_AO_MAXKEYLEN_*2+1];
+    for (int ik = 0; ik<TCP_AO_MAXKEYLEN_; ik++)
+      sprintf(&key_val[ik*2], "%x", tm_all[i].key[ik]);
+    key_val[TCP_AO_MAXKEYLEN_*2] = 0;
+    log(L_INFO "sndid %i rcvid %i, %s %s, cipher %s key %x (%i/%i)",
+                   tm_all[i].sndid, tm_all[i].rcvid, tm_all[i].is_current ? "current" : "",
+                   tm_all[i].is_rnext ? "rnext" : "", tm_all[i].alg_name, key_val, i+1, tm_all[0].nkeys);
+  }
+}
+
+void
+tcp_ao_get_info(int sock_fd, int key_info[4])
+{
+  struct tcp_ao_info_opt_ext tmp;
+  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
+  socklen_t len = sizeof(tmp);
+
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, &len))
+  {
+    log(L_WARN "TCP AO: log tcp ao info failed with err code %i", errno);
+    return;
+  }
+  key_info[0] = tmp.current_key;
+  key_info[1] = tmp.rnext;
+  key_info[2] = tmp.pkt_good;
+  key_info[3] = tmp.pkt_bad;
+}
+
+int
+sk_set_ao_auth(sock *s, ip_addr local UNUSED, ip_addr remote, int pxlen, struct iface *ifa, const char *passwd, int passwd_id_loc, int passwd_id_rem, const char* cipher, int set_current)
+{
+  struct tcp_ao_add_ext ao;
+  memset(&ao, 0, sizeof(struct tcp_ao_add_ext));
+  log(L_DEBUG "tcp ao: socket sets ao, password %s socket fd %i", passwd, s->fd);
+
+  sockaddr_fill((sockaddr *) &ao.addr, s->af, remote, ifa, 0);
+  if (set_current)
+  {
+    ao.set_rnext = 1;
+    ao.set_current = 1;
+  }
+  if (pxlen >= 0)
+    ao.prefix = pxlen;
+  else if(s->af == AF_INET)
+    ao.prefix = 32;
+  else
+    ao.prefix = 128;
+  ao.sndid     = passwd_id_loc;
+  ao.rcvid     = passwd_id_rem;
+  ao.maclen    = 0;
+  ao.keyflags  = 0;
+  ao.ifindex   = 0;
+
+  strncpy(ao.alg_name, (cipher) ? cipher : DEFAULT_TEST_ALGO, 64);
+  ao.keylen = strlen(passwd);
+  memcpy(ao.key, passwd, (strlen(passwd) > TCP_AO_MAXKEYLEN_) ? TCP_AO_MAXKEYLEN_ : strlen(passwd));
+
+  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_ADD_KEY, &ao, sizeof(ao)) < 0)
+  {
+    if (errno == ENOPROTOOPT)
+      ERR_MSG("Kernel does not support extended TCP AO signatures");
+    else
+      ERR("TCP_AOSIG_EXT");
+  }
+  s->use_ao = 1;
+  if (set_current)
+    s->desired_ao_key = passwd_id_loc;
+  log_tcp_ao_get_key(s->fd);
+  return 0;
+}
+
+int
+ao_delete_key(sock *s, ip_addr remote, int pxlen, struct iface *ifa, int passwd_id_loc, int passwd_id_rem)
+{
+  struct tcp_ao_del_ext del;
+  memset(&del, 0, sizeof(struct tcp_ao_del_ext));
+  sockaddr_fill((sockaddr *) &del.addr, s->af, remote, ifa, 0);
+  del.sndid = passwd_id_loc;
+  del.rcvid = passwd_id_rem;
+  if (pxlen >= 0)
+    del.prefix = pxlen;
+  else if(s->af == AF_INET)
+    del.prefix = 32;
+  else
+    del.prefix = 128;
+
+  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_DEL_KEY, &del, sizeof(del)) < 0)
+  {
+    log(L_WARN "TCP AO: deletion of key %i %i on socket fd %i failed with err %i", passwd_id_loc, passwd_id_rem, s->fd, errno);
+    return errno;
+  }
+  log(L_DEBUG "tcp ao: key %i %i deleted", passwd_id_loc, passwd_id_rem);
+  return 0;
+}
+
+void
+ao_try_change_master(sock *s, int next_master_id_loc, int next_master_id_rem)
+{
+  struct tcp_ao_info_opt_ext tmp;
+  memset(&tmp, 0, sizeof(struct tcp_ao_info_opt_ext));
+  tmp.set_rnext = 1;
+  tmp.rnext = next_master_id_rem;
+
+  if (setsockopt(s->fd, IPPROTO_TCP, TCP_AO_INFO, &tmp, sizeof(tmp)))
+  {
+     log(L_WARN "TCP AO: change master key failed with err code %i", errno);
+     log_tcp_ao_get_key(s->fd);
+     return;
+  }
+  else
+    log(L_DEBUG "tcp ao: tried to change master to %i %i", next_master_id_loc, next_master_id_rem);
+  s->desired_ao_key = next_master_id_loc;
+
+}
+
+int check_ao_keys_id(int sock_fd, struct bgp_ao_key *keys)
+{
+  int errors = 0;
+  int expected_keys[256]; //can not have char, because we must support 0 key id
+  memset(expected_keys, 0, sizeof(int)*256);
+  for (struct bgp_ao_key *key = keys; key; key = key->next_key)
+    expected_keys[key->key.local_id] = key->key.remote_id + 1; // the + 1 because we do not want 0 id be 0
+  int nkeys = get_num_ao_keys(sock_fd);
+  if (nkeys == -1)
+  {
+    log(L_WARN "TCP AO: unable to get num of keys");
+    return 1;
+  }
+  struct tcp_ao_getsockopt_ext tm_all[nkeys];
+  socklen_t len = sizeof(struct tcp_ao_getsockopt_ext);
+  memset(tm_all, 0, sizeof(struct tcp_ao_getsockopt_ext)*nkeys);
+  tm_all[0].nkeys = nkeys;
+  tm_all[0].get_all = 1;
+  if (getsockopt(sock_fd, IPPROTO_TCP, TCP_AO_GET_KEYS, tm_all, &len))  // len should be still size of one struct. Because kernel net/ipv4/tcp_ao.c line 2165
+  {
+    log(L_WARN "TCP AO: log tcp ao get keys failed with err code %i", errno);
+    return 1;
+  }
+  for (int i = 0; i< nkeys; i++)
+  {
+    struct tcp_ao_getsockopt_ext sock_key = tm_all[i];
+    if (expected_keys[sock_key.sndid] - 1 != sock_key.rcvid)
+    {
+      if (expected_keys[sock_key.rcvid] == 0)
+        log(L_WARN "TCP AO: unexpected ao key %i %i", sock_key.rcvid, sock_key.sndid);
+      else
+        log(L_WARN "TCP AO: expected key local id %i has different remote id than expected (%i vs %i)", sock_key.sndid, expected_keys[sock_key.sndid] - 1, sock_key.rcvid);
+      errors++;
+    }
+    expected_keys[sock_key.sndid] = 0;
+  }
+  for (int i = 0; i < 256; i++)
+  {
+    if (expected_keys[i] != 0)
+    {
+      log(L_WARN "TCP AO: key %i %i is not in socket", i, expected_keys - 1);
+      errors++;
+    }
+  }
+  return errors;
+}
+
 static inline int
 sk_set_min_ttl4(sock *s, int ttl)
 {
diff --git a/sysdep/linux/tcp-ao.h b/sysdep/linux/tcp-ao.h
new file mode 100644 (file)
index 0000000..6cb9274
--- /dev/null
@@ -0,0 +1,109 @@
+
+#ifndef TCP_AO_ADD_KEY
+#define TCP_AO_ADD_KEY         38      /* Add/Set MKT */
+#define TCP_AO_DEL_KEY         39      /* Delete MKT */
+#define TCP_AO_INFO            40      /* Set/list TCP-AO per-socket options */
+#define TCP_AO_GET_KEYS                41      /* List MKT(s) */
+#define TCP_AO_REPAIR          42      /* Get/Set SNEs and ISNs */
+#endif
+
+
+#ifndef TCP_AO_STRUCTS
+#define TCP_AO_STRUCTS
+
+#define TCP_AO_MAXKEYLEN_      80
+
+#define DEFAULT_TEST_ALGO      "cmac(aes128)"
+
+struct tcp_ao_add_ext { /* setsockopt(TCP_AO_ADD_KEY) */
+       struct sockaddr_storage addr;   /* peer's address for the key */
+       char    alg_name[64];           /* crypto hash algorithm to use */
+       s32     ifindex;                /* L3 dev index for VRF */
+       u32   set_current       :1,     /* set key as Current_key at once */
+               set_rnext       :1,     /* request it from peer with RNext_key */
+               reserved        :30;    /* must be 0 */
+       u16     reserved2;              /* padding, must be 0 */
+       u8      prefix;                 /* peer's address prefix */
+       u8      sndid;                  /* SendID for outgoing segments */
+       u8      rcvid;                  /* RecvID to match for incoming seg */
+       u8      maclen;                 /* length of authentication code (hash) */
+       u8      keyflags;               /* see TCP_AO_KEYF_ */
+       u8      keylen;                 /* length of ::key */
+       u8      key[TCP_AO_MAXKEYLEN_];
+} __attribute__((aligned(8)));
+
+struct tcp_ao_del_ext { /* setsockopt(TCP_AO_DEL_KEY) */
+       struct sockaddr_storage addr;   /* peer's address for the key */
+       s32     ifindex;                /* L3 dev index for VRF */
+       u32   set_current       :1,     /* corresponding ::current_key */
+               set_rnext       :1,     /* corresponding ::rnext */
+               del_async       :1,     /* only valid for listen sockets */
+               reserved        :29;    /* must be 0 */
+       u16     reserved2;              /* padding, must be 0 */
+       u8      prefix;                 /* peer's address prefix */
+       u8      sndid;                  /* SendID for outgoing segments */
+       u8      rcvid;                  /* RecvID to match for incoming seg */
+       u8      current_key;            /* KeyID to set as Current_key */
+       u8      rnext;                  /* KeyID to set as Rnext_key */
+       u8      keyflags;               /* see TCP_AO_KEYF_ */
+} __attribute__((aligned(8)));
+
+struct tcp_ao_info_opt_ext { /* setsockopt(TCP_AO_INFO), getsockopt(TCP_AO_INFO) */
+       /* Here 'in' is for setsockopt(), 'out' is for getsockopt() */
+       u32   set_current       :1,     /* in/out: corresponding ::current_key */
+               set_rnext       :1,     /* in/out: corresponding ::rnext */
+               ao_required     :1,     /* in/out: don't accept non-AO connects */
+               set_counters    :1,     /* in: set/clear ::pkt_* counters */
+               accept_icmps    :1,     /* in/out: accept incoming ICMPs */
+               reserved        :27;    /* must be 0 */
+       u16     reserved2;              /* padding, must be 0 */
+       u8      current_key;            /* in/out: KeyID of Current_key */
+       u8      rnext;                  /* in/out: keyid of RNext_key */
+       u64     pkt_good;               /* in/out: verified segments */
+       u64     pkt_bad;                /* in/out: failed verification */
+       u64     pkt_key_not_found;      /* in/out: could not find a key to verify */
+       u64     pkt_ao_required;        /* in/out: segments missing TCP-AO sign */
+       u64     pkt_dropped_icmp;       /* in/out: ICMPs that were ignored */
+} __attribute__((aligned(8)));
+
+struct tcp_ao_getsockopt_ext { /* getsockopt(TCP_AO_GET_KEYS) */
+       struct sockaddr_storage addr;   /* in/out: dump keys for peer
+                                                * with this address/prefix
+                                                */
+       char    alg_name[64];           /* out: crypto hash algorithm */
+       u8      key[TCP_AO_MAXKEYLEN_];
+       u32     nkeys;                  /* in: size of the userspace buffer
+                                        * @optval, measured in @optlen - the
+                                        * sizeof(struct tcp_ao_getsockopt)
+                                        * out: number of keys that matched
+                                        */
+       u16   is_current        :1,     /* in: match and dump Current_key,
+                                        * out: the dumped key is Current_key
+                                        */
+
+               is_rnext        :1,     /* in: match and dump RNext_key,
+                                        * out: the dumped key is RNext_key
+                                        */
+               get_all         :1,     /* in: dump all keys */
+               reserved        :13;    /* padding, must be 0 */
+       u8      sndid;                  /* in/out: dump keys with SendID */
+       u8      rcvid;                  /* in/out: dump keys with RecvID */
+       u8      prefix;                 /* in/out: dump keys with address/prefix */
+       u8      maclen;                 /* out: key's length of authentication
+                                        * code (hash)
+                                        */
+       u8      keyflags;               /* in/out: see TCP_AO_KEYF_ */
+       u8      keylen;                 /* out: length of ::key */
+       s32     ifindex;                /* in/out: L3 dev index for VRF */
+       u64     pkt_good;               /* out: verified segments */
+       u64     pkt_bad;                /* out: segments that failed verification */
+} __attribute__((aligned(8)));
+
+struct tcp_ao_repair_ext { /* {s,g}etsockopt(TCP_AO_REPAIR) */
+       u32                     snt_isn;  //should be __be32 alias fdt32_t - 32-bit, big-endian, unsigned integer
+       u32                     rcv_isn;  //should be __be32 alias fdt32_t - 32-bit, big-endian, unsigned integer
+       u32                     snd_sne;
+       u32                     rcv_sne;
+} __attribute__((aligned(8)));
+
+#endif /* TCP_AO_STRUCTS*/
index 54768d47f48bce119507e405f18aa3ac10ace407..d0910e209c5eee5ff6ba9f0043d450d79ee812dc 100644 (file)
@@ -1668,9 +1668,24 @@ sk_open(sock *s)
       ERR2("bind");
   }
 
-  if (s->password)
+  if (s->ao_key_init)
+  {
+    struct bgp_ao_key *key = s->ao_key_init;
+    do {
+      if (sk_set_ao_auth(s, s->saddr, s->daddr, -1, s->iface, key->key.master_key, key->key.local_id, key->key.remote_id, key->key.cipher, key->key.required == 1) < 0)
+        goto err;
+      if (s->type == SK_TCP_ACTIVE)
+        key->activ_alive = 1;
+      else
+        key->passiv_alive = 1;
+      key = key->next_key;
+    } while (key);
+  }
+  else if (s->password)
+  {
     if (sk_set_md5_auth(s, s->saddr, s->daddr, -1, s->iface, s->password, 0) < 0)
       goto err;
+  }
 
   switch (s->type)
   {