bird/proto/bfd/packets.c

250 lines
5.6 KiB
C
Raw Normal View History

/*
* BIRD -- Bidirectional Forwarding Detection (BFD)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
2013-09-10 18:09:36 +08:00
#include "bfd.h"
2013-09-10 18:09:36 +08:00
struct bfd_ctl_packet
{
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' */
u32 des_min_tx_int;
u32 req_min_rx_int;
u32 req_min_echo_rx_int;
};
#define BFD_BASE_LEN sizeof(struct bfd_ctl_packet)
2013-09-10 18:09:36 +08:00
static inline u8 bfd_pack_vdiag(u8 version, u8 diag)
2013-09-10 18:09:36 +08:00
{ return (version << 5) | diag; }
static inline u8 bfd_pack_flags(u8 state, u8 flags)
{ return (state << 6) | flags; }
2013-09-10 18:09:36 +08:00
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 bfd_pkt_set_state(struct bfd_ctl_packet *pkt, u8 val)
{ pkt->flags = val << 6; }
void
bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
{
sock *sk = s->bsock->sk;
struct bfd_ctl_packet *pkt = (struct bfd_ctl_packet *) sk->tbuf;
2013-09-10 18:09:36 +08:00
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;
2013-09-10 18:09:36 +08:00
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);
2013-09-10 18:09:36 +08:00
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 (sk->tbuf != sk->tpos)
log(L_ERR "%s: old packet was overwritten in TX buffer", p->p.name);
sk_send_to(sk, pkt->length, s->addr, sk->dport);
2013-09-10 18:09:36 +08:00
}
#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
static int
bfd_rx_hook(sock *sk, int len)
2013-09-10 18:09:36 +08:00
{
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;
2013-09-10 18:09:36 +08:00
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)
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);
}
else
{
u8 ps = bfd_pkt_get_state(pkt);
if (ps > BFD_STATE_DOWN)
DROP("invalid init state", ps);
s = bfd_find_session_by_addr(p, sk->faddr);
2013-09-10 18:09:36 +08:00
/* FIXME: better session matching and message */
if (!s || !s->opened)
return 1;
2013-09-10 18:09:36 +08:00
}
/* FIXME: better authentication handling and message */
if (pkt->flags & BFD_FLAG_AP)
DROP("authentication not supported", 0);
u32 old_tx_int = s->des_min_tx_int;
u32 old_rx_int = s->rem_min_rx_int;
2013-09-10 18:09:36 +08:00
s->rem_id = ntohl(pkt->snd_id);
s->rem_state = bfd_pkt_get_state(pkt);
s->rem_diag = bfd_pkt_get_diag(pkt);
2013-09-10 18:09:36 +08:00
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;
bfd_session_process_ctl(s, pkt->flags, old_tx_int, old_rx_int);
2013-09-10 18:09:36 +08:00
return 1;
drop:
// log(L_WARN "%s: Bad packet from %I - %s (%u)", p->p.name, 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);
}
2013-09-10 18:09:36 +08:00
sock *
bfd_open_rx_sk(struct bfd_proto *p, int multihop)
{
sock *sk = sk_new(p->tpool);
2013-09-10 18:09:36 +08:00
sk->type = SK_UDP;
sk->sport = !multihop ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT;
sk->data = p;
sk->rbsize = 64; // XXX
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_THREAD | SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0);
#ifdef IPV6
sk->flags |= SKF_V6ONLY;
#endif
2013-09-10 18:09:36 +08:00
if (sk_open(sk) < 0)
goto err;
sk_start(sk);
return sk;
err:
rfree(sk);
return NULL;
2013-09-10 18:09:36 +08:00
}
static inline sock *
bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa)
{
sock *sk = sk_new(p->tpool);
2013-09-10 18:09:36 +08:00
sk->type = SK_UDP;
sk->saddr = local;
sk->dport = ifa ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT;
sk->iface = ifa;
2013-09-10 18:09:36 +08:00
sk->data = p;
sk->tbsize = 64; // XXX
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_THREAD;
#ifdef IPV6
sk->flags |= SKF_V6ONLY;
#endif
2013-09-10 18:09:36 +08:00
if (sk_open(sk) < 0)
goto err;
sk_start(sk);
return sk;
err:
rfree(sk);
return NULL;
2013-09-10 18:09:36 +08:00
}
struct bfd_socket *
bfd_get_socket(struct bfd_proto *p, ip_addr local, struct iface *ifa)
{
struct bfd_socket *sk;
WALK_LIST(sk, p->sock_list)
2013-09-10 18:09:36 +08:00
if (ipa_equal(sk->sk->saddr, local) && (sk->sk->iface == ifa))
return sk->uc++, sk;
sk = mb_allocz(p->tpool, sizeof(struct bfd_socket));
2013-09-10 18:09:36 +08:00
sk->sk = bfd_open_tx_sk(p, local, ifa);
sk->uc = 1;
add_tail(&p->sock_list, &sk->n);
2013-09-10 18:09:36 +08:00
return sk;
}
void
bfd_free_socket(struct bfd_socket *sk)
{
if (!sk || --sk->uc)
return;
rem_node(&sk->n);
sk_stop(sk->sk);
rfree(sk->sk);
mb_free(sk);
}