summaryrefslogtreecommitdiff
path: root/proto/bfd
diff options
context:
space:
mode:
authorOndrej Zajicek (work) <santiago@crfreenet.org>2016-10-30 23:51:23 +0100
committerOndrej Zajicek (work) <santiago@crfreenet.org>2016-11-02 17:53:22 +0100
commite03dc6a984555e3c943735d50376cada2220bac8 (patch)
tree7f0c54682b71200d8db1390caeb09b2056ba2853 /proto/bfd
parent29239ba2bbee3e9ec7d17793b25936a1bfc795ca (diff)
BFD: Authentication
Implement BFD authentication (part of RFC 5880). Supports plaintext passwords and cryptographic MD5 / SHA-1 authentication. Based on former commit from Pavel Tvrdik
Diffstat (limited to 'proto/bfd')
-rw-r--r--proto/bfd/bfd.c2
-rw-r--r--proto/bfd/bfd.h19
-rw-r--r--proto/bfd/config.Y46
-rw-r--r--proto/bfd/packets.c245
4 files changed, 293 insertions, 19 deletions
diff --git a/proto/bfd/bfd.c b/proto/bfd/bfd.c
index e7be6a8a..79135fae 100644
--- a/proto/bfd/bfd.c
+++ b/proto/bfd/bfd.c
@@ -316,6 +316,7 @@ bfd_session_timeout(struct bfd_session *s)
s->rem_min_rx_int = 1;
s->rem_demand_mode = 0;
s->rem_detect_mult = 0;
+ s->rx_csn_known = 0;
s->poll_active = 0;
s->poll_scheduled = 0;
@@ -429,6 +430,7 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
s->rem_min_rx_int = 1;
s->detect_mult = ifa->cf->multiplier;
s->passive = ifa->cf->passive;
+ s->tx_csn = random_u32();
s->tx_timer = tm2_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0);
s->hold_timer = tm2_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0);
diff --git a/proto/bfd/bfd.h b/proto/bfd/bfd.h
index 9b61be64..46e09879 100644
--- a/proto/bfd/bfd.h
+++ b/proto/bfd/bfd.h
@@ -14,6 +14,7 @@
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/route.h"
+#include "nest/password.h"
#include "conf/conf.h"
#include "lib/hash.h"
#include "lib/resource.h"
@@ -52,6 +53,8 @@ struct bfd_iface_config
u32 idle_tx_int;
u8 multiplier;
u8 passive;
+ u8 auth_type; /* Authentication type (BFD_AUTH_*) */
+ list *passwords; /* Passwords for authentication */
};
struct bfd_neighbor
@@ -114,7 +117,7 @@ struct bfd_session
u8 passive;
u8 poll_active;
u8 poll_scheduled;
-
+
u8 loc_state;
u8 rem_state;
u8 loc_diag;
@@ -141,6 +144,11 @@ struct bfd_session
list request_list; /* List of client requests (struct bfd_request) */
bird_clock_t last_state_change; /* Time of last state change */
u8 notify_running; /* 1 if notify hooks are running */
+
+ u8 rx_csn_known; /* Received crypto sequence number is known */
+ u32 rx_csn; /* Last received crypto sequence number */
+ u32 tx_csn; /* Last transmitted crypto sequence number */
+ u32 tx_csn_time; /* Timestamp of last tx_csn change */
};
@@ -172,6 +180,15 @@ extern const char *bfd_state_names[];
#define BFD_FLAG_DEMAND (1 << 1)
#define BFD_FLAG_MULTIPOINT (1 << 0)
+#define BFD_AUTH_NONE 0
+#define BFD_AUTH_SIMPLE 1
+#define BFD_AUTH_KEYED_MD5 2
+#define BFD_AUTH_METICULOUS_KEYED_MD5 3
+#define BFD_AUTH_KEYED_SHA1 4
+#define BFD_AUTH_METICULOUS_KEYED_SHA1 5
+
+extern const u8 bfd_auth_type_to_hash_alg[];
+
static inline void bfd_lock_sessions(struct bfd_proto *p) { pthread_spin_lock(&p->lock); }
static inline void bfd_unlock_sessions(struct bfd_proto *p) { pthread_spin_unlock(&p->lock); }
diff --git a/proto/bfd/config.Y b/proto/bfd/config.Y
index 4affb927..73414362 100644
--- a/proto/bfd/config.Y
+++ b/proto/bfd/config.Y
@@ -22,11 +22,12 @@ extern struct bfd_config *bfd_cf;
CF_DECLS
CF_KEYWORDS(BFD, MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE,
- INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL)
+ INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL, AUTHENTICATION,
+ NONE, SIMPLE, METICULOUS, KEYED, MD5, SHA1)
%type <iface> bfd_neigh_iface
%type <a> bfd_neigh_local
-%type <i> bfd_neigh_multihop
+%type <i> bfd_neigh_multihop bfd_auth_type
CF_GRAMMAR
@@ -62,12 +63,35 @@ bfd_proto:
bfd_iface_start:
{
this_ipatt = cfg_allocz(sizeof(struct bfd_iface_config));
+ add_tail(&BFD_CFG->patt_list, NODE this_ipatt);
init_list(&this_ipatt->ipn_list);
BFD_IFACE->min_rx_int = BFD_DEFAULT_MIN_RX_INT;
BFD_IFACE->min_tx_int = BFD_DEFAULT_MIN_TX_INT;
BFD_IFACE->idle_tx_int = BFD_DEFAULT_IDLE_TX_INT;
BFD_IFACE->multiplier = BFD_DEFAULT_MULTIPLIER;
+
+ reset_passwords();
+};
+
+bfd_iface_finish:
+{
+ BFD_IFACE->passwords = get_passwords();
+
+ if (!BFD_IFACE->auth_type != !BFD_IFACE->passwords)
+ log(L_WARN "Authentication and password options should be used together");
+
+ if (BFD_IFACE->passwords)
+ {
+ struct password_item *pass;
+ WALK_LIST(pass, *BFD_IFACE->passwords)
+ {
+ if (pass->alg)
+ cf_error("Password algorithm option not available in BFD protocol");
+
+ pass->alg = bfd_auth_type_to_hash_alg[BFD_IFACE->auth_type];
+ }
+ }
};
bfd_iface_item:
@@ -77,6 +101,17 @@ bfd_iface_item:
| IDLE TX INTERVAL expr_us { BFD_IFACE->idle_tx_int = $4; }
| MULTIPLIER expr { BFD_IFACE->multiplier = $2; }
| PASSIVE bool { BFD_IFACE->passive = $2; }
+ | AUTHENTICATION bfd_auth_type { BFD_IFACE->auth_type = $2; }
+ | password_list {}
+ ;
+
+bfd_auth_type:
+ NONE { $$ = BFD_AUTH_NONE; }
+ | SIMPLE { $$ = BFD_AUTH_SIMPLE; }
+ | KEYED MD5 { $$ = BFD_AUTH_KEYED_MD5; }
+ | KEYED SHA1 { $$ = BFD_AUTH_KEYED_SHA1; }
+ | METICULOUS KEYED MD5 { $$ = BFD_AUTH_METICULOUS_KEYED_MD5; }
+ | METICULOUS KEYED SHA1 { $$ = BFD_AUTH_METICULOUS_KEYED_SHA1; }
;
bfd_iface_opts:
@@ -89,10 +124,11 @@ bfd_iface_opt_list:
| '{' bfd_iface_opts '}'
;
-bfd_iface: bfd_iface_start iface_patt_list_nopx bfd_iface_opt_list
-{ add_tail(&BFD_CFG->patt_list, NODE this_ipatt); };
+bfd_iface:
+ bfd_iface_start iface_patt_list_nopx bfd_iface_opt_list bfd_iface_finish;
-bfd_multihop: bfd_iface_start bfd_iface_opt_list
+bfd_multihop:
+ bfd_iface_start bfd_iface_opt_list bfd_iface_finish
{ BFD_CFG->multihop = BFD_IFACE; };
diff --git a/proto/bfd/packets.c b/proto/bfd/packets.c
index deb501fc..129db72f 100644
--- a/proto/bfd/packets.c
+++ b/proto/bfd/packets.c
@@ -5,24 +5,60 @@
*/
#include "bfd.h"
+#include "lib/mac.h"
struct bfd_ctl_packet
{
- u8 vdiag; /* version and diagnostic */
- u8 flags; /* state and flags */
+ u8 vdiag; /* Version and diagnostic */
+ u8 flags; /* State and flags */
u8 detect_mult;
- u8 length;
- u32 snd_id; /* sender ID, aka 'my discriminator' */
- u32 rcv_id; /* receiver ID, aka 'your discriminator' */
+ u8 length; /* Whole packet length */
+ u32 snd_id; /* Sender ID, aka 'my discriminator' */
+ u32 rcv_id; /* Receiver ID, aka 'your discriminator' */
u32 des_min_tx_int;
u32 req_min_rx_int;
u32 req_min_echo_rx_int;
};
+struct bfd_auth
+{
+ u8 type; /* Authentication type (BFD_AUTH_*) */
+ u8 length; /* Authentication section length */
+};
+
+struct bfd_simple_auth
+{
+ u8 type; /* BFD_AUTH_SIMPLE */
+ u8 length; /* Length of bfd_simple_auth + pasword length */
+ u8 key_id; /* Key ID */
+ byte password[0]; /* Password itself, variable length */
+};
+
+#define BFD_MAX_PASSWORD_LENGTH 16
+
+struct bfd_crypto_auth
+{
+ u8 type; /* BFD_AUTH_*_MD5 or BFD_AUTH_*_SHA1 */
+ u8 length; /* Length of bfd_crypto_auth + hash length */
+ u8 key_id; /* Key ID */
+ u8 zero; /* Reserved, zero on transmit */
+ u32 csn; /* Cryptographic sequence number */
+ byte data[0]; /* Authentication key/hash, length 16 or 20 */
+};
+
#define BFD_BASE_LEN sizeof(struct bfd_ctl_packet)
#define BFD_MAX_LEN 64
+#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
+
+#define LOG_PKT(msg, args...) \
+ log(L_REMOTE "%s: " msg, p->p.name, args)
+
+#define LOG_PKT_AUTH(msg, args...) \
+ log(L_AUTH "%s: " msg, p->p.name, args)
+
+
static inline u8 bfd_pack_vdiag(u8 version, u8 diag)
{ return (version << 5) | diag; }
@@ -59,6 +95,189 @@ bfd_format_flags(u8 flags, char *buf)
return buf;
}
+const u8 bfd_auth_type_to_hash_alg[] = {
+ [BFD_AUTH_NONE] = ALG_UNDEFINED,
+ [BFD_AUTH_SIMPLE] = ALG_UNDEFINED,
+ [BFD_AUTH_KEYED_MD5] = ALG_MD5,
+ [BFD_AUTH_METICULOUS_KEYED_MD5] = ALG_MD5,
+ [BFD_AUTH_KEYED_SHA1] = ALG_SHA1,
+ [BFD_AUTH_METICULOUS_KEYED_SHA1] = ALG_SHA1,
+};
+
+
+/* Fill authentication section and modifies final length in control section packet */
+static void
+bfd_fill_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt)
+{
+ struct bfd_iface_config *cf = s->ifa->cf;
+ struct password_item *pass = password_find(cf->passwords, 0);
+ uint meticulous = 0;
+
+ if (!pass)
+ {
+ /* FIXME: This should not happen */
+ log(L_ERR "%s: No suitable password found for authentication", p->p.name);
+ return;
+ }
+
+ switch (cf->auth_type)
+ {
+ case BFD_AUTH_SIMPLE:
+ {
+ struct bfd_simple_auth *auth = (void *) (pkt + 1);
+ uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH);
+
+ auth->type = BFD_AUTH_SIMPLE;
+ auth->length = sizeof(struct bfd_simple_auth) + pass_len;
+ auth->key_id = pass->id;
+
+ pkt->flags |= BFD_FLAG_AP;
+ pkt->length += auth->length;
+
+ memcpy(auth->password, pass->password, pass_len);
+ return;
+ }
+
+ case BFD_AUTH_METICULOUS_KEYED_MD5:
+ case BFD_AUTH_METICULOUS_KEYED_SHA1:
+ meticulous = 1;
+
+ case BFD_AUTH_KEYED_MD5:
+ case BFD_AUTH_KEYED_SHA1:
+ {
+ struct bfd_crypto_auth *auth = (void *) (pkt + 1);
+ uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type];
+ uint hash_len = mac_type_length(pass->alg);
+
+ /* Increase CSN about one time per second */
+ u32 new_time = (u64) current_time() >> 20;
+ if ((new_time != s->tx_csn_time) || meticulous)
+ {
+ s->tx_csn++;
+ s->tx_csn_time = new_time;
+ }
+
+ DBG("[%I] CSN: %u\n", s->addr, s->last_tx_csn);
+
+ auth->type = cf->auth_type;
+ auth->length = sizeof(struct bfd_crypto_auth) + hash_len;
+ auth->key_id = pass->id;
+ auth->zero = 0;
+ auth->csn = htonl(s->tx_csn);
+
+ pkt->flags |= BFD_FLAG_AP;
+ pkt->length += auth->length;
+
+ strncpy(auth->data, pass->password, hash_len);
+ mac_fill(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth->data);
+ return;
+ }
+ }
+}
+
+static int
+bfd_check_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt)
+{
+ struct bfd_iface_config *cf = s->ifa->cf;
+ const char *err_dsc = NULL;
+ uint err_val = 0;
+ uint auth_type = 0;
+ uint meticulous = 0;
+
+ if (pkt->flags & BFD_FLAG_AP)
+ {
+ struct bfd_auth *auth = (void *) (pkt + 1);
+
+ if ((pkt->length < (BFD_BASE_LEN + sizeof(struct bfd_auth))) ||
+ (pkt->length < (BFD_BASE_LEN + auth->length)))
+ DROP("packet length mismatch", pkt->length);
+
+ /* Zero is reserved, we use it as BFD_AUTH_NONE internally */
+ if (auth->type == 0)
+ DROP("reserved authentication type", 0);
+
+ auth_type = auth->type;
+ }
+
+ if (auth_type != cf->auth_type)
+ DROP("authentication method mismatch", auth_type);
+
+ switch (auth_type)
+ {
+ case BFD_AUTH_NONE:
+ return 1;
+
+ case BFD_AUTH_SIMPLE:
+ {
+ struct bfd_simple_auth *auth = (void *) (pkt + 1);
+
+ if (auth->length < sizeof(struct bfd_simple_auth))
+ DROP("wrong authentication length", auth->length);
+
+ struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id);
+ if (!pass)
+ DROP("no suitable password found", auth->key_id);
+
+ uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH);
+ uint auth_len = sizeof(struct bfd_simple_auth) + pass_len;
+
+ if ((auth->length != auth_len) || memcmp(auth->password, pass->password, pass_len))
+ DROP("wrong password", pass->id);
+
+ return 1;
+ }
+
+ case BFD_AUTH_METICULOUS_KEYED_MD5:
+ case BFD_AUTH_METICULOUS_KEYED_SHA1:
+ meticulous = 1;
+
+ case BFD_AUTH_KEYED_MD5:
+ case BFD_AUTH_KEYED_SHA1:
+ {
+ struct bfd_crypto_auth *auth = (void *) (pkt + 1);
+ uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type];
+ uint hash_len = mac_type_length(hash_alg);
+
+ if (auth->length != (sizeof(struct bfd_crypto_auth) + hash_len))
+ DROP("wrong authentication length", auth->length);
+
+ struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id);
+ if (!pass)
+ DROP("no suitable password found", auth->key_id);
+
+ /* BFD CSNs are in 32-bit circular number space */
+ u32 csn = ntohl(auth->csn);
+ if (s->rx_csn_known &&
+ (((csn - s->rx_csn) > (3 * s->detect_mult)) ||
+ (meticulous && (csn == s->rx_csn))))
+ {
+ /* We want to report both new and old CSN */
+ LOG_PKT_AUTH("Authentication failed for %I - "
+ "wrong sequence number (rcv %u, old %u)",
+ s->addr, csn, s->rx_csn);
+ return 0;
+ }
+
+ byte *auth_data = alloca(hash_len);
+ memcpy(auth_data, auth->data, hash_len);
+ strncpy(auth->data, pass->password, hash_len);
+
+ if (!mac_verify(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth_data))
+ DROP("wrong authentication code", pass->id);
+
+ s->rx_csn = csn;
+ s->rx_csn_known = 1;
+
+ return 1;
+ }
+ }
+
+drop:
+ LOG_PKT_AUTH("Authentication failed for %I - %s (%u)",
+ s->addr, err_dsc, err_val);
+ return 0;
+}
+
void
bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
{
@@ -85,6 +304,9 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
else if (s->poll_active)
pkt->flags |= BFD_FLAG_POLL;
+ if (s->ifa->cf->auth_type)
+ bfd_fill_authentication(p, s, pkt);
+
if (sk->tbuf != sk->tpos)
log(L_WARN "%s: Old packet overwritten in TX buffer", p->p.name);
@@ -94,8 +316,6 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
sk_send_to(sk, pkt->length, s->addr, sk->dport);
}
-#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
-
static int
bfd_rx_hook(sock *sk, uint len)
{
@@ -151,10 +371,9 @@ bfd_rx_hook(sock *sk, uint len)
return 1;
}
- /* FIXME: better authentication handling and message */
- if (pkt->flags & BFD_FLAG_AP)
- DROP("authentication not supported", 0);
-
+ /* bfd_check_authentication() has its own error logging */
+ if (!bfd_check_authentication(p, s, pkt))
+ return 1;
u32 old_tx_int = s->des_min_tx_int;
u32 old_rx_int = s->rem_min_rx_int;
@@ -173,8 +392,8 @@ bfd_rx_hook(sock *sk, uint len)
bfd_session_process_ctl(s, pkt->flags, old_tx_int, old_rx_int);
return 1;
- drop:
- log(L_REMOTE "%s: Bad packet from %I - %s (%u)", p->p.name, sk->faddr, err_dsc, err_val);
+drop:
+ LOG_PKT("Bad packet from %I - %s (%u)", sk->faddr, err_dsc, err_val);
return 1;
}