summaryrefslogtreecommitdiffstats
path: root/ldpd/hello.c
diff options
context:
space:
mode:
Diffstat (limited to 'ldpd/hello.c')
-rw-r--r--ldpd/hello.c586
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);
+}