Reworked and finalized version of TCP-AO.
<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.
<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
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>;
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
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
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
#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) \
#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
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) */
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;
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 */
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);
{ 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]])
* 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
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);
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;
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 *
{ 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);
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",
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;
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;
}
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, ¤t, &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;
}
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;
err:
sk_log_error(sk, p->p.name);
+err2:
log(L_ERR "%s: Incoming connection aborted", p->p.name);
rfree(sk);
return 0;
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 */
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;
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)
}
}
-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)
{
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;
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" };
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);
}
}
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 */
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 */
#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
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 */
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 */
#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
#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,
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
| 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; }
| 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; }
| 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
}
}
-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
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;
}
+/*
+ * 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
*/
/*
- * 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
+/*
+ * 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 */
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 */
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 */
* 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
*/
u32 rcv_sne;
} __attribute__((aligned(8)));
-#endif /* TCP_AO_STRUCTS*/
+#endif
#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"
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)
{
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