From 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:53:30 +0200 Subject: Adding upstream version 8.4.4. Signed-off-by: Daniel Baumann --- ldpd/packet.c | 828 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 828 insertions(+) create mode 100644 ldpd/packet.c (limited to 'ldpd/packet.c') diff --git a/ldpd/packet.c b/ldpd/packet.c new file mode 100644 index 0000000..2cca524 --- /dev/null +++ b/ldpd/packet.c @@ -0,0 +1,828 @@ +/* $OpenBSD$ */ + +/* + * 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 "ldpd.h" +#include "ldpe.h" +#include "log.h" + +#include "sockopt.h" + +static struct iface *disc_find_iface(unsigned int, int, + union ldpd_addr *); +static void session_read(struct thread *thread); +static void session_write(struct thread *thread); +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(struct thread *thread); + +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 = ldp_rtr_id_get(leconf); + 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) +{ + union sockunion su; + + 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"); + } + + addr2sa(af, dst, LDP_PORT, &su); + if (sendto(fd, pkt, len, 0, &su.sa, sockaddr_len(&su.sa)) == -1) { + log_warn("%s: error sending packet to %s", __func__, + log_sockaddr(&su.sa)); + return (-1); + } + + return (0); +} + +/* Discovery functions */ +void disc_recv_packet(struct thread *thread) +{ + int fd = THREAD_FD(thread); + struct thread **threadp = THREAD_ARG(thread); + + union { + struct cmsghdr hdr; +#ifdef HAVE_STRUCT_SOCKADDR_DL + char buf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#else + char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +#endif + } cmsgbuf; + struct msghdr m; + struct sockaddr_storage from; + struct iovec iov; + char *buf; +#ifndef MSG_MCAST + struct cmsghdr *cmsg; +#endif + ssize_t r; + int multicast; + int af; + union ldpd_addr src; + unsigned int ifindex = 0; + struct iface *iface = NULL; + 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; + + /* reschedule read */ + thread_add_read(master, disc_recv_packet, threadp, fd, threadp); + + /* setup buffer */ + memset(&m, 0, sizeof(m)); + iov.iov_base = buf = pkt_ptr; + iov.iov_len = IBUF_READ_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; + } + + sa2addr((struct sockaddr *)&from, &af, &src, NULL); +#ifdef MSG_MCAST + multicast = (m.msg_flags & MSG_MCAST) ? 1 : 0; +#else + multicast = 0; + for (cmsg = CMSG_FIRSTHDR(&m); cmsg != NULL; + cmsg = CMSG_NXTHDR(&m, cmsg)) { +#if defined(HAVE_IP_PKTINFO) + if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *pktinfo; + + pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + if (IN_MULTICAST(ntohl(pktinfo->ipi_addr.s_addr))) + multicast = 1; + break; + } +#elif defined(HAVE_IP_RECVDSTADDR) + if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_RECVDSTADDR) { + struct in_addr *addr; + + addr = (struct in_addr *)CMSG_DATA(cmsg); + if (IN_MULTICAST(ntohl(addr->s_addr))) + multicast = 1; + break; + } +#else +#error "Unsupported socket API" +#endif + if (af == AF_INET6 && cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *pktinfo; + + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + if (IN6_IS_ADDR_MULTICAST(&pktinfo->ipi6_addr)) + multicast = 1; + break; + } + } +#endif /* MSG_MCAST */ + if (bad_addr(af, &src)) { + log_debug("%s: invalid source address: %s", __func__, + log_addr(af, &src)); + return; + } + ifindex = getsockopt_ifindex(af, &m); + + /* find a matching interface */ + if (multicast) { + iface = disc_find_iface(ifindex, af, &src); + 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) +{ + struct iface *iface; + struct iface_af *ia; + + iface = if_lookup(leconf, ifindex); + if (iface == NULL) + return (NULL); + + ia = iface_af_get(iface, af); + if (!ia->enabled) + return (NULL); + + /* + * RFC 7552 - Section 5.1: + * "Link-local IPv6 address MUST be used as the source IP address in + * IPv6 LDP Link Hellos". + */ + if (af == AF_INET6 && !IN6_IS_ADDR_LINKLOCAL(&src->v6)) + return (NULL); + + return (iface); +} + +void session_accept(struct thread *thread) +{ + int fd = THREAD_FD(thread); + struct sockaddr_storage src; + socklen_t len = sizeof(src); + int newfd; + int af; + union ldpd_addr addr; + struct nbr *nbr; + struct pending_conn *pconn; + + newfd = accept(fd, (struct sockaddr *)&src, &len); + 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; + } + sock_set_nonblock(newfd); + + sa2addr((struct sockaddr *)&src, &af, &addr, NULL); + + /* + * 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 %pI4: rejecting additional transport connection", __func__, &nbr->id); + close(newfd); + return; + } + + session_accept_nbr(nbr, newfd); +} + +void +session_accept_nbr(struct nbr *nbr, int fd) +{ +#ifdef __OpenBSD__ + 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 (nbrp && nbrp->auth.method == AUTH_MD5SIG) { + 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; + } + } +#endif + + nbr->tcp = tcp_new(fd, nbr); + nbr_fsm(nbr, NBR_EVT_MATCH_ADJ); +} + +static void session_read(struct thread *thread) +{ + int fd = THREAD_FD(thread); + struct nbr *nbr = THREAD_ARG(thread); + struct tcp_conn *tcp = nbr->tcp; + struct ldp_hdr *ldp_hdr; + struct ldp_msg *msg; + char *buf = NULL, *pdu; + ssize_t n, len; + uint16_t pdu_len, msg_len, msg_size, max_pdu_len; + int ret; + + thread_add_read(master, session_read, nbr, fd, &tcp->rev); + + 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_MSG_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; + case MSG_TYPE_NOTIFICATION: + 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 %pI4", + __func__, &nbr->id); + if (!(ntohs(msg->type) & UNKNOWN_FLAG)) { + nbr->stats.unknown_msg++; + 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; + } + + /* no errors - update per neighbor message counters */ + switch (type) { + case MSG_TYPE_NOTIFICATION: + nbr->stats.notif_rcvd++; + break; + case MSG_TYPE_KEEPALIVE: + nbr->stats.kalive_rcvd++; + break; + case MSG_TYPE_CAPABILITY: + nbr->stats.capability_rcvd++; + break; + case MSG_TYPE_ADDR: + nbr->stats.addr_rcvd++; + break; + case MSG_TYPE_ADDRWITHDRAW: + nbr->stats.addrwdraw_rcvd++; + break; + case MSG_TYPE_LABELMAPPING: + nbr->stats.labelmap_rcvd++; + break; + case MSG_TYPE_LABELREQUEST: + nbr->stats.labelreq_rcvd++; + break; + case MSG_TYPE_LABELWITHDRAW: + nbr->stats.labelwdraw_rcvd++; + break; + case MSG_TYPE_LABELRELEASE: + nbr->stats.labelrel_rcvd++; + break; + case MSG_TYPE_LABELABORTREQ: + nbr->stats.labelabreq_rcvd++; + break; + default: + break; + } + + /* Analyse the next message */ + pdu += msg_size; + len -= msg_size; + } + free(buf); + buf = NULL; + if (len != 0) { + session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); + return; + } + } + + /* shouldn't happen, session_get_pdu should be > 0 if buf was + * allocated - but let's get rid of the SA warning. + */ + free(buf); +} + +static void session_write(struct thread *thread) +{ + struct tcp_conn *tcp = THREAD_ARG(thread); + struct nbr *nbr = tcp->nbr; + + tcp->wbuf.ev = NULL; + + if (msgbuf_write(&tcp->wbuf.wbuf) <= 0) + if (errno != EAGAIN && 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)) + THREAD_OFF(nbr->ev_connect); + break; + case NBR_STA_INITIAL: + case NBR_STA_OPENREC: + case NBR_STA_OPENSENT: + /* update SNMP session counters during initialization */ + leconf->stats.session_attempts++; + send_notification(nbr->tcp, status, msg_id, msg_type); + + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + break; + case NBR_STA_OPER: + 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 %pI4", __func__, + &nbr->id); + + ldp_sync_fsm_nbr_event(nbr, LDP_SYNC_EVT_SESSION_CLOSE); + + 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; + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + + 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__); + + thread_add_read(master, session_read, nbr, tcp->fd, &tcp->rev); + tcp->nbr = nbr; + } + + if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) + log_warn("%s: getsockname", __func__); + else + sa2addr((struct sockaddr *)&ss, NULL, NULL, &tcp->lport); + if (getpeername(fd, (struct sockaddr *)&ss, &len) != 0) + log_warn("%s: getpeername", __func__); + else + sa2addr((struct sockaddr *)&ss, NULL, NULL, &tcp->rport); + + 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) { + THREAD_OFF(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; + + if ((pconn = calloc(1, sizeof(*pconn))) == NULL) + fatal(__func__); + + pconn->fd = fd; + pconn->af = af; + pconn->addr = *addr; + TAILQ_INSERT_TAIL(&global.pending_conns, pconn, entry); + pconn->ev_timeout = NULL; + thread_add_timer(master, pending_conn_timeout, pconn, PENDING_CONN_TIMEOUT, + &pconn->ev_timeout); + + return (pconn); +} + +void +pending_conn_del(struct pending_conn *pconn) +{ + THREAD_OFF(pconn->ev_timeout); + 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(struct thread *thread) +{ + struct pending_conn *pconn = THREAD_ARG(thread); + struct tcp_conn *tcp; + + pconn->ev_timeout = NULL; + + 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); +} -- cgit v1.2.3