/* * BIRD -- Bidirectional Forwarding Detection (BFD) * * Can be freely distributed and used under the terms of the GNU GPL. */ #include "bfd.h" #include "lib/mac.h" struct bfd_ctl_packet { u8 vdiag; /* Version and diagnostic */ u8 flags; /* State and flags */ u8 detect_mult; 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; } static inline u8 bfd_pack_flags(u8 state, u8 flags) { return (state << 6) | flags; } static inline u8 bfd_pkt_get_version(struct bfd_ctl_packet *pkt) { return pkt->vdiag >> 5; } static inline u8 bfd_pkt_get_diag(struct bfd_ctl_packet *pkt) { return pkt->vdiag & 0x1f; } static inline u8 bfd_pkt_get_state(struct bfd_ctl_packet *pkt) { return pkt->flags >> 6; } static inline void UNUSED bfd_pkt_set_state(struct bfd_ctl_packet *pkt, u8 val) { pkt->flags = val << 6; } char * bfd_format_flags(u8 flags, char *buf) { char *bp = buf; if (flags & BFD_FLAGS) *bp++ = ' '; if (flags & BFD_FLAG_POLL) *bp++ = 'P'; if (flags & BFD_FLAG_FINAL) *bp++ = 'F'; if (flags & BFD_FLAG_CPI) *bp++ = 'C'; if (flags & BFD_FLAG_AP) *bp++ = 'A'; if (flags & BFD_FLAG_DEMAND) *bp++ = 'D'; if (flags & BFD_FLAG_MULTIPOINT) *bp++ = 'M'; *bp = 0; 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_session_config *cf = &s->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; /* fallthrough */ 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_session_config *cf = &s->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; /* fallthrough */ 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 * (uint) 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) { sock *sk = s->ifa->sk; struct bfd_ctl_packet *pkt; char fb[8]; if (!sk) return; pkt = (struct bfd_ctl_packet *) sk->tbuf; pkt->vdiag = bfd_pack_vdiag(1, s->loc_diag); pkt->flags = bfd_pack_flags(s->loc_state, 0); pkt->detect_mult = s->detect_mult; pkt->length = BFD_BASE_LEN; pkt->snd_id = htonl(s->loc_id); pkt->rcv_id = htonl(s->rem_id); pkt->des_min_tx_int = htonl(s->des_min_tx_new); pkt->req_min_rx_int = htonl(s->req_min_rx_new); pkt->req_min_echo_rx_int = 0; if (final) pkt->flags |= BFD_FLAG_FINAL; else if (s->poll_active) pkt->flags |= BFD_FLAG_POLL; if (s->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); TRACE(D_PACKETS, "Sending CTL to %I [%s%s]", s->addr, bfd_state_names[s->loc_state], bfd_format_flags(pkt->flags, fb)); sk_send_to(sk, pkt->length, s->addr, sk->dport); } static int bfd_rx_hook(sock *sk, uint len) { struct bfd_proto *p = sk->data; struct bfd_ctl_packet *pkt = (struct bfd_ctl_packet *) sk->rbuf; const char *err_dsc = NULL; uint err_val = 0; char fb[8]; if ((sk->sport == BFD_CONTROL_PORT) && (sk->rcv_ttl < 255)) DROP("wrong TTL", sk->rcv_ttl); if (len < BFD_BASE_LEN) DROP("too short", len); u8 version = bfd_pkt_get_version(pkt); if (version != 1) DROP("version mismatch", version); if ((pkt->length < BFD_BASE_LEN) || (pkt->length > len)) DROP("length mismatch", pkt->length); if (pkt->detect_mult == 0) DROP("invalid detect mult", 0); if ((pkt->flags & BFD_FLAG_MULTIPOINT) || ((pkt->flags & BFD_FLAG_POLL) && (pkt->flags & BFD_FLAG_FINAL))) DROP("invalid flags", pkt->flags); if (pkt->snd_id == 0) DROP("invalid my discriminator", 0); struct bfd_session *s; u32 id = ntohl(pkt->rcv_id); if (id) { s = bfd_find_session_by_id(p, id); if (!s) DROP("unknown session id", id); } else { u8 ps = bfd_pkt_get_state(pkt); if (ps > BFD_STATE_DOWN) DROP("invalid init state", ps); uint ifindex = (sk->sport == BFD_CONTROL_PORT) ? (sk->iface ? sk->iface->index : sk->lifindex) : 0; s = bfd_find_session_by_addr(p, sk->faddr, ifindex); /* FIXME: better session matching and message */ if (!s) return 1; /* For active sessions we require matching remote id */ if ((s->loc_state == BFD_STATE_UP) && (ntohl(pkt->snd_id) != s->rem_id)) DROP("mismatched remote id", ntohl(pkt->snd_id)); } /* 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; s->rem_id = ntohl(pkt->snd_id); s->rem_state = bfd_pkt_get_state(pkt); s->rem_diag = bfd_pkt_get_diag(pkt); s->rem_demand_mode = pkt->flags & BFD_FLAG_DEMAND; s->rem_min_tx_int = ntohl(pkt->des_min_tx_int); s->rem_min_rx_int = ntohl(pkt->req_min_rx_int); s->rem_detect_mult = pkt->detect_mult; TRACE(D_PACKETS, "CTL received from %I [%s%s]", sk->faddr, bfd_state_names[s->rem_state], bfd_format_flags(pkt->flags, fb)); bfd_session_process_ctl(s, pkt->flags, old_tx_int, old_rx_int); return 1; drop: LOG_PKT("Bad packet from %I - %s (%u)", sk->faddr, err_dsc, err_val); return 1; } static void bfd_err_hook(sock *sk, int err) { struct bfd_proto *p = sk->data; log(L_ERR "%s: Socket error: %m", p->p.name, err); } sock * bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af) { struct bfd_config *cf = (struct bfd_config *) (p->p.cf); sock *sk = sk_new(p->p.pool); sk->type = SK_UDP; sk->subtype = af; sk->sport = !multihop ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT; sk->vrf = p->p.vrf; sk->data = p; sk->rbsize = BFD_MAX_LEN; sk->rx_hook = bfd_rx_hook; sk->err_hook = bfd_err_hook; /* TODO: configurable ToS and priority */ sk->tos = IP_PREC_INTERNET_CONTROL; sk->priority = sk_priority_control; sk->flags = SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0); if (cf->zero_udp6_checksum_rx) sk->flags |= SKF_UDP6_NO_CSUM_RX; if (sk_open(sk, p->p.loop) < 0) goto err; return sk; err: sk_log_error(sk, p->p.name); sk_close(sk); return NULL; } sock * bfd_open_rx_sk_bound(struct bfd_proto *p, ip_addr local, struct iface *ifa) { struct bfd_config *cf = (struct bfd_config *) (p->p.cf); sock *sk = sk_new(p->tpool); sk->type = SK_UDP; sk->saddr = local; sk->sport = ifa ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT; sk->iface = ifa; sk->vrf = p->p.vrf; sk->data = p; sk->rbsize = BFD_MAX_LEN; sk->rx_hook = bfd_rx_hook; sk->err_hook = bfd_err_hook; /* TODO: configurable ToS and priority */ sk->tos = IP_PREC_INTERNET_CONTROL; sk->priority = sk_priority_control; sk->flags = SKF_BIND | (ifa ? SKF_TTL_RX : 0); if (cf->zero_udp6_checksum_rx) sk->flags |= SKF_UDP6_NO_CSUM_RX; if (sk_open(sk, p->p.loop) < 0) goto err; return sk; err: sk_log_error(sk, p->p.name); sk_close(sk); return NULL; } sock * bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa) { sock *sk = sk_new(p->p.pool); sk->type = SK_UDP; sk->saddr = local; sk->dport = ifa ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT; sk->iface = ifa; sk->vrf = p->p.vrf; sk->data = p; sk->tbsize = BFD_MAX_LEN; sk->err_hook = bfd_err_hook; /* TODO: configurable ToS, priority and TTL security */ sk->tos = IP_PREC_INTERNET_CONTROL; sk->priority = sk_priority_control; sk->ttl = ifa ? 255 : -1; sk->flags = SKF_BIND | SKF_HIGH_PORT; if (sk_open(sk, p->p.loop) < 0) goto err; return sk; err: sk_log_error(sk, p->p.name); sk_close(sk); return NULL; }