diff options
Diffstat (limited to 'ldpd/hello.c')
-rw-r--r-- | ldpd/hello.c | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/ldpd/hello.c b/ldpd/hello.c new file mode 100644 index 0000000..0b07f24 --- /dev/null +++ b/ldpd/hello.c @@ -0,0 +1,586 @@ +// 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); +} |