summaryrefslogtreecommitdiffstats
path: root/bgpd/bgp_network.c
diff options
context:
space:
mode:
Diffstat (limited to 'bgpd/bgp_network.c')
-rw-r--r--bgpd/bgp_network.c1198
1 files changed, 803 insertions, 395 deletions
diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c
index 9e3427d2..d857c856 100644
--- a/bgpd/bgp_network.c
+++ b/bgpd/bgp_network.c
@@ -1,210 +1,676 @@
/* BGP network related fucntions
- Copyright (C) 1999 Kunihiro Ishiguro
-
-This file is part of GNU Zebra.
-
-GNU Zebra is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation; either version 2, or (at your option) any
-later version.
-
-GNU Zebra is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU Zebra; see the file COPYING. If not, write to the Free
-Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-02111-1307, USA. */
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
#include <zebra.h>
-#include "thread.h"
#include "sockunion.h"
#include "sockopt.h"
#include "memory.h"
#include "log.h"
#include "if.h"
#include "prefix.h"
-#include "command.h"
+//#include "command.h"
#include "privs.h"
-#include "linklist.h"
-#include "bgpd/bgpd.h"
-#include "bgpd/bgp_fsm.h"
-#include "bgpd/bgp_attr.h"
#include "bgpd/bgp_debug.h"
#include "bgpd/bgp_network.h"
+#include "bgpd/bgp_engine.h"
+#include "bgpd/bgp_session.h"
+#include "bgpd/bgp_connection.h"
+#include "bgpd/bgp_fsm.h"
+#include "qpselect.h"
+
extern struct zebra_privs_t bgpd_privs;
+/*==============================================================================
+ * This is the socket connect/listen/accept/close stuff for the BGP Engine.
+ *
+ * NB: this code is for use in the BGP Engine *only*.
+ */
+
+/* Forward references. */
+static void
+bgp_connect_action(qps_file qf, void* file_info) ;
+
+static void
+bgp_accept_action(qps_file qf, void* file_info) ;
+
+static int
+bgp_getsockname(int fd, union sockunion* su_local, union sockunion* su_remote) ;
+
+static int
+bgp_socket_set_common_options(int fd, union sockunion* su, int ttl,
+ const char* password) ;
+/*==============================================================================
+ * Open and close the listeners.
+ *
+ * When the BGP Engine is started it is passed the address and port to listen
+ * to. By default the address is NULL, which maps to INADDR_ANY and
+ * (if supported) IN6ADDR_ANY_INIT.
+ *
+ * When the BGP Engine is stopped the listening ports are closed.
+ *
+ * NB: once the listeners are opened they are active in the BGP Engine Nexus,
+ * and will be fielding attempts to connect.
+ *
+ * The BGP listeners are kept here. Keep lists of IPv4 and IPv6 listeners for
+ * the convenience of setting MD5 passwords.
+ */
+
+typedef struct bgp_listener* bgp_listener ;
+
+static bgp_listener bgp_listeners[] =
+ {
+ [AF_INET] = NULL,
+ [AF_INET6] = NULL
+ } ;
+
+CONFIRM((AF_INET < 20) && (AF_INET6 < 20)) ;
+
/* BGP listening socket. */
struct bgp_listener
{
- int fd;
- union sockunion su;
- struct thread *thread;
-};
-
-/*
- * Set MD5 key for the socket, for the given IPv4 peer address.
- * If the password is NULL or zero-length, the option will be disabled.
+ bgp_listener next ;
+ struct qps_file qf ;
+ union sockunion su ;
+} ;
+
+/* Forward reference */
+static int bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen) ;
+
+/*------------------------------------------------------------------------------
+ * Open Listeners.
+ *
+ * Using given address and port, get all possible addresses and set up a
+ * listener on each one.
+ *
+ * NB: only listens on AF_INET and (if HAVE_IPV6) AF_INET6.
+ *
+ * Returns: 0 => OK
+ * -1 => failed -- no listeners set up
+ *
*/
-static int
-bgp_md5_set_socket (int socket, union sockunion *su, const char *password)
+extern int
+bgp_open_listeners(unsigned short port, const char *address)
{
- int ret = -1;
- int en = ENOSYS;
-
- assert (socket >= 0);
-
-#if HAVE_DECL_TCP_MD5SIG
- ret = sockopt_tcp_signature (socket, su, password);
- en = errno;
-#endif /* HAVE_TCP_MD5SIG */
-
- if (ret < 0)
- zlog (NULL, LOG_WARNING, "can't set TCP_MD5SIG option on socket %d: %s",
- socket, safe_strerror (en));
+#if defined (HAVE_IPV6) && ! defined (NRL) /*----------------------------*/
- return ret;
-}
+ /* IPv6 supported version of BGP server socket setup. */
-/* Helper for bgp_connect */
-static int
-bgp_md5_set_connect (int socket, union sockunion *su, const char *password)
-{
- int ret = -1;
+ struct addrinfo *ainfo;
+ struct addrinfo *ainfo_save;
+ int ret, count;
+ char port_str[16];
-#if HAVE_DECL_TCP_MD5SIG
- if ( bgpd_privs.change (ZPRIVS_RAISE) )
+ static const struct addrinfo req = {
+ .ai_family = AF_UNSPEC,
+ .ai_flags = AI_PASSIVE,
+ .ai_socktype = SOCK_STREAM,
+ } ;
+
+ snprintf (port_str, sizeof(port_str), "%d", port);
+ port_str[sizeof (port_str) - 1] = '\0';
+
+ ret = getaddrinfo (address, port_str, &req, &ainfo_save);
+ if (ret != 0)
{
- zlog_err ("%s: could not raise privs", __func__);
- return ret;
+ zlog_err ("getaddrinfo: %s", gai_strerror (ret));
+ return -1;
}
-
- ret = bgp_md5_set_socket (socket, su, password);
- if (bgpd_privs.change (ZPRIVS_LOWER) )
- zlog_err ("%s: could not lower privs", __func__);
-#endif /* HAVE_TCP_MD5SIG */
-
- return ret;
-}
+ count = 0;
+ for (ainfo = ainfo_save; ainfo; ainfo = ainfo->ai_next)
+ {
+ int sock;
-int
-bgp_md5_set (struct peer *peer)
-{
- struct listnode *node;
- int ret = 0;
- struct bgp_listener *listener;
+ if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6)
+ continue;
- if ( bgpd_privs.change (ZPRIVS_RAISE) )
+ sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
+ if (sock < 0)
+ {
+ zlog_err ("socket: %s", safe_strerror (errno));
+ continue;
+ }
+
+ ret = bgp_init_listener(sock, ainfo->ai_addr, ainfo->ai_addrlen);
+
+ if (ret == 0)
+ ++count;
+ else
+ close(sock);
+ }
+ freeaddrinfo (ainfo_save);
+
+ if (count == 0)
{
- zlog_err ("%s: could not raise privs", __func__);
+ zlog_err ("%s: no usable addresses", __func__);
return -1;
}
-
- /* Just set the password on the listen socket(s). Outbound connections
- * are taken care of in bgp_connect() below.
- */
- for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener))
- if (listener->su.sa.sa_family == peer->su.sa.sa_family)
- {
- ret = bgp_md5_set_socket (listener->fd, &peer->su, peer->password);
- break;
- }
- if (bgpd_privs.change (ZPRIVS_LOWER) )
- zlog_err ("%s: could not lower privs", __func__);
-
- return ret;
+ return 0;
}
-
-/* Accept bgp connection. */
-static int
-bgp_accept (struct thread *thread)
-{
- int bgp_sock;
- int accept_sock;
- union sockunion su;
- struct bgp_listener *listener = THREAD_ARG(thread);
- struct peer *peer;
- struct peer *peer1;
- char buf[SU_ADDRSTRLEN];
-
- /* Register accept thread. */
- accept_sock = THREAD_FD (thread);
- if (accept_sock < 0)
+#else /*----------------------------------------------------*/
+
+ /* Traditional IPv4 only version. */
+
+ int sock;
+ int socklen;
+ struct sockaddr_in sin;
+ int ret, en;
+
+ memset (&sin, 0, sizeof (struct sockaddr_in));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons (port);
+ socklen = sizeof (struct sockaddr_in);
+
+ if (address && ((ret = inet_aton(address, &sin.sin_addr)) < 1))
{
- zlog_err ("accept_sock is nevative value %d", accept_sock);
- return -1;
+ zlog_err("bgp_socket: could not parse ip address %s: %s",
+ address, safe_strerror (errno));
+ return ret;
}
- listener->thread = thread_add_read (master, bgp_accept, listener, accept_sock);
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+ sin.sin_len = socklen;
+#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
- /* Accept client connection. */
- bgp_sock = sockunion_accept (accept_sock, &su);
- if (bgp_sock < 0)
+ sock = socket (AF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
{
- zlog_err ("[Error] BGP socket accept failed (%s)", safe_strerror (errno));
- return -1;
+ zlog_err ("socket: %s", safe_strerror (errno));
+ return sock;
}
- if (BGP_DEBUG (events, EVENTS))
- zlog_debug ("[Event] BGP connection from host %s", inet_sutop (&su, buf));
-
- /* Check remote IP address */
- peer1 = peer_lookup (NULL, &su);
- if (! peer1 || peer1->status == Idle)
+ ret = bgp_init_listener (sock, (struct sockaddr *) &sin, socklen);
+ if (ret < 0)
{
- if (BGP_DEBUG (events, EVENTS))
- {
- if (! peer1)
- zlog_debug ("[Event] BGP connection IP address %s is not configured",
- inet_sutop (&su, buf));
- else
- zlog_debug ("[Event] BGP connection IP address %s is Idle state",
- inet_sutop (&su, buf));
- }
- close (bgp_sock);
- return -1;
+ close (sock);
+ return ret;
}
+ return sock;
+}
+#endif /* HAVE_IPV6 && !NRL --------------------------------------------------*/
+
+/*------------------------------------------------------------------------------
+ * Close Listeners.
+ *
+ * Empty the listener lists, close files, remove from the selection.
+ *
+ */
+static void bgp_reset_listeners(bgp_listener* p_listener) ;
+
+extern void
+bgp_close_listeners(void)
+{
+ bgp_reset_listeners(&bgp_listeners[AF_INET]) ;
+ bgp_reset_listeners(&bgp_listeners[AF_INET6]) ;
+} ;
- /* In case of peer is EBGP, we should set TTL for this connection. */
- if (peer_sort (peer1) == BGP_PEER_EBGP)
- sockopt_ttl (peer1->su.sa.sa_family, bgp_sock, peer1->ttl);
+static void
+bgp_reset_listeners(bgp_listener* p_listener)
+{
+ bgp_listener listener ;
+ bgp_listener next ;
+
+ next = *p_listener ;
+ *p_listener = NULL ;
+
+ while (next != NULL)
+ {
+ listener = next ;
+ next = listener->next ;
+
+ close(qps_file_fd(&listener->qf)) ;
+ qps_remove_file(&listener->qf) ;
+
+ XFREE(MTYPE_BGP_LISTENER, listener) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Initialise Listener Socket
+ *
+ * Sets up socket with the usual options. Binds to given address and listens.
+ *
+ * If all that is successful, creates bgp_listener, sets up qpselect file, adds
+ * to the BGP Engine selection and enables it for reading.
+ *
+ * Listener read events are handled by bgp_accept_action().
+ *
+ * Returns: 0 : OK
+ * != 0 : error number (from errno or otherwise)
+ */
+static int
+bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen)
+{
+ bgp_listener listener ;
+ int ret ;
- /* Make dummy peer until read Open packet. */
- if (BGP_DEBUG (events, EVENTS))
- zlog_debug ("[Event] Make dummy peer structure until read Open packet");
+ ret = bgp_socket_set_common_options(sock, (union sockunion*)sa, 0, NULL) ;
+ if (ret != 0)
+ return ret ;
+#ifdef IPV6_V6ONLY
+ /* Want only IPV6 on ipv6 socket (not mapped addresses) */
+ if (sa->sa_family == AF_INET6)
{
- char buf[SU_ADDRSTRLEN + 1];
-
- peer = peer_create_accept (peer1->bgp);
- SET_FLAG (peer->sflags, PEER_STATUS_ACCEPT_PEER);
- peer->su = su;
- peer->fd = bgp_sock;
- peer->status = Active;
- peer->local_id = peer1->local_id;
- peer->v_holdtime = peer1->v_holdtime;
- peer->v_keepalive = peer1->v_keepalive;
-
- /* Make peer's address string. */
- sockunion2str (&su, buf, SU_ADDRSTRLEN);
- peer->host = XSTRDUP (MTYPE_BGP_PEER_HOST, buf);
+ int on = 1;
+ /* TODO: trap errors when setting IPPROTO_IPV6, IPV6_V6ONLY ?? */
+ setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)) ;
}
+#endif
- BGP_EVENT_ADD (peer, TCP_connection_open);
+ if (bgpd_privs.change(ZPRIVS_RAISE))
+ {
+ ret = errno ;
+ zlog_err("%s: could not raise privs", __func__);
- return 0;
-}
+ return ret ;
+ } ;
+
+ ret = bind(sock, sa, salen) ;
+ if (ret < 0)
+ {
+ ret = errno ;
+ zlog_err ("bind: %s", safe_strerror(ret));
+ } ;
+
+ if (bgpd_privs.change(ZPRIVS_LOWER))
+ {
+ if (ret == 0)
+ ret = errno ;
+ zlog_err("%s: could not lower privs", __func__) ;
+ } ;
+
+ if (ret != 0)
+ return ret ;
+
+ ret = listen (sock, 3);
+ if (ret < 0)
+ {
+ ret = errno ;
+ zlog_err ("listen: %s", safe_strerror(ret)) ;
+ return ret;
+ }
+
+ /* Having successfully opened the listener, record it so that can be found
+ * again, add it to the BGP Engine Nexus file selection and enable it for
+ * reading.
+ */
+
+ listener = XCALLOC(MTYPE_BGP_LISTENER, sizeof(struct bgp_listener)) ;
+
+ qps_file_init_new(&listener->qf, NULL) ;
+ qps_add_file(p_bgp_engine->selection, &listener->qf, sock, listener) ;
+ qps_enable_mode(&listener->qf, qps_read_mnum, bgp_accept_action) ;
+
+ memcpy(&listener->su, sa, salen) ;
+
+ listener->next = bgp_listeners[sa->sa_family] ;
+ bgp_listeners[sa->sa_family] = listener ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepare to accept() connection
+ *
+ * Sets session->accept true -- so accept() action will accept the connection.
+ *
+ *
+ *
+ * NB: requires the session mutex LOCKED.
+ */
+extern void
+bgp_open_accept(bgp_connection connection)
+{
+
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept bgp connection -- this is the read action for qpselect.
+ *
+ * Accepts the connection, then checks to see whether the source is a configured
+ * peer, and if it is, whether currently accepting connections from that peer.
+ *
+ * If connection passes those tests, sets up the new listener connection for
+ * the session (including qpselect file), and kicks the FSM for that into life
+ * by generating a bgp_fsm_TCP_connection_open event. At this point the qfile
+ * is not enabled in any mode and no timers are running.
+ *
+ * NB: uses bgp_session_lookup() to find the session, so will lock and unlock
+ * its mutex.
+ *
+ * NB: locks and unlocks the session mutex.
+ *
+ * NB: does not set up connection unless all parts of the accept process
+ * succeed.
+ *
+ * Events and Errors:
+ *
+ * * if the accept() fails, log (err) the error and continue.
+ *
+ * Error is no associated with any connection or session.
+ *
+ * * if the connection is not acceptable, because:
+ *
+ * (a) peer is not configured
+ * (b) session not currently accepting connections (for whatever reason)
+ *
+ * log (debug) the event and continue.
+ *
+ * -- could Cease/Connection Rejected in most cases
+ * -- could Cease/Connection Collision Resolution in those cases
+ *
+ * * if the connection is acceptable, but fails in getting the remote/local
+ * addresses or in setting options
+ *
+ * report error on primary connection and generate bgp_fsm_TCP_fatal_error
+ * event.
+ *
+ * * if all goes well, generate bgp_fsm_TCP_connection_open either for the
+ * new (secondary) connection or for the primary.
+ *
+ * Sets connection->err to the error (if any).
+ */
+static void
+bgp_accept_action(qps_file qf, void* file_info)
+{
+ bgp_connection connection ;
+ bgp_session session ;
+ union sockunion su_remote ;
+ union sockunion su_local ;
+ int exists ;
+ int fd ;
+ int ret ;
+ char buf[SU_ADDRSTRLEN] ;
+
+ /* Accept client connection. */
+ fd = sockunion_accept(qps_file_fd(qf), &su_remote) ;
+ if (fd < 0)
+ {
+ if (fd == -1)
+ zlog_err("[Error] BGP socket accept failed (%s)",
+ safe_strerror(errno)) ;
+ return ; /* have no connection to report this to */
+ } ;
+
+ if (BGP_DEBUG(events, EVENTS))
+ zlog_debug("[Event] BGP connection from host %s",
+ inet_sutop(&su_remote, buf)) ;
+
+ /* See if we are ready to accept connections from the connecting party */
+ session = bgp_session_lookup(&su_remote, &exists) ;
+ if (bgp_session_is_accepting(session))
+ {
+ if (BGP_DEBUG(events, EVENTS))
+ zlog_debug(exists
+ ? "[Event] BGP accept IP address %s is not accepting"
+ : "[Event] BGP accept IP address %s is not configured",
+ inet_sutop(&su_remote, buf)) ;
+ close(fd) ;
+ return ; /* quietly reject connection */
+ /* RFC recommends sending a NOTIFICATION... */
+ } ;
+
+ /* Will accept the connection.
+ *
+ * Now need the session locked, 'cos are about to start a new connection.
+ *
+ * This is running in the BGP Engine thread, so cannot in any case be
+ * foxed by the other connection making changes.
+ *
+ * The session is active, so the Peering Engine will not make any changes
+ * except under the mutex, and will not destroy the session.
+ */
+
+ BGP_SESSION_LOCK(session) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+
+ /* Set the common socket options.
+ * Does not set password -- that is inherited from the listener.
+ *
+ * If all is well, set up the listener connection, and set it ready
+ * to go. Set session not to accept further inbound connections.
+ *
+ * Kicks the FSM with bgp_fsm_TCP_connection_open.
+ */
+
+ ret = bgp_getsockname(fd, &su_local, &su_remote) ;
+ if (ret != 0)
+ ret = bgp_socket_set_common_options(fd, &su_remote, session->ttl, NULL) ;
+
+ connection = session->connections[bgp_connection_secondary] ;
+
+ if (ret == 0)
+ {
+ bgp_connection_open(connection, fd) ;
+
+ memcpy(&connection->su_local, &su_local, sizeof(union sockunion)) ;
+ memcpy(&connection->su_remote, &su_remote, sizeof(union sockunion)) ;
+ }
+ else
+ close(fd) ;
+
+ BGP_SESSION_UNLOCK(session) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+
+ /* Now kick the FSM in an appropriate fashion */
+ bgp_fsm_connect_completed(connection, ret) ;
+} ;
+
+/*==============================================================================
+ * Open BGP Connection -- connect() to the other end
+ */
+
+static int bgp_bind(bgp_connection connection) ;
+static int bgp_update_source (bgp_connection connection) ;
+static bgp_fsm_event_t bgp_sex_connect_error(int err) ;
+
+/*------------------------------------------------------------------------------
+ * Open BGP Connection -- connect() to the other end
+ *
+ * Creates a *non-blocking* socket.
+ *
+ * If fails immediately, generate suitable FSM event -- setting connection->err.
+ *
+ * Success (immediate or otherwise) and delayed failure are dealt with in the
+ * qpselect action -- bgp_connect_action() -- below.
+ *
+ * NB: requires the session mutex LOCKED.
+ */
+extern void
+bgp_open_connect(bgp_connection connection)
+{
+ unsigned int ifindex = 0 ;
+ int fd ;
+ int ret ;
+ union sockunion* p_su = &connection->session->su_peer ;
+
+ /* Make socket for the connect connection. */
+ fd = sockunion_socket(p_su) ;
+ if (fd < 0)
+ return errno ; /* give up immediately if cannot create socket */
+
+ /* Set the common options. */
+ ret = bgp_socket_set_common_options(fd, p_su, connection->session->ttl,
+ connection->session->password) ;
+
+ /* Bind socket. */
+ if (ret == 0)
+ ret = bgp_bind(connection) ;
+
+ /* Update source bind. */
+ if (ret == 0)
+ ret = bgp_update_source(connection) ;
+
+#if 0 /* TODO: worry about peer->ifname and sessions ! */
+#ifdef HAVE_IPV6
+ if (peer->ifname)
+ ifindex = if_nametoindex(peer->ifname);
+#endif /* HAVE_IPV6 */
+#endif
+
+ if (BGP_DEBUG(events, EVENTS))
+ plog_debug(connection->log, "%s [Event] Connect start to %s fd %d",
+ connection->host, connection->host, fd);
+
+ /* Connect to the remote peer. */
+ if (ret == 0)
+ ret = sockunion_connect(fd, p_su, connection->session->port, ifindex) ;
+ /* does not report EINPROGRESS as an error. */
+
+ /* If not OK now, close the fd and signal the error */
+
+ if (ret != 0)
+ {
+ close(fd) ;
+
+ bgp_fsm_connect_completed(connection, ret) ;
+
+ return ;
+ } ;
+
+ /* Set connection waiting for connection to complete.
+ *
+ * The file is then enabled for both read and write:
+ *
+ * if succeeds: will become writable (may also be readable if data turns
+ * up immediately).
+ * if fails: will become readable (may also become writable)
+ *
+ * Generally, expect it to be a while before the fd becomes readable or
+ * writable. But for local connections this may happen immediately. But,
+ * in any case, this will be handled by the qpselect action.
+ */
+
+ bgp_connection_open(connection, fd) ;
+
+ qps_enable_mode(&connection->qf, qps_read_mnum, bgp_connect_action) ;
+ qps_enable_mode(&connection->qf, qps_write_mnum, bgp_connect_action) ;
+
+ return ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Complete non-blocking bgp connect() -- this is the read and write action for
+ * qpselect.
+ *
+ * If the connection succeeds, expect the socket to become writable. May also
+ * become readable if data arrives immediately.
+ *
+ * If the connection fails, expect the socket to become readable. May also
+ * become writable.
+ *
+ * Either way, use getsockopt() to extract any error condition.
+ *
+ * NB: does not require the session mutex.
+ *
+ * Events and Errors:
+ *
+ * * if has succeeded, generate a bgp_fsm_TCP_connection_open event.
+ *
+ * At this point the qfile is not enabled in any mode..
+ *
+ * * if has failed, generate:
+ *
+ * * bgp_fsm_TCP_connection_open_failed event
+ *
+ * for "soft" errors.
+ *
+ * * bgp_fsm_TCP_fatal_error event
+ *
+ * for "hard" errors.
+ *
+ * Sets connection->err to the error (if any).
+ */
+static void
+bgp_connect_action(qps_file qf, void* file_info)
+{
+ bgp_connection connection ;
+ int ret, err ;
+ socklen_t len = sizeof(err) ;
+
+ connection = file_info ;
+
+ /* See if connection successful or not. */
+ /* If successful, set the connection->su_local and ->su_remote */
+
+ ret = getsockopt(qps_file_fd(qf), SOL_SOCKET, SO_ERROR, &err, &len) ;
+ if (ret != 0)
+ ret = errno ;
+ else if (len != sizeof(err))
+ zabort("getsockopt returned unexpected length") ;
+ else if (err != 0)
+ ret = err ;
+ else
+ ret = bgp_getsockname(qps_file_fd(qf), &connection->su_local,
+ &connection->su_remote) ;
+
+ /* In any case, disable both read and write for this file. */
+ qps_disable_modes(qf, qps_write_mbit | qps_read_mbit) ;
+
+ /* Now kick the FSM in an appropriate fashion */
+ bgp_fsm_connect_completed(connection, ret) ;
+} ;
+
+/*==============================================================================
+ * Get local and remote address and port for connection.
+ */
+static int
+bgp_getsockname(int fd, union sockunion* su_local, union sockunion* su_remote)
+{
+ int ret_l, ret_r ;
+
+ ret_l = sockunion_getsockname(fd, su_local) ;
+ ret_r = sockunion_getpeername(fd, su_remote) ;
+
+#if 0 /* TODO: restore setting of peer->nexthop */
+ bgp_nexthop_set(peer->su_local, peer->su_remote, &peer->nexthop, peer);
+#endif
+
+ return (ret_l != 0) ? ret_l : ret_r ;
+} ;
-/* BGP socket bind. */
+/*==============================================================================
+ * Specific binding of outbound connections to interfaces...
+ *
+ */
+
+static struct in_addr* bgp_update_address (struct interface *ifp) ;
+static int bgp_bind_address (int sock, struct in_addr *addr) ;
+
+/*------------------------------------------------------------------------------
+ * BGP socket bind.
+ *
+ * If there is a specific interface to bind an outbound connection to, that
+ * is done here.
+ *
+ *
+ * Returns: 0 : OK (so far so good)
+ * != 0 : error number (from errno or otherwise)
+ */
static int
-bgp_bind (struct peer *peer)
+bgp_bind(bgp_connection connection)
{
+#if 0 /* TODO: restore binding to specific interfaces */
#ifdef SO_BINDTODEVICE
int ret;
struct ifreq ifreq;
@@ -215,10 +681,10 @@ bgp_bind (struct peer *peer)
strncpy ((char *)&ifreq.ifr_name, peer->ifname, sizeof (ifreq.ifr_name));
if ( bgpd_privs.change (ZPRIVS_RAISE) )
- zlog_err ("bgp_bind: could not raise privs");
-
- ret = setsockopt (peer->fd, SOL_SOCKET, SO_BINDTODEVICE,
- &ifreq, sizeof (ifreq));
+ zlog_err ("bgp_bind: could not raise privs");
+
+ ret = setsockopt (peer->fd, SOL_SOCKET, SO_BINDTODEVICE,
+ &ifreq, sizeof (ifreq));
if (bgpd_privs.change (ZPRIVS_LOWER) )
zlog_err ("bgp_bind: could not lower privs");
@@ -229,12 +695,54 @@ bgp_bind (struct peer *peer)
return ret;
}
#endif /* SO_BINDTODEVICE */
+#endif
return 0;
-}
+} ;
+/*------------------------------------------------------------------------------
+ * Update source selection.
+ *
+ * Returns: 0 : OK (so far so good)
+ * != 0 : error number (from errno or otherwise)
+ */
+static int
+bgp_update_source (bgp_connection connection)
+{
+#if 0 /* TODO: restore update-source handling */
+ struct interface *ifp;
+ struct in_addr *addr;
+
+ /* Source is specified with interface name. */
+ if (peer->update_if)
+ {
+ ifp = if_lookup_by_name (peer->update_if);
+ if (! ifp)
+ return;
+
+ addr = bgp_update_address (ifp);
+ if (! addr)
+ return;
+
+ bgp_bind_address (peer->fd, addr);
+ }
+
+ /* Source is specified with IP address. */
+ if (peer->update_source)
+ sockunion_bind (peer->fd, peer->update_source, 0, peer->update_source);
+#else
+ return 0 ;
+#endif
+} ;
+
+/*------------------------------------------------------------------------------
+ * Bind given socket to given address... ???
+ *
+ */
static int
bgp_bind_address (int sock, struct in_addr *addr)
{
+#if 0 /* TODO: restore update-source handling */
+
int ret;
struct sockaddr_in local;
@@ -247,20 +755,28 @@ bgp_bind_address (int sock, struct in_addr *addr)
if ( bgpd_privs.change (ZPRIVS_RAISE) )
zlog_err ("bgp_bind_address: could not raise privs");
-
+
ret = bind (sock, (struct sockaddr *)&local, sizeof (struct sockaddr_in));
if (ret < 0)
;
-
+
if (bgpd_privs.change (ZPRIVS_LOWER) )
zlog_err ("bgp_bind_address: could not lower privs");
-
+
+#endif
return 0;
-}
+} ;
+/*------------------------------------------------------------------------------
+ * Update address.... ???
+ *
+ * Returns: address of IPv4 prefix
+ * or: NULL
+ */
static struct in_addr *
bgp_update_address (struct interface *ifp)
{
+#if 0 /* TODO: restore update-source handling */
struct prefix_ipv4 *p;
struct connected *connected;
struct listnode *node;
@@ -270,266 +786,158 @@ bgp_update_address (struct interface *ifp)
p = (struct prefix_ipv4 *) connected->address;
if (p->family == AF_INET)
- return &p->prefix;
+ return &p->prefix;
}
+#endif
return NULL;
}
-/* Update source selection. */
-static void
-bgp_update_source (struct peer *peer)
-{
- struct interface *ifp;
- struct in_addr *addr;
-
- /* Source is specified with interface name. */
- if (peer->update_if)
- {
- ifp = if_lookup_by_name (peer->update_if);
- if (! ifp)
- return;
-
- addr = bgp_update_address (ifp);
- if (! addr)
- return;
-
- bgp_bind_address (peer->fd, addr);
- }
-
- /* Source is specified with IP address. */
- if (peer->update_source)
- sockunion_bind (peer->fd, peer->update_source, 0, peer->update_source);
-}
+/*==============================================================================
+ * BGP Socket Option handling
+ */
-/* BGP try to connect to the peer. */
-int
-bgp_connect (struct peer *peer)
+static int
+bgp_md5_set_socket(int fd, union sockunion *su, const char *password) ;
+
+/*------------------------------------------------------------------------------
+ * Common socket options:
+ *
+ * * non-blocking -- at all times
+ * * reuseaddr
+ * * reuseport
+ * * set TTL if given ttl != 0
+ * * set password if given password != NULL
+ * * for IPv4, set TOS if required
+ *
+ * These options are set on all sockets: connect/listen/accept
+ *
+ * Returns: 0 => OK
+ * != 0 == errno -- not that we really expect any errors here
+ */
+static int
+bgp_socket_set_common_options(int fd, union sockunion* su, int ttl,
+ const char* password)
{
- unsigned int ifindex = 0;
-
- /* Make socket for the peer. */
- peer->fd = sockunion_socket (&peer->su);
- if (peer->fd < 0)
- return -1;
-
- /* If we can get socket for the peer, adjest TTL and make connection. */
- if (peer_sort (peer) == BGP_PEER_EBGP)
- sockopt_ttl (peer->su.sa.sa_family, peer->fd, peer->ttl);
+ int ret ;
+ int val ;
+
+ /* Make socket non-blocking */
+ val = fcntl(fd, F_GETFL, 0) ;
+ if (val != -1) /* Don't really expect it to be -1 (see POSIX) */
+ val = fcntl(fd, F_SETFL, val | O_NONBLOCK) ;
+ if (val == -1)
+ return errno ;
+
+ /* Reuse addr and port */
+ if (sockopt_reuseaddr(fd) < 0)
+ return errno ;
+ if (sockopt_reuseport(fd) < 0)
+ return errno ;
+
+ /* Adjust ttl if required */
+ if (ttl != 0)
+ if ((ret = sockopt_ttl(sockunion_family(su), fd, ttl)) != 0)
+ return ret ;
+
+ /* Set the TCP MD5 "password", if required. */
+ if (password != NULL)
+ if ((ret = bgp_md5_set_socket(fd, su, password)) != 0)
+ return ret ;
- sockopt_reuseaddr (peer->fd);
- sockopt_reuseport (peer->fd);
-
#ifdef IPTOS_PREC_INTERNETCONTROL
- if (sockunion_family (&peer->su) == AF_INET)
- setsockopt_ipv4_tos (peer->fd, IPTOS_PREC_INTERNETCONTROL);
+ /* set IPPROTO_IP/IP_TOS -- if is AF_INET */
+ if (sockunion_family(su) == AF_INET)
+ if (setsockopt_ipv4_tos(fd, IPTOS_PREC_INTERNETCONTROL) < 0)
+ return errno ;
#endif
- if (peer->password)
- bgp_md5_set_connect (peer->fd, &peer->su, peer->password);
-
- /* Bind socket. */
- bgp_bind (peer);
-
- /* Update source bind. */
- bgp_update_source (peer);
-
-#ifdef HAVE_IPV6
- if (peer->ifname)
- ifindex = if_nametoindex (peer->ifname);
-#endif /* HAVE_IPV6 */
-
- if (BGP_DEBUG (events, EVENTS))
- plog_debug (peer->log, "%s [Event] Connect start to %s fd %d",
- peer->host, peer->host, peer->fd);
-
- /* Connect to the remote peer. */
- return sockunion_connect (peer->fd, &peer->su, htons (peer->port), ifindex);
-}
-
-/* After TCP connection is established. Get local address and port. */
-void
-bgp_getsockname (struct peer *peer)
-{
- if (peer->su_local)
- {
- sockunion_free (peer->su_local);
- peer->su_local = NULL;
- }
-
- if (peer->su_remote)
- {
- sockunion_free (peer->su_remote);
- peer->su_remote = NULL;
- }
-
- peer->su_local = sockunion_getsockname (peer->fd);
- peer->su_remote = sockunion_getpeername (peer->fd);
-
- bgp_nexthop_set (peer->su_local, peer->su_remote, &peer->nexthop, peer);
-}
-
+ return 0 ;
+} ;
+/*------------------------------------------------------------------------------
+ * Set (or clear) MD5 key for the socket, for the given IPv4 peer address.
+ *
+ * If the password is NULL or zero-length, the option will be disabled.
+ *
+ * Returns: 0 => OK
+ * otherwise: errno
+ *
+ * NB: if MD5 is not supported, returns ENOSYS error (but it should not come
+ * to this !).
+ *
+ * NB: has to change up privileges, which can fail (if things are badly set up)
+ */
static int
-bgp_listener (int sock, struct sockaddr *sa, socklen_t salen)
+bgp_md5_set_socket(int fd, union sockunion *su, const char *password)
{
- struct bgp_listener *listener;
- int ret, en;
+ int ret ;
- sockopt_reuseaddr (sock);
- sockopt_reuseport (sock);
+ assert(fd >= 0) ;
-#ifdef IPTOS_PREC_INTERNETCONTROL
- if (sa->sa_family == AF_INET)
- setsockopt_ipv4_tos (sock, IPTOS_PREC_INTERNETCONTROL);
-#endif
-
-#ifdef IPV6_V6ONLY
- /* Want only IPV6 on ipv6 socket (not mapped addresses) */
- if (sa->sa_family == AF_INET6) {
- int on = 1;
- setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY,
- (void *) &on, sizeof (on));
- }
-#endif
-
- if (bgpd_privs.change (ZPRIVS_RAISE) )
- zlog_err ("bgp_socket: could not raise privs");
-
- ret = bind (sock, sa, salen);
- en = errno;
- if (bgpd_privs.change (ZPRIVS_LOWER) )
- zlog_err ("bgp_bind_address: could not lower privs");
-
- if (ret < 0)
- {
- zlog_err ("bind: %s", safe_strerror (en));
- return ret;
- }
-
- ret = listen (sock, 3);
- if (ret < 0)
+ if (bgpd_privs.change(ZPRIVS_RAISE))
{
- zlog_err ("listen: %s", safe_strerror (errno));
- return ret;
- }
-
- listener = XMALLOC (MTYPE_BGP_LISTENER, sizeof(*listener));
- listener->fd = sock;
- memcpy(&listener->su, sa, salen);
- listener->thread = thread_add_read (master, bgp_accept, listener, sock);
- listnode_add (bm->listen_sockets, listener);
-
- return 0;
-}
+ ret = errno ;
+ zlog_err("%s: could not raise privs", __func__);
-/* IPv6 supported version of BGP server socket setup. */
-#if defined (HAVE_IPV6) && ! defined (NRL)
-int
-bgp_socket (unsigned short port, const char *address)
-{
- struct addrinfo *ainfo;
- struct addrinfo *ainfo_save;
- static const struct addrinfo req = {
- .ai_family = AF_UNSPEC,
- .ai_flags = AI_PASSIVE,
- .ai_socktype = SOCK_STREAM,
- };
- int ret, count;
- char port_str[BUFSIZ];
+ return ret ;
+ } ;
- snprintf (port_str, sizeof(port_str), "%d", port);
- port_str[sizeof (port_str) - 1] = '\0';
+ ret = sockopt_tcp_signature(fd, su, password) ;
- ret = getaddrinfo (address, port_str, &req, &ainfo_save);
- if (ret != 0)
- {
- zlog_err ("getaddrinfo: %s", gai_strerror (ret));
- return -1;
- }
+ if (ret != 0) /* TODO: error already logged as zlog_err() */
+ zlog (NULL, LOG_WARNING, "can't set TCP_MD5SIG option on socket %d: %s",
+ fd, safe_strerror(ret));
- count = 0;
- for (ainfo = ainfo_save; ainfo; ainfo = ainfo->ai_next)
+ if (bgpd_privs.change(ZPRIVS_LOWER))
{
- int sock;
-
- if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6)
- continue;
-
- sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
- if (sock < 0)
- {
- zlog_err ("socket: %s", safe_strerror (errno));
- continue;
- }
-
- ret = bgp_listener (sock, ainfo->ai_addr, ainfo->ai_addrlen);
if (ret == 0)
- ++count;
- else
- close(sock);
- }
- freeaddrinfo (ainfo_save);
- if (count == 0)
- {
- zlog_err ("%s: no usable addresses", __func__);
- return -1;
- }
+ ret = errno ;
+ zlog_err("%s: could not lower privs", __func__) ;
+ } ;
- return 0;
-}
-#else
-/* Traditional IPv4 only version. */
-int
-bgp_socket (unsigned short port, const char *address)
+ return ret;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set MD5 password for given peer in the listener(s) for the peer's address
+ * family.
+ *
+ * NB: requires the session mutex LOCKED.
+ *
+ * This allows system to accept MD5 "signed" incoming connections from the
+ * given address.
+ *
+ * Returns: 0 => OK
+ * otherwise: errno -- the first error encountered.
+ *
+ * NB: peer address must be AF_INET or (if supported) AF_INET6
+ *
+ * NB: if there are no listeners in the required
+ */
+extern int
+bgp_md5_set_listeners(bgp_connection connection)
{
- int sock;
- int socklen;
- struct sockaddr_in sin;
- int ret, en;
+ bgp_listener listener ;
+ int ret ;
- sock = socket (AF_INET, SOCK_STREAM, 0);
- if (sock < 0)
- {
- zlog_err ("socket: %s", safe_strerror (errno));
- return sock;
- }
+ union sockunion* su = &connection->session->su_peer ;
- memset (&sin, 0, sizeof (struct sockaddr_in));
- sin.sin_family = AF_INET;
- sin.sin_port = htons (port);
- socklen = sizeof (struct sockaddr_in);
+#ifdef HAVE_IPV6
+ assert((su->sa.sa_family == AF_INET) || (su->sa.sa_family == AF_INET6)) ;
+#else
+ assert(su->sa.sa_family == AF_INET) ;
+#endif
- if (address && ((ret = inet_aton(address, &sin.sin_addr)) < 1))
- {
- zlog_err("bgp_socket: could not parse ip address %s: %s",
- address, safe_strerror (errno));
- return ret;
- }
-#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
- sin.sin_len = socklen;
-#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
+ listener = bgp_listeners[su->sa.sa_family] ;
- ret = bgp_listener (sock, (struct sockaddr *) &sin, socklen);
- if (ret < 0)
+ while (listener != NULL)
{
- close (sock);
- return ret;
- }
- return sock;
-}
-#endif /* HAVE_IPV6 && !NRL */
+ ret = bgp_md5_set_socket(qps_file_fd(&listener->qf), su,
+ connection->session->password) ;
+ if (ret != 0)
+ return ret ;
+ } ;
-void
-bgp_close (void)
-{
- struct listnode *node, *next;
- struct bgp_listener *listener;
+ return 0 ;
+} ;
- for (ALL_LIST_ELEMENTS (bm->listen_sockets, node, next, listener))
- {
- thread_cancel (listener->thread);
- close (listener->fd);
- listnode_delete (bm->listen_sockets, listener);
- XFREE (MTYPE_BGP_LISTENER, listener);
- }
-}