/* $OpenBSD: packet.c,v 1.73 2024/11/21 13:12:11 claudio Exp $ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "ldpd.h" #include "ldpe.h" #include "log.h" static struct iface *disc_find_iface(unsigned int, int, union ldpd_addr *, int); static void session_read(int, short, void *); static void session_write(int, short, void *); static ssize_t session_get_pdu(struct ibuf_read *, char **); static void tcp_close(struct tcp_conn *); static struct pending_conn *pending_conn_new(int, int, union ldpd_addr *); static void pending_conn_timeout(int, short, void *); static u_int8_t *recv_buf; int gen_ldp_hdr(struct ibuf *buf, uint16_t size) { struct ldp_hdr ldp_hdr; memset(&ldp_hdr, 0, sizeof(ldp_hdr)); ldp_hdr.version = htons(LDP_VERSION); /* exclude the 'Version' and 'PDU Length' fields from the total */ ldp_hdr.length = htons(size - LDP_HDR_DEAD_LEN); ldp_hdr.lsr_id = leconf->rtr_id.s_addr; ldp_hdr.lspace_id = 0; return (ibuf_add(buf, &ldp_hdr, LDP_HDR_SIZE)); } int gen_msg_hdr(struct ibuf *buf, uint16_t type, uint16_t size) { static int msgcnt = 0; struct ldp_msg msg; memset(&msg, 0, sizeof(msg)); msg.type = htons(type); /* exclude the 'Type' and 'Length' fields from the total */ msg.length = htons(size - LDP_MSG_DEAD_LEN); msg.id = htonl(++msgcnt); return (ibuf_add(buf, &msg, sizeof(msg))); } /* send packets */ int send_packet(int fd, int af, union ldpd_addr *dst, struct iface_af *ia, void *pkt, size_t len) { struct sockaddr *sa; switch (af) { case AF_INET: if (ia && IN_MULTICAST(ntohl(dst->v4.s_addr))) { /* set outgoing interface for multicast traffic */ if (sock_set_ipv4_mcast(ia->iface) == -1) { log_debug("%s: error setting multicast " "interface, %s", __func__, ia->iface->name); return (-1); } } break; case AF_INET6: if (ia && IN6_IS_ADDR_MULTICAST(&dst->v6)) { /* set outgoing interface for multicast traffic */ if (sock_set_ipv6_mcast(ia->iface) == -1) { log_debug("%s: error setting multicast " "interface, %s", __func__, ia->iface->name); return (-1); } } break; default: fatalx("send_packet: unknown af"); } sa = addr2sa(af, dst, LDP_PORT); if (sendto(fd, pkt, len, 0, sa, sa->sa_len) == -1) { log_warn("%s: error sending packet to %s", __func__, log_sockaddr(sa)); return (-1); } return (0); } /* Discovery functions */ #define CMSG_MAXLEN max(sizeof(struct sockaddr_dl), sizeof(struct in6_pktinfo)) void disc_recv_packet(int fd, short event, void *bula) { union { struct cmsghdr hdr; char buf[CMSG_SPACE(CMSG_MAXLEN)]; } cmsgbuf; struct msghdr m; struct sockaddr_storage from; struct iovec iov; char *buf; struct cmsghdr *cmsg; ssize_t r; int multicast; int af; union ldpd_addr src; unsigned int ifindex = 0; struct iface *iface; uint16_t len; struct ldp_hdr ldp_hdr; uint16_t pdu_len; struct ldp_msg msg; uint16_t msg_len; struct in_addr lsr_id; if (event != EV_READ) return; if (recv_buf == NULL) if ((recv_buf = malloc(READ_BUF_SIZE)) == NULL) fatal(__func__); /* setup buffer */ memset(&m, 0, sizeof(m)); iov.iov_base = buf = recv_buf; iov.iov_len = READ_BUF_SIZE; m.msg_name = &from; m.msg_namelen = sizeof(from); m.msg_iov = &iov; m.msg_iovlen = 1; m.msg_control = &cmsgbuf.buf; m.msg_controllen = sizeof(cmsgbuf.buf); if ((r = recvmsg(fd, &m, 0)) == -1) { if (errno != EAGAIN && errno != EINTR) log_debug("%s: read error: %s", __func__, strerror(errno)); return; } multicast = (m.msg_flags & MSG_MCAST) ? 1 : 0; sa2addr((struct sockaddr *)&from, &af, &src); if (bad_addr(af, &src)) { log_debug("%s: invalid source address: %s", __func__, log_addr(af, &src)); return; } for (cmsg = CMSG_FIRSTHDR(&m); cmsg != NULL; cmsg = CMSG_NXTHDR(&m, cmsg)) { if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVIF) { ifindex = ((struct sockaddr_dl *) CMSG_DATA(cmsg))->sdl_index; break; } if (af == AF_INET6 && cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { ifindex = ((struct in6_pktinfo *) CMSG_DATA(cmsg))->ipi6_ifindex; break; } } /* find a matching interface */ iface = disc_find_iface(ifindex, af, &src, multicast); if (iface == NULL) return; /* check packet size */ len = (uint16_t)r; if (len < (LDP_HDR_SIZE + LDP_MSG_SIZE) || len > LDP_MAX_LEN) { log_debug("%s: bad packet size, source %s", __func__, log_addr(af, &src)); return; } /* LDP header sanity checks */ memcpy(&ldp_hdr, buf, sizeof(ldp_hdr)); if (ntohs(ldp_hdr.version) != LDP_VERSION) { log_debug("%s: invalid LDP version %d, source %s", __func__, ntohs(ldp_hdr.version), log_addr(af, &src)); return; } if (ntohs(ldp_hdr.lspace_id) != 0) { log_debug("%s: invalid label space %u, source %s", __func__, ntohs(ldp_hdr.lspace_id), log_addr(af, &src)); return; } /* check "PDU Length" field */ pdu_len = ntohs(ldp_hdr.length); if ((pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE)) || (pdu_len > (len - LDP_HDR_DEAD_LEN))) { log_debug("%s: invalid LDP packet length %u, source %s", __func__, ntohs(ldp_hdr.length), log_addr(af, &src)); return; } buf += LDP_HDR_SIZE; len -= LDP_HDR_SIZE; lsr_id.s_addr = ldp_hdr.lsr_id; /* * For UDP, we process only the first message of each packet. This does * not impose any restrictions since LDP uses UDP only for sending Hello * packets. */ memcpy(&msg, buf, sizeof(msg)); /* check "Message Length" field */ msg_len = ntohs(msg.length); if (msg_len < LDP_MSG_LEN || ((msg_len + LDP_MSG_DEAD_LEN) > pdu_len)) { log_debug("%s: invalid LDP message length %u, source %s", __func__, ntohs(msg.length), log_addr(af, &src)); return; } buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; /* switch LDP packet type */ switch (ntohs(msg.type)) { case MSG_TYPE_HELLO: recv_hello(lsr_id, &msg, af, &src, iface, multicast, buf, len); break; default: log_debug("%s: unknown LDP packet type, source %s", __func__, log_addr(af, &src)); } } static struct iface * disc_find_iface(unsigned int ifindex, int af, union ldpd_addr *src, int multicast) { struct iface *iface; struct iface_af *ia; struct if_addr *if_addr; in_addr_t mask; iface = if_lookup(leconf, ifindex); if (iface == NULL) return (NULL); /* * For unicast packets, we just need to make sure that the interface * is enabled for the given address-family. */ if (!multicast) { ia = iface_af_get(iface, af); if (ia->enabled) return (iface); return (NULL); } switch (af) { case AF_INET: LIST_FOREACH(if_addr, &iface->addr_list, entry) { if (if_addr->af != AF_INET) continue; switch (iface->type) { case IF_TYPE_POINTOPOINT: if (if_addr->dstbrd.v4.s_addr == src->v4.s_addr) return (iface); break; default: mask = prefixlen2mask(if_addr->prefixlen); if ((if_addr->addr.v4.s_addr & mask) == (src->v4.s_addr & mask)) return (iface); break; } } break; case AF_INET6: if (IN6_IS_ADDR_LINKLOCAL(&src->v6)) return (iface); break; default: fatalx("disc_find_iface: unknown af"); } return (NULL); } void session_accept(int fd, short event, void *bula) { struct sockaddr_storage src; socklen_t len = sizeof(src); int newfd; int af; union ldpd_addr addr; struct nbr *nbr; struct pending_conn *pconn; if (!(event & EV_READ)) return; newfd = accept4(fd, (struct sockaddr *)&src, &len, SOCK_NONBLOCK | SOCK_CLOEXEC); if (newfd == -1) { /* * Pause accept if we are out of file descriptors, or * libevent will haunt us here too. */ if (errno == ENFILE || errno == EMFILE) { accept_pause(); } else if (errno != EWOULDBLOCK && errno != EINTR && errno != ECONNABORTED) log_debug("%s: accept error: %s", __func__, strerror(errno)); return; } sa2addr((struct sockaddr *)&src, &af, &addr); /* * Since we don't support label spaces, we can identify this neighbor * just by its source address. This way we don't need to wait for its * Initialization message to know who we are talking to. */ nbr = nbr_find_addr(af, &addr); if (nbr == NULL) { /* * According to RFC 5036, we would need to send a No Hello * Error Notification message and close this TCP connection * right now. But doing so would trigger the backoff exponential * timer in the remote peer, which would considerably slow down * the session establishment process. The trick here is to wait * five seconds before sending the Notification Message. There's * a good chance that the remote peer will send us a Hello * message within this interval, so it's worth waiting before * taking a more drastic measure. */ pconn = pending_conn_find(af, &addr); if (pconn) close(newfd); else pending_conn_new(newfd, af, &addr); return; } /* protection against buggy implementations */ if (nbr_session_active_role(nbr)) { close(newfd); return; } if (nbr->state != NBR_STA_PRESENT) { log_debug("%s: lsr-id %s: rejecting additional transport " "connection", __func__, inet_ntoa(nbr->id)); close(newfd); return; } session_accept_nbr(nbr, newfd); } void session_accept_nbr(struct nbr *nbr, int fd) { struct nbr_params *nbrp; int opt; socklen_t len; nbrp = nbr_params_find(leconf, nbr->id); if (nbr_gtsm_check(fd, nbr, nbrp)) { close(fd); return; } if (!LIST_EMPTY(&leconf->auth_list)) { if (sysdep.no_pfkey || sysdep.no_md5sig) { log_warnx("md5sig configured but not available"); close(fd); return; } len = sizeof(opt); if (getsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, &len) == -1) fatal("getsockopt TCP_MD5SIG"); if (!opt) { /* non-md5'd connection! */ log_warnx("connection attempt without md5 signature"); close(fd); return; } } nbr->tcp = tcp_new(fd, nbr); nbr_fsm(nbr, NBR_EVT_MATCH_ADJ); } static void session_read(int fd, short event, void *arg) { struct nbr *nbr = arg; struct tcp_conn *tcp = nbr->tcp; struct ldp_hdr *ldp_hdr; struct ldp_msg *msg; char *buf, *pdu; ssize_t n, len; uint16_t pdu_len, msg_len, msg_size, max_pdu_len; int ret; if (event != EV_READ) return; if ((n = read(fd, tcp->rbuf->buf + tcp->rbuf->wpos, sizeof(tcp->rbuf->buf) - tcp->rbuf->wpos)) == -1) { if (errno != EINTR && errno != EAGAIN) { log_warn("%s: read error", __func__); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); return; } /* retry read */ return; } if (n == 0) { /* connection closed */ log_debug("%s: connection closed by remote end", __func__); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); return; } tcp->rbuf->wpos += n; while ((len = session_get_pdu(tcp->rbuf, &buf)) > 0) { pdu = buf; ldp_hdr = (struct ldp_hdr *)pdu; if (ntohs(ldp_hdr->version) != LDP_VERSION) { session_shutdown(nbr, S_BAD_PROTO_VER, 0, 0); free(buf); return; } pdu_len = ntohs(ldp_hdr->length); /* * RFC 5036 - Section 3.5.3: * "Prior to completion of the negotiation, the maximum * allowable length is 4096 bytes". */ if (nbr->state == NBR_STA_OPER) max_pdu_len = nbr->max_pdu_len; else max_pdu_len = LDP_MAX_LEN; if (pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE) || pdu_len > max_pdu_len) { session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); free(buf); return; } pdu_len -= LDP_HDR_PDU_LEN; if (ldp_hdr->lsr_id != nbr->id.s_addr || ldp_hdr->lspace_id != 0) { session_shutdown(nbr, S_BAD_LDP_ID, 0, 0); free(buf); return; } pdu += LDP_HDR_SIZE; len -= LDP_HDR_SIZE; nbr_fsm(nbr, NBR_EVT_PDU_RCVD); while (len >= LDP_MSG_SIZE) { uint16_t type; msg = (struct ldp_msg *)pdu; type = ntohs(msg->type); msg_len = ntohs(msg->length); if (msg_len < LDP_MSG_LEN || (msg_len + LDP_MSG_DEAD_LEN) > pdu_len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); free(buf); return; } msg_size = msg_len + LDP_MSG_DEAD_LEN; pdu_len -= msg_size; /* check for error conditions earlier */ switch (type) { case MSG_TYPE_INIT: if ((nbr->state != NBR_STA_INITIAL) && (nbr->state != NBR_STA_OPENSENT)) { session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); free(buf); return; } break; case MSG_TYPE_KEEPALIVE: if ((nbr->state == NBR_STA_INITIAL) || (nbr->state == NBR_STA_OPENSENT)) { session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); free(buf); return; } break; default: if (nbr->state != NBR_STA_OPER) { session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); free(buf); return; } break; } /* switch LDP packet type */ switch (type) { case MSG_TYPE_NOTIFICATION: ret = recv_notification(nbr, pdu, msg_size); break; case MSG_TYPE_INIT: ret = recv_init(nbr, pdu, msg_size); break; case MSG_TYPE_KEEPALIVE: ret = recv_keepalive(nbr, pdu, msg_size); break; case MSG_TYPE_CAPABILITY: ret = recv_capability(nbr, pdu, msg_size); break; case MSG_TYPE_ADDR: case MSG_TYPE_ADDRWITHDRAW: ret = recv_address(nbr, pdu, msg_size); break; case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELREQUEST: case MSG_TYPE_LABELWITHDRAW: case MSG_TYPE_LABELRELEASE: case MSG_TYPE_LABELABORTREQ: ret = recv_labelmessage(nbr, pdu, msg_size, type); break; default: log_debug("%s: unknown LDP message from nbr %s", __func__, inet_ntoa(nbr->id)); if (!(ntohs(msg->type) & UNKNOWN_FLAG)) send_notification(nbr->tcp, S_UNKNOWN_MSG, msg->id, msg->type); /* ignore the message */ ret = 0; break; } if (ret == -1) { /* parser failed, giving up */ free(buf); return; } /* Analyse the next message */ pdu += msg_size; len -= msg_size; } free(buf); if (len != 0) { session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); return; } } } static void session_write(int fd, short event, void *arg) { struct tcp_conn *tcp = arg; struct nbr *nbr = tcp->nbr; if (!(event & EV_WRITE)) return; if (msgbuf_write(&tcp->wbuf.wbuf) == -1) if (nbr) nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); if (nbr == NULL && !tcp->wbuf.wbuf.queued) { /* * We are done sending the notification message, now we can * close the socket. */ tcp_close(tcp); return; } evbuf_event_add(&tcp->wbuf); } void session_shutdown(struct nbr *nbr, uint32_t status, uint32_t msg_id, uint32_t msg_type) { switch (nbr->state) { case NBR_STA_PRESENT: if (nbr_pending_connect(nbr)) event_del(&nbr->ev_connect); break; case NBR_STA_INITIAL: case NBR_STA_OPENREC: case NBR_STA_OPENSENT: case NBR_STA_OPER: log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); send_notification(nbr->tcp, status, msg_id, msg_type); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); break; default: fatalx("session_shutdown: unknown neighbor state"); } } void session_close(struct nbr *nbr) { log_debug("%s: closing session with lsr-id %s", __func__, inet_ntoa(nbr->id)); tcp_close(nbr->tcp); nbr_stop_ktimer(nbr); nbr_stop_ktimeout(nbr); nbr_stop_itimeout(nbr); } static ssize_t session_get_pdu(struct ibuf_read *r, char **b) { struct ldp_hdr l; size_t av, dlen, left; av = r->wpos; if (av < sizeof(l)) return (0); memcpy(&l, r->buf, sizeof(l)); dlen = ntohs(l.length) + LDP_HDR_DEAD_LEN; if (dlen > av) return (0); if ((*b = malloc(dlen)) == NULL) return (-1); memcpy(*b, r->buf, dlen); if (dlen < av) { left = av - dlen; memmove(r->buf, r->buf + dlen, left); r->wpos = left; } else r->wpos = 0; return (dlen); } struct tcp_conn * tcp_new(int fd, struct nbr *nbr) { struct tcp_conn *tcp; if ((tcp = calloc(1, sizeof(*tcp))) == NULL) fatal(__func__); tcp->fd = fd; evbuf_init(&tcp->wbuf, tcp->fd, session_write, tcp); if (nbr) { if ((tcp->rbuf = calloc(1, sizeof(struct ibuf_read))) == NULL) fatal(__func__); event_set(&tcp->rev, tcp->fd, EV_READ | EV_PERSIST, session_read, nbr); event_add(&tcp->rev, NULL); tcp->nbr = nbr; } return (tcp); } static void tcp_close(struct tcp_conn *tcp) { /* try to flush write buffer */ msgbuf_write(&tcp->wbuf.wbuf); evbuf_clear(&tcp->wbuf); if (tcp->nbr) { event_del(&tcp->rev); free(tcp->rbuf); tcp->nbr->tcp = NULL; } close(tcp->fd); accept_unpause(); free(tcp); } static struct pending_conn * pending_conn_new(int fd, int af, union ldpd_addr *addr) { struct pending_conn *pconn; struct timeval tv; if ((pconn = calloc(1, sizeof(*pconn))) == NULL) fatal(__func__); pconn->fd = fd; pconn->af = af; pconn->addr = *addr; evtimer_set(&pconn->ev_timeout, pending_conn_timeout, pconn); TAILQ_INSERT_TAIL(&global.pending_conns, pconn, entry); timerclear(&tv); tv.tv_sec = PENDING_CONN_TIMEOUT; if (evtimer_add(&pconn->ev_timeout, &tv) == -1) fatal(__func__); return (pconn); } void pending_conn_del(struct pending_conn *pconn) { if (evtimer_pending(&pconn->ev_timeout, NULL) && evtimer_del(&pconn->ev_timeout) == -1) fatal(__func__); TAILQ_REMOVE(&global.pending_conns, pconn, entry); free(pconn); } struct pending_conn * pending_conn_find(int af, union ldpd_addr *addr) { struct pending_conn *pconn; TAILQ_FOREACH(pconn, &global.pending_conns, entry) if (af == pconn->af && ldp_addrcmp(af, addr, &pconn->addr) == 0) return (pconn); return (NULL); } static void pending_conn_timeout(int fd, short event, void *arg) { struct pending_conn *pconn = arg; struct tcp_conn *tcp; log_debug("%s: no adjacency with remote end: %s", __func__, log_addr(pconn->af, &pconn->addr)); /* * Create a write buffer detached from any neighbor to send a * notification message reliably. */ tcp = tcp_new(pconn->fd, NULL); send_notification(tcp, S_NO_HELLO, 0, 0); msgbuf_write(&tcp->wbuf.wbuf); pending_conn_del(pconn); }