// 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); }