diff options
Diffstat (limited to 'usr.sbin/ldpd/hello.c')
-rw-r--r-- | usr.sbin/ldpd/hello.c | 343 |
1 files changed, 288 insertions, 55 deletions
diff --git a/usr.sbin/ldpd/hello.c b/usr.sbin/ldpd/hello.c index be195e393ba..043eac8b7da 100644 --- a/usr.sbin/ldpd/hello.c +++ b/usr.sbin/ldpd/hello.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hello.c,v 1.39 2016/05/23 18:33:56 renato Exp $ */ +/* $OpenBSD: hello.c,v 1.40 2016/05/23 18:58:48 renato Exp $ */ /* * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org> @@ -38,42 +38,70 @@ extern struct ldpd_conf *leconf; int tlv_decode_hello_prms(char *, uint16_t, uint16_t *, uint16_t *); -int tlv_decode_opt_hello_prms(char *, uint16_t, struct in_addr *, - uint32_t *); +int tlv_decode_opt_hello_prms(char *, uint16_t, int *, int, + union ldpd_addr *, uint32_t *, uint16_t *); int gen_hello_prms_tlv(struct ibuf *buf, uint16_t, uint16_t); int gen_opt4_hello_prms_tlv(struct ibuf *, uint16_t, uint32_t); +int gen_opt16_hello_prms_tlv(struct ibuf *, uint16_t, uint8_t *); +int gen_ds_hello_prms_tlv(struct ibuf *, uint32_t); int -send_hello(enum hello_type type, struct iface *iface, struct tnbr *tnbr) +send_hello(enum hello_type type, struct iface_af *ia, struct tnbr *tnbr) { - struct sockaddr_in dst; - struct ibuf *buf; + int af; + union ldpd_addr dst; uint16_t size, holdtime = 0, flags = 0; int fd = 0; - - dst.sin_port = htons(LDP_PORT); - dst.sin_family = AF_INET; - dst.sin_len = sizeof(struct sockaddr_in); + struct ibuf *buf; switch (type) { case HELLO_LINK: - inet_aton(AllRouters, &dst.sin_addr); - holdtime = iface->hello_holdtime; + af = ia->af; + holdtime = ia->hello_holdtime; flags = 0; - fd = global.ldp_disc_socket; + fd = (ldp_af_global_get(&global, af))->ldp_disc_socket; + + /* multicast destination address */ + switch (af) { + case AF_INET: + 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: - dst.sin_addr = tnbr->addr; + af = tnbr->af; holdtime = tnbr->hello_holdtime; flags = TARGETED_HELLO; if ((tnbr->flags & F_TNBR_CONFIGURED) || tnbr->pw_count) flags |= REQUEST_TARG_HELLO; - fd = global.ldp_edisc_socket; + fd = (ldp_af_global_get(&global, af))->ldp_edisc_socket; + + /* unicast destination address */ + dst = tnbr->addr; break; + default: + fatalx("send_hello: unknown hello type"); } - size = LDP_HDR_SIZE + LDP_MSG_SIZE + sizeof(struct hello_prms_tlv) + - sizeof(struct hello_prms_opt4_tlv); + /* 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"); + } + if (ldp_is_dual_stack(leconf)) + size += sizeof(struct hello_prms_opt4_tlv); /* generate message */ if ((buf = ibuf_open(size)) == NULL) @@ -83,26 +111,57 @@ send_hello(enum hello_type type, struct iface *iface, struct tnbr *tnbr) size -= LDP_HDR_SIZE; gen_msg_hdr(buf, MSG_TYPE_HELLO, size); gen_hello_prms_tlv(buf, holdtime, flags); - gen_opt4_hello_prms_tlv(buf, TLV_TYPE_IPV4TRANSADDR, - leconf->trans_addr.s_addr); - send_packet(fd, iface, buf->buf, buf->wpos, &dst); + /* + * 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: + gen_opt4_hello_prms_tlv(buf, TLV_TYPE_IPV4TRANSADDR, + leconf->ipv4.trans_addr.v4.s_addr); + break; + case AF_INET6: + gen_opt16_hello_prms_tlv(buf, TLV_TYPE_IPV6TRANSADDR, + leconf->ipv6.trans_addr.v6.s6_addr); + break; + default: + fatalx("send_hello: unknown af"); + } + + /* + * 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)) + gen_ds_hello_prms_tlv(buf, leconf->trans_pref); + + 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 *lm, struct in_addr src, - struct iface *iface, int multicast, char *buf, uint16_t len) +recv_hello(struct in_addr lsr_id, struct ldp_msg *lm, int af, + union ldpd_addr *src, struct iface *iface, int multicast, char *buf, + uint16_t len) { - struct adj *adj; + struct adj *adj = NULL; struct nbr *nbr; uint16_t holdtime, flags; - struct in_addr transport_addr; + int tlvs_rcvd; + int ds_tlv; + union ldpd_addr trans_addr; + uint32_t scope_id = 0; uint32_t conf_number; + 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); @@ -133,7 +192,19 @@ recv_hello(struct in_addr lsr_id, struct ldp_msg *lm, struct in_addr src, memset(&source, 0, sizeof(source)); if (flags & TARGETED_HELLO) { - tnbr = tnbr_find(leconf, src); + /* + * 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 %s: targeted hello with " + "link-local source address", __func__, + inet_ntoa(lsr_id)); + return; + } + + tnbr = tnbr_find(leconf, af, src); /* remove the dynamic tnbr if the 'R' bit was cleared */ if (tnbr && (tnbr->flags & F_TNBR_DYNAMIC) && @@ -144,10 +215,11 @@ recv_hello(struct in_addr lsr_id, struct ldp_msg *lm, struct in_addr src, if (!tnbr) { if (!((flags & REQUEST_TARG_HELLO) && - leconf->flags & F_LDPD_TH_ACCEPT)) + ((ldp_af_conf_get(leconf, af))->flags & + F_LDPD_AF_THELLO_ACCEPT))) return; - tnbr = tnbr_new(leconf, src); + tnbr = tnbr_new(leconf, af, src); tnbr->flags |= F_TNBR_DYNAMIC; tnbr_update(tnbr); LIST_INSERT_HEAD(&leconf->tnbr_list, tnbr, entry); @@ -156,13 +228,14 @@ recv_hello(struct in_addr lsr_id, struct ldp_msg *lm, struct in_addr src, source.type = HELLO_TARGETED; source.target = tnbr; } else { + ia = iface_af_get(iface, af); source.type = HELLO_LINK; - source.link.iface = iface; - source.link.src_addr = src; + source.link.ia = ia; + source.link.src_addr = *src; } - r = tlv_decode_opt_hello_prms(buf, len, &transport_addr, - &conf_number); + r = tlv_decode_opt_hello_prms(buf, len, &tlvs_rcvd, af, &trans_addr, + &conf_number, &trans_pref); if (r == -1) { log_debug("%s: lsr-id %s: failed to decode optional params", __func__, inet_ntoa(lsr_id)); @@ -175,30 +248,118 @@ recv_hello(struct in_addr lsr_id, struct ldp_msg *lm, struct in_addr src, } /* implicit transport address */ - if (transport_addr.s_addr == INADDR_ANY) - transport_addr = src; - if (bad_ip_addr(transport_addr)) { + if (!(tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR)) + trans_addr = *src; + if (bad_addr(af, &trans_addr)) { log_debug("%s: lsr-id %s: invalid transport address %s", - __func__, inet_ntoa(lsr_id), inet_ntoa(transport_addr)); + __func__, inet_ntoa(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 (source.type == HELLO_TARGETED) + return; + scope_id = iface->ifindex; + } + adj = adj_find(&source); nbr = nbr_find_ldpid(lsr_id.s_addr); - if (!nbr) { + + /* check dual-stack tlv */ + ds_tlv = (tlvs_rcvd & F_HELLO_TLV_RCVD_DS) ? 1 : 0; + 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 %s: remote transport preference does not " + "match the local preference", __func__, inet_ntoa(lsr_id)); + if (nbr) + session_shutdown(nbr, S_TRANS_MISMTCH, lm->msgid, + lm->type); + if (adj) + adj_del(adj); + return; + } + + if (adj == NULL) { + adj = adj_new(lsr_id, &source, &trans_addr); + if (nbr) { + adj->nbr = nbr; + LIST_INSERT_HEAD(&nbr->adj_list, adj, nbr_entry); + } + } + + if (nbr == NULL) { + /* + * The hello adjacency's address-family doesn't match the local + * preference. + */ + if (ds_tlv && + ((trans_pref == DUAL_STACK_LDPOV4 && af != AF_INET) || + (trans_pref == DUAL_STACK_LDPOV6 && af != AF_INET6))) + return; + + nbr = nbr_find_addr(af, &trans_addr); + if (nbr) { + log_debug("%s: transport address %s is already being " + "used by lsr-id %s", __func__, log_addr(af, + &trans_addr), inet_ntoa(nbr->id)); + return; + } + /* create new adjacency and new neighbor */ - nbr = nbr_new(lsr_id, transport_addr); - adj = adj_new(nbr, &source, transport_addr); + nbr = nbr_new(lsr_id, af, ds_tlv, &trans_addr, scope_id); } else { - adj = adj_find(nbr, &source); - if (!adj) { - /* create new adjacency for existing neighbor */ - adj = adj_new(nbr, &source, transport_addr); - - if (nbr->raddr.s_addr != transport_addr.s_addr) - log_warnx("%s: lsr-id %s: multiple " - "adjacencies advertising different " - "transport addresses", __func__, - inet_ntoa(lsr_id)); + /* + * Check for noncompliant dual-stack neighbor according to + * RFC 7552 section 6.1.1. + */ + if (!ds_tlv) { + switch (af) { + case AF_INET: + if (nbr_adj_count(nbr, AF_INET6) > 0) { + session_shutdown(nbr, S_DS_NONCMPLNCE, + lm->msgid, lm->type); + return; + } + break; + case AF_INET6: + if (nbr_adj_count(nbr, AF_INET) > 0) { + session_shutdown(nbr, S_DS_NONCMPLNCE, + lm->msgid, lm->type); + return; + } + break; + default: + fatalx("recv_hello: unknown af"); + } + } + + /* + * Protection against misconfigured networks and buggy + * implementations. + */ + if (af == nbr->af && + (ldp_addrcmp(af, &nbr->raddr, &trans_addr) || + nbr->raddr_scope != scope_id)) { + log_warnx("%s: lsr-id %s: ignoring hello packet " + "advertising different transport address", __func__, + inet_ntoa(lsr_id)); + return; } } @@ -208,7 +369,7 @@ recv_hello(struct in_addr lsr_id, struct ldp_msg *lm, struct in_addr src, if (holdtime == 0) holdtime = LINK_DFLT_HOLDTIME; - adj->holdtime = min(iface->hello_holdtime, holdtime); + adj->holdtime = min(ia->hello_holdtime, holdtime); break; case HELLO_TARGETED: if (holdtime == 0) @@ -247,13 +408,37 @@ gen_opt4_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint32_t value) memset(&parms, 0, sizeof(parms)); parms.type = htons(type); - parms.length = htons(4); + parms.length = htons(sizeof(parms.value)); parms.value = value; return (ibuf_add(buf, &parms, sizeof(parms))); } 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))); +} + +int +gen_ds_hello_prms_tlv(struct ibuf *buf, uint32_t value) +{ + if (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)); +} + +int tlv_decode_hello_prms(char *buf, uint16_t len, uint16_t *holdtime, uint16_t *flags) { @@ -275,30 +460,78 @@ tlv_decode_hello_prms(char *buf, uint16_t len, uint16_t *holdtime, } int -tlv_decode_opt_hello_prms(char *buf, uint16_t len, struct in_addr *addr, - uint32_t *conf_number) +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, sizeof(tlv)); tlv_len = ntohs(tlv.length); switch (ntohs(tlv.type)) { case TLV_TYPE_IPV4TRANSADDR: - if (tlv_len != sizeof(uint32_t)) + if (tlv_len != sizeof(addr->v4)) + return (-1); + if (af != AF_INET || ldp_addrisset(AF_INET, addr)) + break; + memcpy(&addr->v4, buf + TLV_HDR_LEN, sizeof(addr->v4)); + *tlvs_rcvd |= F_HELLO_TLV_RCVD_ADDR; + break; + case TLV_TYPE_IPV6TRANSADDR: + if (tlv_len != sizeof(addr->v6)) return (-1); - memcpy(addr, buf + TLV_HDR_LEN, sizeof(uint32_t)); + if (af != AF_INET6 || ldp_addrisset(AF_INET6, addr)) + break; + memcpy(&addr->v6, buf + TLV_HDR_LEN, sizeof(addr->v6)); + *tlvs_rcvd |= F_HELLO_TLV_RCVD_ADDR; break; case TLV_TYPE_CONFIG: if (tlv_len != sizeof(uint32_t)) return (-1); memcpy(conf_number, buf + TLV_HDR_LEN, sizeof(uint32_t)); + *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 (leconf->flags & F_LDPD_DS_CISCO_INTEROP) { + memcpy(trans_pref, buf + TLV_HDR_LEN + + sizeof(uint16_t), sizeof(uint16_t)); + *trans_pref = ntohs(*trans_pref); + } else { + memcpy(trans_pref, buf + TLV_HDR_LEN, + sizeof(uint16_t)); + *trans_pref = ntohs(*trans_pref) >> 12; + } + *tlvs_rcvd |= F_HELLO_TLV_RCVD_DS; break; default: /* if unknown flag set, ignore TLV */ |