mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-10-31 10:56:49 +00:00 
			
		
		
		
	 bb53f1bc66
			
		
	
	
		bb53f1bc66
		
	
	
	
	
		
			
			clang-SA thinks we can skip the SA but have something allocated in buf. Signed-off-by: David Lamparter <equinox@diac24.net>
		
			
				
	
	
		
			839 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			839 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*	$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 int			 session_read(struct thread *);
 | |
| static int			 session_write(struct 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 int			 pending_conn_timeout(struct 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 */
 | |
| int
 | |
| 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 */
 | |
| 	*threadp = NULL;
 | |
| 	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 (0);
 | |
| 	}
 | |
| 
 | |
| 	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 (0);
 | |
| 	}
 | |
| 	ifindex = getsockopt_ifindex(af, &m);
 | |
| 
 | |
| 	/* find a matching interface */
 | |
| 	if (multicast) {
 | |
| 		iface = disc_find_iface(ifindex, af, &src);
 | |
| 		if (iface == NULL)
 | |
| 			return (0);
 | |
| 	}
 | |
| 
 | |
| 	/* 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 (0);
 | |
| 	}
 | |
| 
 | |
| 	/* 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 (0);
 | |
| 	}
 | |
| 	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 (0);
 | |
| 	}
 | |
| 	/* 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 (0);
 | |
| 	}
 | |
| 	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 (0);
 | |
| 	}
 | |
| 	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));
 | |
| 	}
 | |
| 
 | |
| 	return (0);
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 | |
| int
 | |
| 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 (0);
 | |
| 	}
 | |
| 	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 (0);
 | |
| 	}
 | |
| 	/* protection against buggy implementations */
 | |
| 	if (nbr_session_active_role(nbr)) {
 | |
| 		close(newfd);
 | |
| 		return (0);
 | |
| 	}
 | |
| 	if (nbr->state != NBR_STA_PRESENT) {
 | |
| 		log_debug("%s: lsr-id %s: rejecting additional transport "
 | |
| 		    "connection", __func__, inet_ntoa(nbr->id));
 | |
| 		close(newfd);
 | |
| 		return (0);
 | |
| 	}
 | |
| 
 | |
| 	session_accept_nbr(nbr, newfd);
 | |
| 
 | |
| 	return (0);
 | |
| }
 | |
| 
 | |
| 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 int
 | |
| 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;
 | |
| 
 | |
| 	tcp->rev = NULL;
 | |
| 	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 (0);
 | |
| 		}
 | |
| 		/* retry read */
 | |
| 		return (0);
 | |
| 	}
 | |
| 	if (n == 0) {
 | |
| 		/* connection closed */
 | |
| 		log_debug("%s: connection closed by remote end", __func__);
 | |
| 		nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
 | |
| 		return (0);
 | |
| 	}
 | |
| 	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 (0);
 | |
| 		}
 | |
| 
 | |
| 		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 (0);
 | |
| 		}
 | |
| 		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 (0);
 | |
| 		}
 | |
| 		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 (0);
 | |
| 			}
 | |
| 			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 (0);
 | |
| 				}
 | |
| 				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 (0);
 | |
| 				}
 | |
| 				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 (0);
 | |
| 				}
 | |
| 				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 (0);
 | |
| 			}
 | |
| 
 | |
| 			/* 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 (0);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* shouldn't happen, session_get_pdu should be > 0 if buf was
 | |
| 	 * allocated - but let's get rid of the SA warning.
 | |
| 	 */
 | |
| 	free(buf);
 | |
| 	return (0);
 | |
| }
 | |
| 
 | |
| static int
 | |
| 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 (0);
 | |
| 	}
 | |
| 
 | |
| 	evbuf_event_add(&tcp->wbuf);
 | |
| 
 | |
| 	return (0);
 | |
| }
 | |
| 
 | |
| 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_WRITE_OFF(nbr->ev_connect);
 | |
| 		break;
 | |
| 	case NBR_STA_INITIAL:
 | |
| 	case NBR_STA_OPENREC:
 | |
| 	case NBR_STA_OPENSENT:
 | |
| 	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 %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;
 | |
| 	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__);
 | |
| 
 | |
| 		tcp->rev = NULL;
 | |
| 		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_READ_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_TIMER_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 int
 | |
| 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);
 | |
| 
 | |
| 	return (0);
 | |
| }
 |