frr/ldpd/hello.c

587 lines
16 KiB
C

// SPDX-License-Identifier: ISC
/* $OpenBSD$ */
/*
* Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
* Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
*/
#include <zebra.h>
#include "ldpd.h"
#include "ldpe.h"
#include "log.h"
#include "ldp_debug.h"
static int gen_hello_prms_tlv(struct ibuf *buf, uint16_t, uint16_t);
static int gen_opt4_hello_prms_tlv(struct ibuf *, uint16_t, uint32_t);
static int gen_opt16_hello_prms_tlv(struct ibuf *, uint16_t, uint8_t *);
static int gen_ds_hello_prms_tlv(struct ibuf *, uint32_t);
static int tlv_decode_hello_prms(char *, uint16_t, uint16_t *, uint16_t *);
static int tlv_decode_opt_hello_prms(char *, uint16_t, int *, int,
union ldpd_addr *, uint32_t *, uint16_t *);
int
send_hello(enum hello_type type, struct iface_af *ia, struct tnbr *tnbr)
{
int af;
union ldpd_addr dst;
uint16_t size, holdtime = 0, flags = 0;
int fd = 0;
struct ibuf *buf;
int err = 0;
switch (type) {
case HELLO_LINK:
af = ia->af;
holdtime = if_get_hello_holdtime(ia);
flags = 0;
fd = (ldp_af_global_get(&global, af))->ldp_disc_socket;
/* multicast destination address */
switch (af) {
case AF_INET:
if (!CHECK_FLAG(leconf->ipv4.flags, F_LDPD_AF_NO_GTSM))
SET_FLAG(flags, F_HELLO_GTSM);
dst.v4 = global.mcast_addr_v4;
break;
case AF_INET6:
dst.v6 = global.mcast_addr_v6;
break;
default:
fatalx("send_hello: unknown af");
}
break;
case HELLO_TARGETED:
af = tnbr->af;
holdtime = tnbr_get_hello_holdtime(tnbr);
flags = F_HELLO_TARGETED;
if (CHECK_FLAG(tnbr->flags, F_TNBR_CONFIGURED) ||
tnbr->pw_count ||
tnbr->rlfa_count)
flags |= F_HELLO_REQ_TARG;
fd = (ldp_af_global_get(&global, af))->ldp_edisc_socket;
/* unicast destination address */
dst = tnbr->addr;
break;
default:
fatalx("send_hello: unknown hello type");
}
/* calculate message size */
size = LDP_HDR_SIZE + LDP_MSG_SIZE + sizeof(struct hello_prms_tlv);
switch (af) {
case AF_INET:
size += sizeof(struct hello_prms_opt4_tlv);
break;
case AF_INET6:
size += sizeof(struct hello_prms_opt16_tlv);
break;
default:
fatalx("send_hello: unknown af");
}
size += sizeof(struct hello_prms_opt4_tlv);
if (ldp_is_dual_stack(leconf))
size += sizeof(struct hello_prms_opt4_tlv);
/* generate message */
if ((buf = ibuf_open(size)) == NULL)
fatal(__func__);
SET_FLAG(err, gen_ldp_hdr(buf, size));
size -= LDP_HDR_SIZE;
SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_HELLO, size));
SET_FLAG(err, gen_hello_prms_tlv(buf, holdtime, flags));
/*
* RFC 7552 - Section 6.1:
* "An LSR MUST include only the transport address whose address
* family is the same as that of the IP packet carrying the Hello
* message".
*/
switch (af) {
case AF_INET:
SET_FLAG(err, gen_opt4_hello_prms_tlv(buf, TLV_TYPE_IPV4TRANSADDR,
leconf->ipv4.trans_addr.v4.s_addr));
break;
case AF_INET6:
SET_FLAG(err, gen_opt16_hello_prms_tlv(buf, TLV_TYPE_IPV6TRANSADDR,
leconf->ipv6.trans_addr.v6.s6_addr));
break;
default:
fatalx("send_hello: unknown af");
}
SET_FLAG(err, gen_opt4_hello_prms_tlv(buf, TLV_TYPE_CONFIG,
htonl(global.conf_seqnum)));
/*
* RFC 7552 - Section 6.1.1:
* "A Dual-stack LSR (i.e., an LSR supporting Dual-stack LDP for a peer)
* MUST include the Dual-Stack capability TLV in all of its LDP Hellos".
*/
if (ldp_is_dual_stack(leconf))
SET_FLAG(err, gen_ds_hello_prms_tlv(buf, leconf->trans_pref));
if (err) {
ibuf_free(buf);
return (-1);
}
switch (type) {
case HELLO_LINK:
debug_hello_send("iface %s (%s) holdtime %u", ia->iface->name,
af_name(ia->af), holdtime);
break;
case HELLO_TARGETED:
debug_hello_send("targeted-neighbor %s (%s) holdtime %u",
log_addr(tnbr->af, &tnbr->addr), af_name(tnbr->af),
holdtime);
break;
default:
fatalx("send_hello: unknown hello type");
}
send_packet(fd, af, &dst, ia, buf->buf, buf->wpos);
ibuf_free(buf);
return (0);
}
void
recv_hello(struct in_addr lsr_id, struct ldp_msg *msg, int af,
union ldpd_addr *src, struct iface *iface, int multicast, char *buf,
uint16_t len)
{
struct adj *adj = NULL;
struct nbr *nbr, *nbrt;
uint16_t holdtime = 0, flags = 0;
int tlvs_rcvd;
int ds_tlv;
union ldpd_addr trans_addr;
ifindex_t scope_id = 0;
uint32_t conf_seqnum;
uint16_t trans_pref;
int r;
struct hello_source source;
struct iface_af *ia = NULL;
struct tnbr *tnbr = NULL;
r = tlv_decode_hello_prms(buf, len, &holdtime, &flags);
if (r == -1) {
log_debug("%s: lsr-id %pI4: failed to decode params", __func__, &lsr_id);
return;
}
/* safety checks */
if (holdtime != 0 && holdtime < MIN_HOLDTIME) {
log_debug("%s: lsr-id %pI4: invalid hello holdtime (%u)",
__func__, &lsr_id, holdtime);
return;
}
if (multicast && CHECK_FLAG(flags, F_HELLO_TARGETED)) {
log_debug("%s: lsr-id %pI4: multicast targeted hello", __func__, &lsr_id);
return;
}
if (!multicast && !CHECK_FLAG(flags, F_HELLO_TARGETED)) {
log_debug("%s: lsr-id %pI4: unicast link hello", __func__, &lsr_id);
return;
}
buf += r;
len -= r;
r = tlv_decode_opt_hello_prms(buf, len, &tlvs_rcvd, af, &trans_addr,
&conf_seqnum, &trans_pref);
if (r == -1) {
log_debug("%s: lsr-id %pI4: failed to decode optional params",
__func__, &lsr_id);
return;
}
if (r != len) {
log_debug("%s: lsr-id %pI4: unexpected data in message",
__func__, &lsr_id);
return;
}
ds_tlv = CHECK_FLAG(tlvs_rcvd, F_HELLO_TLV_RCVD_DS) ? 1 : 0;
/* implicit transport address */
if (!CHECK_FLAG(tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR))
trans_addr = *src;
if (bad_addr(af, &trans_addr)) {
log_debug("%s: lsr-id %pI4: invalid transport address %s",
__func__, &lsr_id, log_addr(af, &trans_addr));
return;
}
if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&trans_addr.v6)) {
/*
* RFC 7552 - Section 6.1:
* "An LSR MUST use a global unicast IPv6 address in an IPv6
* Transport Address optional object of outgoing targeted
* Hellos and check for the same in incoming targeted Hellos
* (i.e., MUST discard the targeted Hello if it failed the
* check)".
*/
if (CHECK_FLAG(flags, F_HELLO_TARGETED)) {
log_debug("%s: lsr-id %pI4: invalid targeted hello transport address %s", __func__, &lsr_id,
log_addr(af, &trans_addr));
return;
}
scope_id = iface->ifindex;
}
memset(&source, 0, sizeof(source));
if (CHECK_FLAG(flags, F_HELLO_TARGETED)) {
/*
* RFC 7552 - Section 5.2:
* "The link-local IPv6 addresses MUST NOT be used as the
* targeted LDP Hello packet's source or destination addresses".
*/
if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&src->v6)) {
log_debug("%s: lsr-id %pI4: targeted hello with link-local source address", __func__,
&lsr_id);
return;
}
tnbr = tnbr_find(leconf, af, src);
/* remove the dynamic tnbr if the 'R' bit was cleared */
if (tnbr &&
CHECK_FLAG(tnbr->flags, F_TNBR_DYNAMIC) &&
!CHECK_FLAG(flags, F_HELLO_REQ_TARG)) {
UNSET_FLAG(tnbr->flags, F_TNBR_DYNAMIC);
tnbr = tnbr_check(leconf, tnbr);
}
if (!tnbr) {
struct ldpd_af_conf *af_conf;
if (!CHECK_FLAG(flags, F_HELLO_REQ_TARG))
return;
af_conf = ldp_af_conf_get(leconf, af);
if (!CHECK_FLAG(af_conf->flags, F_LDPD_AF_THELLO_ACCEPT))
return;
if (ldpe_acl_check(af_conf->acl_thello_accept_from, af,
src, (af == AF_INET) ? 32 : 128) != FILTER_PERMIT)
return;
tnbr = tnbr_new(af, src);
SET_FLAG(tnbr->flags, F_TNBR_DYNAMIC);
tnbr_update(tnbr);
RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr);
}
source.type = HELLO_TARGETED;
source.target = tnbr;
} else {
ia = iface_af_get(iface, af);
source.type = HELLO_LINK;
source.link.ia = ia;
source.link.src_addr = *src;
}
debug_hello_recv("%s lsr-id %pI4 transport-address %s holdtime %u%s",
log_hello_src(&source), &lsr_id, log_addr(af, &trans_addr),
holdtime, (ds_tlv) ? " (dual stack TLV present)" : "");
adj = adj_find(lsr_id, &source);
if (adj && adj->ds_tlv != ds_tlv) {
/*
* Transient condition, ignore packet and wait until adjacency
* times out.
*/
return;
}
nbr = nbr_find_ldpid(lsr_id.s_addr);
/* check dual-stack tlv */
if (ds_tlv && trans_pref != leconf->trans_pref) {
/*
* RFC 7552 - Section 6.1.1:
* "If the Dual-Stack capability TLV is present and the remote
* preference does not match the local preference (or does not
* get recognized), then the LSR MUST discard the Hello message
* and log an error.
* If an LDP session was already in place, then the LSR MUST
* send a fatal Notification message with status code of
* 'Transport Connection Mismatch' and reset the session".
*/
log_debug("%s: lsr-id %pI4: remote transport preference does not match the local preference", __func__, &lsr_id);
if (nbr)
session_shutdown(nbr, S_TRANS_MISMTCH, msg->id, msg->type);
if (adj)
adj_del(adj, S_SHUTDOWN);
return;
}
/*
* Check for noncompliant dual-stack neighbor according to
* RFC 7552 section 6.1.1.
*/
if (nbr && !ds_tlv) {
switch (af) {
case AF_INET:
if (nbr_adj_count(nbr, AF_INET6) > 0) {
session_shutdown(nbr, S_DS_NONCMPLNCE, msg->id, msg->type);
return;
}
break;
case AF_INET6:
if (nbr_adj_count(nbr, AF_INET) > 0) {
session_shutdown(nbr, S_DS_NONCMPLNCE, msg->id, msg->type);
return;
}
break;
default:
fatalx("recv_hello: unknown af");
}
}
/*
* Protections against misconfigured networks and buggy implementations.
*/
if (nbr && nbr->af == af &&
(ldp_addrcmp(af, &nbr->raddr, &trans_addr) ||
nbr->raddr_scope != scope_id)) {
log_warnx("%s: lsr-id %pI4: hello packet advertising a different transport address", __func__, &lsr_id);
if (adj)
adj_del(adj, S_SHUTDOWN);
return;
}
if (nbr == NULL) {
nbrt = nbr_find_addr(af, &trans_addr);
if (nbrt) {
log_debug("%s: transport address %s is already being used by lsr-id %pI4", __func__, log_addr(af,
&trans_addr), &nbrt->id);
if (adj)
adj_del(adj, S_SHUTDOWN);
return;
}
}
if (adj == NULL) {
adj = adj_new(lsr_id, &source, &trans_addr);
if (nbr) {
adj->nbr = nbr;
RB_INSERT(nbr_adj_head, &nbr->adj_tree, adj);
}
ldp_sync_fsm_adj_event(adj, LDP_SYNC_EVT_ADJ_NEW);
}
adj->ds_tlv = ds_tlv;
/*
* If the hello adjacency's address-family doesn't match the local
* preference, then an adjacency is still created but we don't attempt
* to start an LDP session.
*/
if (nbr == NULL && (!ds_tlv ||
((trans_pref == DUAL_STACK_LDPOV4 && af == AF_INET) ||
(trans_pref == DUAL_STACK_LDPOV6 && af == AF_INET6))))
nbr = nbr_new(lsr_id, af, ds_tlv, &trans_addr, scope_id);
/* dynamic LDPv4 GTSM negotiation as per RFC 6720 */
if (nbr) {
if (CHECK_FLAG(flags, F_HELLO_GTSM))
SET_FLAG(nbr->flags, F_NBR_GTSM_NEGOTIATED);
else
UNSET_FLAG(nbr->flags, F_NBR_GTSM_NEGOTIATED);
}
/* update neighbor's configuration sequence number */
if (nbr && (tlvs_rcvd & F_HELLO_TLV_RCVD_CONF)) {
if (conf_seqnum > nbr->conf_seqnum && nbr_pending_idtimer(nbr))
nbr_stop_idtimer(nbr);
nbr->conf_seqnum = conf_seqnum;
}
/* always update the holdtime to properly handle runtime changes */
switch (source.type) {
case HELLO_LINK:
if (holdtime == 0)
holdtime = LINK_DFLT_HOLDTIME;
adj->holdtime = MIN(if_get_hello_holdtime(ia), holdtime);
break;
case HELLO_TARGETED:
if (holdtime == 0)
holdtime = TARGETED_DFLT_HOLDTIME;
adj->holdtime = MIN(tnbr_get_hello_holdtime(tnbr), holdtime);
}
if (adj->holdtime != INFINITE_HOLDTIME)
adj_start_itimer(adj);
else
adj_stop_itimer(adj);
if (nbr && nbr->state == NBR_STA_PRESENT && !nbr_pending_idtimer(nbr) &&
nbr_session_active_role(nbr) && !nbr_pending_connect(nbr))
nbr_establish_connection(nbr);
}
static int
gen_hello_prms_tlv(struct ibuf *buf, uint16_t holdtime, uint16_t flags)
{
struct hello_prms_tlv parms;
memset(&parms, 0, sizeof(parms));
parms.type = htons(TLV_TYPE_COMMONHELLO);
parms.length = htons(sizeof(parms.holdtime) + sizeof(parms.flags));
parms.holdtime = htons(holdtime);
parms.flags = htons(flags);
return (ibuf_add(buf, &parms, sizeof(parms)));
}
static int
gen_opt4_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint32_t value)
{
struct hello_prms_opt4_tlv parms;
memset(&parms, 0, sizeof(parms));
parms.type = htons(type);
parms.length = htons(sizeof(parms.value));
parms.value = value;
return (ibuf_add(buf, &parms, sizeof(parms)));
}
static int
gen_opt16_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint8_t *value)
{
struct hello_prms_opt16_tlv parms;
memset(&parms, 0, sizeof(parms));
parms.type = htons(type);
parms.length = htons(sizeof(parms.value));
memcpy(&parms.value, value, sizeof(parms.value));
return (ibuf_add(buf, &parms, sizeof(parms)));
}
static int
gen_ds_hello_prms_tlv(struct ibuf *buf, uint32_t value)
{
if (CHECK_FLAG(leconf->flags, F_LDPD_DS_CISCO_INTEROP))
value = htonl(value);
else
value = htonl(value << 28);
return (gen_opt4_hello_prms_tlv(buf, TLV_TYPE_DUALSTACK, value));
}
static int
tlv_decode_hello_prms(char *buf, uint16_t len, uint16_t *holdtime,
uint16_t *flags)
{
struct hello_prms_tlv tlv;
if (len < sizeof(tlv))
return (-1);
memcpy(&tlv, buf, sizeof(tlv));
if (tlv.type != htons(TLV_TYPE_COMMONHELLO))
return (-1);
if (ntohs(tlv.length) != sizeof(tlv) - TLV_HDR_SIZE)
return (-1);
*holdtime = ntohs(tlv.holdtime);
*flags = ntohs(tlv.flags);
return (sizeof(tlv));
}
static int
tlv_decode_opt_hello_prms(char *buf, uint16_t len, int *tlvs_rcvd, int af,
union ldpd_addr *addr, uint32_t *conf_number, uint16_t *trans_pref)
{
struct tlv tlv;
uint16_t tlv_len;
int total = 0;
*tlvs_rcvd = 0;
memset(addr, 0, sizeof(*addr));
*conf_number = 0;
*trans_pref = 0;
/*
* RFC 7552 - Section 6.1:
* "An LSR SHOULD accept the Hello message that contains both IPv4 and
* IPv6 Transport Address optional objects but MUST use only the
* transport address whose address family is the same as that of the
* IP packet carrying the Hello message. An LSR SHOULD accept only
* the first Transport Address optional object for a given address
* family in the received Hello message and ignore the rest if the
* LSR receives more than one Transport Address optional object for a
* given address family".
*/
while (len >= sizeof(tlv)) {
memcpy(&tlv, buf, TLV_HDR_SIZE);
tlv_len = ntohs(tlv.length);
if (tlv_len + TLV_HDR_SIZE > len)
return (-1);
buf += TLV_HDR_SIZE;
len -= TLV_HDR_SIZE;
total += TLV_HDR_SIZE;
switch (ntohs(tlv.type)) {
case TLV_TYPE_IPV4TRANSADDR:
if (tlv_len != sizeof(addr->v4))
return (-1);
if (af != AF_INET)
return (-1);
if (CHECK_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR))
break;
memcpy(&addr->v4, buf, sizeof(addr->v4));
SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR);
break;
case TLV_TYPE_IPV6TRANSADDR:
if (tlv_len != sizeof(addr->v6))
return (-1);
if (af != AF_INET6)
return (-1);
if (CHECK_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR))
break;
memcpy(&addr->v6, buf, sizeof(addr->v6));
SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR);
break;
case TLV_TYPE_CONFIG:
if (tlv_len != sizeof(uint32_t))
return (-1);
memcpy(conf_number, buf, sizeof(uint32_t));
SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_CONF);
break;
case TLV_TYPE_DUALSTACK:
if (tlv_len != sizeof(uint32_t))
return (-1);
/*
* RFC 7552 - Section 6.1:
* "A Single-stack LSR does not need to use the
* Dual-Stack capability in Hello messages and SHOULD
* ignore this capability if received".
*/
if (!ldp_is_dual_stack(leconf))
break;
/* Shame on you, Cisco! */
if (CHECK_FLAG(leconf->flags, F_LDPD_DS_CISCO_INTEROP)) {
memcpy(trans_pref, buf + sizeof(uint16_t), sizeof(uint16_t));
*trans_pref = ntohs(*trans_pref);
} else {
memcpy(trans_pref, buf , sizeof(uint16_t));
*trans_pref = ntohs(*trans_pref) >> 12;
}
SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_DS);
break;
default:
/* if unknown flag set, ignore TLV */
if (!CHECK_FLAG(ntohs(tlv.type), UNKNOWN_FLAG))
return (-1);
break;
}
buf += tlv_len;
len -= tlv_len;
total += tlv_len;
}
return (total);
}