Edit

IABSD.fr/src/usr.sbin/ldpd/neighbor.c

Branch :

  • Show log

    Commit

  • Author : deraadt
    Date : 2019-06-28 13:32:41
    Hash : df69c215
    Message : When system calls indicate an error they return -1, not some arbitrary value < 0. errno is only updated in this case. Change all (most?) callers of syscalls to follow this better, and let's see if this strictness helps us in the future.

  • usr.sbin/ldpd/neighbor.c
  • /*	$OpenBSD: neighbor.c,v 1.81 2019/06/28 13:32:48 deraadt Exp $ */
    
    /*
     * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
     * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
     * Copyright (c) 2005 Claudio Jeker <claudio@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 <sys/types.h>
    #include <sys/time.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #include "ldpd.h"
    #include "ldpe.h"
    #include "lde.h"
    #include "log.h"
    
    static __inline int	 nbr_id_compare(struct nbr *, struct nbr *);
    static __inline int	 nbr_addr_compare(struct nbr *, struct nbr *);
    static __inline int	 nbr_pid_compare(struct nbr *, struct nbr *);
    static void		 nbr_update_peerid(struct nbr *);
    static void		 nbr_ktimer(int, short, void *);
    static void		 nbr_start_ktimer(struct nbr *);
    static void		 nbr_ktimeout(int, short, void *);
    static void		 nbr_start_ktimeout(struct nbr *);
    static void		 nbr_itimeout(int, short, void *);
    static void		 nbr_start_itimeout(struct nbr *);
    static void		 nbr_idtimer(int, short, void *);
    static int		 nbr_act_session_operational(struct nbr *);
    static void		 nbr_send_labelmappings(struct nbr *);
    
    RB_GENERATE(nbr_id_head, nbr, id_tree, nbr_id_compare)
    RB_GENERATE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare)
    RB_GENERATE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare)
    
    struct {
    	int		state;
    	enum nbr_event	event;
    	enum nbr_action	action;
    	int		new_state;
    } nbr_fsm_tbl[] = {
        /* current state	event that happened	action to take		resulting state */
    /* Passive Role */
        {NBR_STA_PRESENT,	NBR_EVT_MATCH_ADJ,	NBR_ACT_NOTHING,	NBR_STA_INITIAL},
        {NBR_STA_INITIAL,	NBR_EVT_INIT_RCVD,	NBR_ACT_PASSIVE_INIT,	NBR_STA_OPENREC},
        {NBR_STA_OPENREC,	NBR_EVT_KEEPALIVE_RCVD,	NBR_ACT_SESSION_EST,	NBR_STA_OPER},
    /* Active Role */
        {NBR_STA_PRESENT,	NBR_EVT_CONNECT_UP,	NBR_ACT_CONNECT_SETUP,	NBR_STA_INITIAL},
        {NBR_STA_INITIAL,	NBR_EVT_INIT_SENT,	NBR_ACT_NOTHING,	NBR_STA_OPENSENT},
        {NBR_STA_OPENSENT,	NBR_EVT_INIT_RCVD,	NBR_ACT_KEEPALIVE_SEND,	NBR_STA_OPENREC},
    /* Session Maintenance */
        {NBR_STA_OPER,	NBR_EVT_PDU_RCVD,	NBR_ACT_RST_KTIMEOUT,	0},
        {NBR_STA_SESSION,	NBR_EVT_PDU_RCVD,	NBR_ACT_NOTHING,	0},
        {NBR_STA_OPER,	NBR_EVT_PDU_SENT,	NBR_ACT_RST_KTIMER,	0},
        {NBR_STA_SESSION,	NBR_EVT_PDU_SENT,	NBR_ACT_NOTHING,	0},
    /* Session Close */
        {NBR_STA_PRESENT,	NBR_EVT_CLOSE_SESSION,	NBR_ACT_NOTHING,	0},
        {NBR_STA_SESSION,	NBR_EVT_CLOSE_SESSION,	NBR_ACT_CLOSE_SESSION,	NBR_STA_PRESENT},
        {-1,		NBR_EVT_NOTHING,	NBR_ACT_NOTHING,	0},
    };
    
    const char * const nbr_event_names[] = {
    	"NOTHING",
    	"ADJACENCY MATCHED",
    	"CONNECTION UP",
    	"SESSION CLOSE",
    	"INIT RECEIVED",
    	"KEEPALIVE RECEIVED",
    	"PDU RECEIVED",
    	"PDU SENT",
    	"INIT SENT"
    };
    
    const char * const nbr_action_names[] = {
    	"NOTHING",
    	"RESET KEEPALIVE TIMEOUT",
    	"START NEIGHBOR SESSION",
    	"RESET KEEPALIVE TIMER",
    	"SETUP NEIGHBOR CONNECTION",
    	"SEND INIT AND KEEPALIVE",
    	"SEND KEEPALIVE",
    	"CLOSE SESSION"
    };
    
    struct nbr_id_head nbrs_by_id = RB_INITIALIZER(&nbrs_by_id);
    struct nbr_addr_head nbrs_by_addr = RB_INITIALIZER(&nbrs_by_addr);
    struct nbr_pid_head nbrs_by_pid = RB_INITIALIZER(&nbrs_by_pid);
    
    static __inline int
    nbr_id_compare(struct nbr *a, struct nbr *b)
    {
    	return (ntohl(a->id.s_addr) - ntohl(b->id.s_addr));
    }
    
    static __inline int
    nbr_addr_compare(struct nbr *a, struct nbr *b)
    {
    	if (a->af < b->af)
    		return (-1);
    	if (a->af > b->af)
    		return (1);
    
    	return (ldp_addrcmp(a->af, &a->raddr, &b->raddr));
    }
    
    static __inline int
    nbr_pid_compare(struct nbr *a, struct nbr *b)
    {
    	return (a->peerid - b->peerid);
    }
    
    int
    nbr_fsm(struct nbr *nbr, enum nbr_event event)
    {
    	struct timeval	now;
    	int		old_state;
    	int		new_state = 0;
    	int		i;
    
    	old_state = nbr->state;
    	for (i = 0; nbr_fsm_tbl[i].state != -1; i++)
    		if ((nbr_fsm_tbl[i].state & old_state) &&
    		    (nbr_fsm_tbl[i].event == event)) {
    			new_state = nbr_fsm_tbl[i].new_state;
    			break;
    		}
    
    	if (nbr_fsm_tbl[i].state == -1) {
    		/* event outside of the defined fsm, ignore it. */
    		log_warnx("%s: lsr-id %s, event %s not expected in "
    		    "state %s", __func__, inet_ntoa(nbr->id),
    		    nbr_event_names[event], nbr_state_name(old_state));
    		return (0);
    	}
    
    	if (new_state != 0)
    		nbr->state = new_state;
    
    	if (old_state != nbr->state) {
    		log_debug("%s: event %s resulted in action %s and "
    		    "changing state for lsr-id %s from %s to %s",
    		    __func__, nbr_event_names[event],
    		    nbr_action_names[nbr_fsm_tbl[i].action],
    		    inet_ntoa(nbr->id), nbr_state_name(old_state),
    		    nbr_state_name(nbr->state));
    
    		if (nbr->state == NBR_STA_OPER) {
    			gettimeofday(&now, NULL);
    			nbr->uptime = now.tv_sec;
    		}
    	}
    
    	if (nbr->state == NBR_STA_OPER || nbr->state == NBR_STA_PRESENT)
    		nbr_stop_itimeout(nbr);
    	else
    		nbr_start_itimeout(nbr);
    
    	switch (nbr_fsm_tbl[i].action) {
    	case NBR_ACT_RST_KTIMEOUT:
    		nbr_start_ktimeout(nbr);
    		break;
    	case NBR_ACT_RST_KTIMER:
    		nbr_start_ktimer(nbr);
    		break;
    	case NBR_ACT_SESSION_EST:
    		nbr_act_session_operational(nbr);
    		nbr_start_ktimer(nbr);
    		nbr_start_ktimeout(nbr);
    		if (nbr->v4_enabled)
    			send_address_all(nbr, AF_INET);
    		if (nbr->v6_enabled)
    			send_address_all(nbr, AF_INET6);
    		nbr_send_labelmappings(nbr);
    		break;
    	case NBR_ACT_CONNECT_SETUP:
    		nbr->tcp = tcp_new(nbr->fd, nbr);
    
    		/* trigger next state */
    		send_init(nbr);
    		nbr_fsm(nbr, NBR_EVT_INIT_SENT);
    		break;
    	case NBR_ACT_PASSIVE_INIT:
    		send_init(nbr);
    		send_keepalive(nbr);
    		break;
    	case NBR_ACT_KEEPALIVE_SEND:
    		nbr_start_ktimeout(nbr);
    		send_keepalive(nbr);
    		break;
    	case NBR_ACT_CLOSE_SESSION:
    		ldpe_imsg_compose_lde(IMSG_NEIGHBOR_DOWN, nbr->peerid, 0,
    		    NULL, 0);
    		session_close(nbr);
    		break;
    	case NBR_ACT_NOTHING:
    		/* do nothing */
    		break;
    	}
    
    	return (0);
    }
    
    struct nbr *
    nbr_new(struct in_addr id, int af, int ds_tlv, union ldpd_addr *addr,
        uint32_t scope_id)
    {
    	struct nbr		*nbr;
    	struct adj		*adj;
    	struct pending_conn	*pconn;
    
    	log_debug("%s: lsr-id %s transport-address %s", __func__,
    	    inet_ntoa(id), log_addr(af, addr));
    
    	if ((nbr = calloc(1, sizeof(*nbr))) == NULL)
    		fatal(__func__);
    
    	LIST_INIT(&nbr->adj_list);
    	nbr->state = NBR_STA_PRESENT;
    	nbr->peerid = 0;
    	nbr->af = af;
    	nbr->ds_tlv = ds_tlv;
    	if (af == AF_INET || ds_tlv)
    		nbr->v4_enabled = 1;
    	if (af == AF_INET6 || ds_tlv)
    		nbr->v6_enabled = 1;
    	nbr->id = id;
    	nbr->laddr = (ldp_af_conf_get(leconf, af))->trans_addr;
    	nbr->raddr = *addr;
    	nbr->raddr_scope = scope_id;
    	nbr->conf_seqnum = 0;
    
    	LIST_FOREACH(adj, &global.adj_list, global_entry) {
    		if (adj->lsr_id.s_addr == nbr->id.s_addr) {
    			adj->nbr = nbr;
    			LIST_INSERT_HEAD(&nbr->adj_list, adj, nbr_entry);
    		}
    	}
    
    	if (RB_INSERT(nbr_id_head, &nbrs_by_id, nbr) != NULL)
    		fatalx("nbr_new: RB_INSERT(nbrs_by_id) failed");
    	if (RB_INSERT(nbr_addr_head, &nbrs_by_addr, nbr) != NULL)
    		fatalx("nbr_new: RB_INSERT(nbrs_by_addr) failed");
    
    	TAILQ_INIT(&nbr->mapping_list);
    	TAILQ_INIT(&nbr->withdraw_list);
    	TAILQ_INIT(&nbr->request_list);
    	TAILQ_INIT(&nbr->release_list);
    	TAILQ_INIT(&nbr->abortreq_list);
    
    	/* set event structures */
    	evtimer_set(&nbr->keepalive_timeout, nbr_ktimeout, nbr);
    	evtimer_set(&nbr->keepalive_timer, nbr_ktimer, nbr);
    	evtimer_set(&nbr->init_timeout, nbr_itimeout, nbr);
    	evtimer_set(&nbr->initdelay_timer, nbr_idtimer, nbr);
    
    	if (pfkey_establish(leconf, nbr) == -1)
    		fatalx("pfkey setup failed");
    
    	pconn = pending_conn_find(nbr->af, &nbr->raddr);
    	if (pconn) {
    		session_accept_nbr(nbr, pconn->fd);
    		pending_conn_del(pconn);
    	}
    
    	return (nbr);
    }
    
    void
    nbr_del(struct nbr *nbr)
    {
    	log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
    
    	nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
    	pfkey_remove(nbr);
    
    	if (nbr_pending_connect(nbr))
    		event_del(&nbr->ev_connect);
    	nbr_stop_ktimer(nbr);
    	nbr_stop_ktimeout(nbr);
    	nbr_stop_itimeout(nbr);
    	nbr_stop_idtimer(nbr);
    
    	mapping_list_clr(&nbr->mapping_list);
    	mapping_list_clr(&nbr->withdraw_list);
    	mapping_list_clr(&nbr->request_list);
    	mapping_list_clr(&nbr->release_list);
    	mapping_list_clr(&nbr->abortreq_list);
    
    	if (nbr->peerid)
    		RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr);
    	RB_REMOVE(nbr_id_head, &nbrs_by_id, nbr);
    	RB_REMOVE(nbr_addr_head, &nbrs_by_addr, nbr);
    
    	free(nbr);
    }
    
    static void
    nbr_update_peerid(struct nbr *nbr)
    {
    	static uint32_t	 peercnt = 1;
    
    	if (nbr->peerid)
    		RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr);
    
    	/* get next unused peerid */
    	while (nbr_find_peerid(++peercnt))
    		;
    	nbr->peerid = peercnt;
    
    	if (RB_INSERT(nbr_pid_head, &nbrs_by_pid, nbr) != NULL)
    		fatalx("nbr_update_peerid: RB_INSERT(nbrs_by_pid) failed");
    }
    
    struct nbr *
    nbr_find_ldpid(uint32_t lsr_id)
    {
    	struct nbr	n;
    	n.id.s_addr = lsr_id;
    	return (RB_FIND(nbr_id_head, &nbrs_by_id, &n));
    }
    
    struct nbr *
    nbr_find_addr(int af, union ldpd_addr *addr)
    {
    	struct nbr	n;
    	n.af = af;
    	n.raddr = *addr;
    	return (RB_FIND(nbr_addr_head, &nbrs_by_addr, &n));
    }
    
    struct nbr *
    nbr_find_peerid(uint32_t peerid)
    {
    	struct nbr	n;
    	n.peerid = peerid;
    	return (RB_FIND(nbr_pid_head, &nbrs_by_pid, &n));
    }
    
    int
    nbr_adj_count(struct nbr *nbr, int af)
    {
    	struct adj	*adj;
    	int		 total = 0;
    
    	LIST_FOREACH(adj, &nbr->adj_list, nbr_entry)
    		if (adj_get_af(adj) == af)
    			total++;
    
    	return (total);
    }
    
    int
    nbr_session_active_role(struct nbr *nbr)
    {
    	if (ldp_addrcmp(nbr->af, &nbr->laddr, &nbr->raddr) > 0)
    		return (1);
    
    	return (0);
    }
    
    /* timers */
    
    /* Keepalive timer: timer to send keepalive message to neighbors */
    
    static void
    nbr_ktimer(int fd, short event, void *arg)
    {
    	struct nbr	*nbr = arg;
    
    	send_keepalive(nbr);
    	nbr_start_ktimer(nbr);
    }
    
    static void
    nbr_start_ktimer(struct nbr *nbr)
    {
    	struct timeval	 tv;
    
    	/* send three keepalives per period */
    	timerclear(&tv);
    	tv.tv_sec = (time_t)(nbr->keepalive / KEEPALIVE_PER_PERIOD);
    	if (evtimer_add(&nbr->keepalive_timer, &tv) == -1)
    		fatal(__func__);
    }
    
    void
    nbr_stop_ktimer(struct nbr *nbr)
    {
    	if (evtimer_pending(&nbr->keepalive_timer, NULL) &&
    	    evtimer_del(&nbr->keepalive_timer) == -1)
    		fatal(__func__);
    }
    
    /* Keepalive timeout: if the nbr hasn't sent keepalive */
    
    static void
    nbr_ktimeout(int fd, short event, void *arg)
    {
    	struct nbr *nbr = arg;
    
    	log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
    
    	session_shutdown(nbr, S_KEEPALIVE_TMR, 0, 0);
    }
    
    static void
    nbr_start_ktimeout(struct nbr *nbr)
    {
    	struct timeval	tv;
    
    	timerclear(&tv);
    	tv.tv_sec = nbr->keepalive;
    
    	if (evtimer_add(&nbr->keepalive_timeout, &tv) == -1)
    		fatal(__func__);
    }
    
    void
    nbr_stop_ktimeout(struct nbr *nbr)
    {
    	if (evtimer_pending(&nbr->keepalive_timeout, NULL) &&
    	    evtimer_del(&nbr->keepalive_timeout) == -1)
    		fatal(__func__);
    }
    
    /* Session initialization timeout: if nbr got stuck in the initialization FSM */
    
    static void
    nbr_itimeout(int fd, short event, void *arg)
    {
    	struct nbr *nbr = arg;
    
    	log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
    
    	nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
    }
    
    static void
    nbr_start_itimeout(struct nbr *nbr)
    {
    	struct timeval	 tv;
    
    	timerclear(&tv);
    	tv.tv_sec = INIT_FSM_TIMEOUT;
    	if (evtimer_add(&nbr->init_timeout, &tv) == -1)
    		fatal(__func__);
    }
    
    void
    nbr_stop_itimeout(struct nbr *nbr)
    {
    	if (evtimer_pending(&nbr->init_timeout, NULL) &&
    	    evtimer_del(&nbr->init_timeout) == -1)
    		fatal(__func__);
    }
    
    /* Init delay timer: timer to retry to iniziatize session */
    
    static void
    nbr_idtimer(int fd, short event, void *arg)
    {
    	struct nbr *nbr = arg;
    
    	log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
    
    	nbr_establish_connection(nbr);
    }
    
    void
    nbr_start_idtimer(struct nbr *nbr)
    {
    	struct timeval	tv;
    
    	timerclear(&tv);
    
    	tv.tv_sec = INIT_DELAY_TMR;
    	switch(nbr->idtimer_cnt) {
    	default:
    		/* do not further increase the counter */
    		tv.tv_sec = MAX_DELAY_TMR;
    		break;
    	case 2:
    		tv.tv_sec *= 2;
    		/* FALLTHROUGH */
    	case 1:
    		tv.tv_sec *= 2;
    		/* FALLTHROUGH */
    	case 0:
    		nbr->idtimer_cnt++;
    		break;
    	}
    
    	if (evtimer_add(&nbr->initdelay_timer, &tv) == -1)
    		fatal(__func__);
    }
    
    void
    nbr_stop_idtimer(struct nbr *nbr)
    {
    	if (evtimer_pending(&nbr->initdelay_timer, NULL) &&
    	    evtimer_del(&nbr->initdelay_timer) == -1)
    		fatal(__func__);
    }
    
    int
    nbr_pending_idtimer(struct nbr *nbr)
    {
    	if (evtimer_pending(&nbr->initdelay_timer, NULL))
    		return (1);
    
    	return (0);
    }
    
    int
    nbr_pending_connect(struct nbr *nbr)
    {
    	if (event_initialized(&nbr->ev_connect) &&
    	    event_pending(&nbr->ev_connect, EV_WRITE, NULL))
    		return (1);
    
    	return (0);
    }
    
    static void
    nbr_connect_cb(int fd, short event, void *arg)
    {
    	struct nbr	*nbr = arg;
    	int		 error;
    	socklen_t	 len;
    
    	len = sizeof(error);
    	if (getsockopt(nbr->fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) {
    		log_warn("%s: getsockopt SOL_SOCKET SO_ERROR", __func__);
    		return;
    	}
    
    	if (error) {
    		close(nbr->fd);
    		errno = error;
    		log_debug("%s: error while connecting to %s: %s", __func__,
    		    log_addr(nbr->af, &nbr->raddr), strerror(errno));
    		return;
    	}
    
    	nbr_fsm(nbr, NBR_EVT_CONNECT_UP);
    }
    
    int
    nbr_establish_connection(struct nbr *nbr)
    {
    	struct sockaddr_storage	 local_sa;
    	struct sockaddr_storage	 remote_sa;
    	struct adj		*adj;
    	struct nbr_params	*nbrp;
    	int			 opt = 1;
    
    	nbr->fd = socket(nbr->af,
    	    SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
    	if (nbr->fd == -1) {
    		log_warn("%s: error while creating socket", __func__);
    		return (-1);
    	}
    
    	if (nbr->auth_established) {
    		if (sysdep.no_pfkey || sysdep.no_md5sig) {
    			log_warnx("md5sig configured but not available");
    			close(nbr->fd);
    			return (-1);
    		}
    		if (setsockopt(nbr->fd, IPPROTO_TCP, TCP_MD5SIG,
    		    &opt, sizeof(opt)) == -1) {
    			log_warn("setsockopt md5sig");
    			close(nbr->fd);
    			return (-1);
    		}
    	}
    
    	memcpy(&local_sa, addr2sa(nbr->af, &nbr->laddr, 0), sizeof(local_sa));
    	memcpy(&remote_sa, addr2sa(nbr->af, &nbr->raddr, LDP_PORT),
    	    sizeof(local_sa));
    	if (nbr->af == AF_INET6 && nbr->raddr_scope)
    		addscope((struct sockaddr_in6 *)&remote_sa, nbr->raddr_scope);
    
    	if (bind(nbr->fd, (struct sockaddr *)&local_sa,
    	    local_sa.ss_len) == -1) {
    		log_warn("%s: error while binding socket to %s", __func__,
    		    log_sockaddr((struct sockaddr *)&local_sa));
    		close(nbr->fd);
    		return (-1);
    	}
    
    	nbrp = nbr_params_find(leconf, nbr->id);
    	if (nbr_gtsm_check(nbr->fd, nbr, nbrp)) {
    		close(nbr->fd);
    		return (-1);
    	}
    
    	/*
    	 * Send an extra hello to guarantee that the remote peer has formed
    	 * an adjacency as well.
    	 */
    	LIST_FOREACH(adj, &nbr->adj_list, nbr_entry)
    		send_hello(adj->source.type, adj->source.link.ia,
    		    adj->source.target);
    
    	if (connect(nbr->fd, (struct sockaddr *)&remote_sa,
    	    remote_sa.ss_len) == -1) {
    		if (errno == EINPROGRESS) {
    			event_set(&nbr->ev_connect, nbr->fd, EV_WRITE,
    			    nbr_connect_cb, nbr);
    			event_add(&nbr->ev_connect, NULL);
    			return (0);
    		}
    		log_warn("%s: error while connecting to %s", __func__,
    		    log_sockaddr((struct sockaddr *)&remote_sa));
    		close(nbr->fd);
    		return (-1);
    	}
    
    	/* connection completed immediately */
    	nbr_fsm(nbr, NBR_EVT_CONNECT_UP);
    
    	return (0);
    }
    
    int
    nbr_gtsm_enabled(struct nbr *nbr, struct nbr_params *nbrp)
    {
    	/*
    	 * RFC 6720 - Section 3:
    	 * "This document allows for the implementation to provide an option to
    	 * statically (e.g., via configuration) and/or dynamically override the
    	 * default behavior and enable/disable GTSM on a per-peer basis".
    	 */
    	if (nbrp && (nbrp->flags & F_NBRP_GTSM))
    		return (nbrp->gtsm_enabled);
    
    	if ((ldp_af_conf_get(leconf, nbr->af))->flags & F_LDPD_AF_NO_GTSM)
    		return (0);
    
    	/* By default, GTSM support has to be negotiated for LDPv4 */
    	if (nbr->af == AF_INET && !(nbr->flags & F_NBR_GTSM_NEGOTIATED))
    		return (0);
    
    	return (1);
    }
    
    int
    nbr_gtsm_setup(int fd, int af, struct nbr_params *nbrp)
    {
    	int	 ttl = 255;
    
    	if (nbrp && (nbrp->flags & F_NBRP_GTSM_HOPS))
    		ttl = 256 - nbrp->gtsm_hops;
    
    	switch (af) {
    	case AF_INET:
    		if (sock_set_ipv4_minttl(fd, ttl) == -1)
    			return (-1);
    		ttl = 255;
    		if (sock_set_ipv4_ucast_ttl(fd, ttl) == -1)
    			return (-1);
    		break;
    	case AF_INET6:
    		if (sock_set_ipv6_minhopcount(fd, ttl) == -1)
    			return (-1);
    		ttl = 255;
    		if (sock_set_ipv6_ucast_hops(fd, ttl) == -1)
    			return (-1);
    		break;
    	default:
    		fatalx("nbr_gtsm_setup: unknown af");
    	}
    
    	return (0);
    }
    
    int
    nbr_gtsm_check(int fd, struct nbr *nbr, struct nbr_params *nbrp)
    {
    	if (!nbr_gtsm_enabled(nbr, nbrp)) {
    		switch (nbr->af) {
    		case AF_INET:
    			sock_set_ipv4_ucast_ttl(fd, -1);
    			break;
    		case AF_INET6:
    			/*
    			 * Send packets with a Hop Limit of 255 even when GSTM
    			 * is disabled to guarantee interoperability.
    			 */
    			sock_set_ipv6_ucast_hops(fd, 255);
    			break;
    		default:
    			fatalx("nbr_gtsm_check: unknown af");
    			break;
    		}
    		return (0);
    	}
    
    	if (nbr_gtsm_setup(fd, nbr->af, nbrp) == -1) {
    		log_warnx("%s: error enabling GTSM for lsr-id %s", __func__,
    		    inet_ntoa(nbr->id));
    		return (-1);
    	}
    
    	return (0);
    }
    
    static int
    nbr_act_session_operational(struct nbr *nbr)
    {
    	struct lde_nbr	 lde_nbr;
    
    	nbr->idtimer_cnt = 0;
    
    	/* this is necessary to avoid ipc synchronization issues */
    	nbr_update_peerid(nbr);
    
    	memset(&lde_nbr, 0, sizeof(lde_nbr));
    	lde_nbr.id = nbr->id;
    	lde_nbr.v4_enabled = nbr->v4_enabled;
    	lde_nbr.v6_enabled = nbr->v6_enabled;
    	lde_nbr.flags = nbr->flags;
    	return (ldpe_imsg_compose_lde(IMSG_NEIGHBOR_UP, nbr->peerid, 0,
    	    &lde_nbr, sizeof(lde_nbr)));
    }
    
    static void
    nbr_send_labelmappings(struct nbr *nbr)
    {
    	ldpe_imsg_compose_lde(IMSG_LABEL_MAPPING_FULL, nbr->peerid, 0,
    	    NULL, 0);
    }
    
    struct nbr_params *
    nbr_params_new(struct in_addr lsr_id)
    {
    	struct nbr_params	*nbrp;
    
    	if ((nbrp = calloc(1, sizeof(*nbrp))) == NULL)
    		fatal(__func__);
    
    	nbrp->lsr_id = lsr_id;
    
    	return (nbrp);
    }
    
    struct nbr_params *
    nbr_params_find(struct ldpd_conf *xconf, struct in_addr lsr_id)
    {
    	struct nbr_params *nbrp;
    
    	LIST_FOREACH(nbrp, &xconf->nbrp_list, entry)
    		if (nbrp->lsr_id.s_addr == lsr_id.s_addr)
    			return (nbrp);
    
    	return (NULL);
    }
    
    uint16_t
    nbr_get_keepalive(int af, struct in_addr lsr_id)
    {
    	struct nbr_params	*nbrp;
    
    	nbrp = nbr_params_find(leconf, lsr_id);
    	if (nbrp && (nbrp->flags & F_NBRP_KEEPALIVE))
    		return (nbrp->keepalive);
    
    	return ((ldp_af_conf_get(leconf, af))->keepalive);
    }
    
    struct ctl_nbr *
    nbr_to_ctl(struct nbr *nbr)
    {
    	static struct ctl_nbr	 nctl;
    	struct timeval		 now;
    
    	nctl.af = nbr->af;
    	nctl.id = nbr->id;
    	nctl.laddr = nbr->laddr;
    	nctl.raddr = nbr->raddr;
    	nctl.nbr_state = nbr->state;
    
    	gettimeofday(&now, NULL);
    	if (nbr->state == NBR_STA_OPER) {
    		nctl.uptime = now.tv_sec - nbr->uptime;
    	} else
    		nctl.uptime = 0;
    
    	return (&nctl);
    }
    
    void
    nbr_clear_ctl(struct ctl_nbr *nctl)
    {
    	struct nbr		*nbr;
    
    	RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) {
    		if (ldp_addrisset(nctl->af, &nctl->raddr) &&
    		    ldp_addrcmp(nctl->af, &nctl->raddr, &nbr->raddr))
    			continue;
    
    		log_debug("%s: neighbor %s manually cleared", __func__,
    		    log_addr(nbr->af, &nbr->raddr));
    		session_shutdown(nbr, S_SHUTDOWN, 0, 0);
    	}
    }