summaryrefslogtreecommitdiffstats
path: root/ldpd/packet.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ldpd/packet.c828
1 files changed, 828 insertions, 0 deletions
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 <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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 <zebra.h>
+
+#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);
+}