/* 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. */ #include #include #include "sockunion.h" #include "sockopt.h" #include "network.h" #include "memory.h" #include "log.h" #include "if.h" #include "prefix.h" #include "privs.h" #include "qpselect.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_connection.h" #include "bgpd/bgp_session.h" #include "bgpd/bgp_peer_index.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_get_names(int sock_fd, union sockunion* su_local, union sockunion* su_remote) ; static int bgp_socket_set_common_options(int sock_fd, sockunion su, bgp_connection connection) ; static int bgp_set_ttl(int sock_fd, bgp_connection connnection, int ttl, bool gtsm) ; static int bgp_md5_set_listeners(union sockunion* su, 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_inet = NULL ; #ifdef HAVE_IPV6 static bgp_listener bgp_listeners_inet6 = NULL ; #endif #if defined(HAVE_IPV6) && !defined(NRL) # define BGP_USE_ADDRINFO 1 #else # define BGP_USE_ADDRINFO 0 #endif /* BGP listening socket. */ struct bgp_listener { bgp_listener next ; struct qps_file qf ; union sockunion su ; } ; /* Get pointer to list base for listeners in the given address family. */ static inline bgp_listener* bgp_listeners(sa_family_t family) { switch (family) { case AF_INET: return &bgp_listeners_inet ; #ifdef HAVE_IPV6 case AF_INET6: return &bgp_listeners_inet6 ; #endif default: zabort("invalid address family") ; } ; } /* Forward reference */ static int bgp_open_listeners_addrinfo(const char* address, unsigned short port) ; static int bgp_open_listeners_simple(const char* address, unsigned short port) ; static int bgp_open_listener(sockunion su, unsigned short port, int sock_type, int sock_protocol) ; /*------------------------------------------------------------------------------ * Open Listeners. * * Using given address and port, get all possible addresses and set up a * listener on each one. * * Accepts: address = NULL => any local address * address = comma separated list of addresses * * NB: an empty address counts as "any local address", so: * * "80.177.246.130,80.177.246.131" -- will listen on those addresses. * * "80.177.246.130," -- will list on that address and * any other local address. * * NB: only listens on AF_INET and AF_INET6 (if HAVE_IPV6). * * Returns: > 0 => OK -- number of listeners set up * -1 => failed -- no listeners set up */ extern int bgp_open_listeners(const char* address, unsigned short port) { int count ; bool done_null ; char* copy ; char* next ; if (address == NULL) address = "" ; copy = XSTRDUP(MTYPE_TMP, address) ; done_null = false ; next = copy ; count = 0 ; do { address = next ; next = strchr(address, ',') ; if (next != NULL) *next++ = '\0' ; /* replace ',' and step past */ if (*address == '\0') { if (done_null) continue ; /* don't do "" more than once */ else done_null = true ; } ; count += BGP_USE_ADDRINFO ? bgp_open_listeners_addrinfo(address, port) : bgp_open_listeners_simple(address, port) ; } while (next != NULL) ; XFREE(MTYPE_TMP, copy) ; if (count == 0) { zlog_err ("%s: no usable addresses", __func__); return -1; } return 0; } ; /*------------------------------------------------------------------------------ * Open listeners using getaddrinfo() to find the addresses. * * Note that this will accept names as well as numeric addresses. * * Returns: count of listeners opened successfully. */ static int bgp_open_listeners_addrinfo(const char* address, unsigned short port) { #if BGP_USE_ADDRINFO # ifndef HAVE_IPV6 # error Using getaddrinfo() but HAVE_IPV6 is not defined ?? # endif struct addrinfo *ainfo; struct addrinfo *ainfo_save; int ret, count; char port_str[16]; 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'; if (*address == '\0') address = NULL ; ret = getaddrinfo (address, port_str, &req, &ainfo_save); if (ret != 0) { zlog_err ("%s: getaddrinfo: %s", __func__, eaitoa(ret, errno, 0).str); return 0 ; } count = 0; for (ainfo = ainfo_save; ainfo; ainfo = ainfo->ai_next) { union sockunion su ; int err ; if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) continue; sockunion_new_sockaddr(&su, ainfo->ai_addr) ; err = bgp_open_listener(&su, port, ainfo->ai_socktype, ainfo->ai_protocol) ; if (err == 0) ++count; } freeaddrinfo (ainfo_save); return count ; #else zabort("bgp_open_listeners_addrinfo not implemented") ; #endif /* BGP_USE_ADDRINFO */ } /*------------------------------------------------------------------------------ * Open listener the old fashioned way. * * NB: if address is "" tries IPv4 and IPv6 (if supported). * * NB: if address is not NULL, must be a numeric IP address (which may be IPv6 * if that is supported). * * Returns: count of listeners opened successfully. */ static int bgp_open_listeners_simple(const char* address, unsigned short port) { union sockunion su ; int err ; int count ; /* If address is not null, must be a single, specific, numeric address */ if (*address != '\0') { int ret = str2sockunion (address, &su) ; if (ret < 0) { zlog_err("bgp_socket: could not parse ip address %s: %s", address, errtoa(errno, 0).str); return 0 ; } err = bgp_open_listener(&su, port, SOCK_STREAM, 0) ; return (err == 0) ? 1 : 0 ; } ; /* Null address, try for IPv4 and (if supported) IPv6 */ count = 0 ; sockunion_init_new(&su, AF_INET) ; err = bgp_open_listener(&su, port, SOCK_STREAM, 0) ; if (err == 0) ++count ; #ifdef HAVE_IPV6 sockunion_init_new(&su, AF_INET6) ; err = bgp_open_listener(&su, port, SOCK_STREAM, 0) ; if (err == 0) ++count ; #endif return count ; } ; /*------------------------------------------------------------------------------ * Open 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_open_listener(sockunion su, unsigned short port, int sock_type, int sock_protocol) { bgp_listener listener ; int ret, err ; int slen ; int sock_fd ; /* Construct socket and set the common options. */ sock_fd = sockunion_socket(su, sock_type, sock_protocol) ; if (sock_fd < 0) { err = errno ; zlog_err ("%s: could not open socket for family %d: %s", __func__, sockunion_family(su), errtoa(err, 0).str) ; return errno = err ; } err = bgp_socket_set_common_options(sock_fd, su, NULL) ; /* Want only IPV6 on ipv6 socket (not mapped addresses) * * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the * attempt to bind to :: after binding to 0.0.0.0. * * Also, for all the apparent utility of IPv4-mapped addresses, the semantics * are simpler if IPv6 sockets speak IPv6 and IPv4 sockets speak IPv4. */ #ifdef HAVE_IPV6 if ((err == 0) && (sockunion_family(su) == AF_INET6)) if (setsockopt_ipv6_v6only(sock_fd) < 0) err = errno ; #endif /* Bind to port and address (if any) */ if (err == 0) { if (bgpd_privs.change(ZPRIVS_RAISE)) { err = errno ; zlog_err("%s: could not raise privs: %s", __func__, errtoa(errno, 0).str) ; } ; slen = sockunion_set_port(su, port) ; ret = bind(sock_fd, &su->sa, slen) ; if (ret < 0) { err = errno ; zlog_err ("%s: bind: %s", __func__, errtoa(err, 0).str); } ; if (bgpd_privs.change(ZPRIVS_LOWER)) { if (err == 0) err = errno ; zlog_err("%s: could not lower privs: %s", __func__, errtoa(errno, 0).str) ; } ; } ; /* Last lap... listen() */ if (err == 0) { ret = listen (sock_fd, 43); if (ret < 0) { err = errno ; zlog_err ("%s: listen: %s", __func__, errtoa(err, 0).str) ; } } ; if (err != 0) { close(sock_fd) ; return err ; } ; /* 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(bgp_nexus->selection, &listener->qf, sock_fd, listener) ; qps_enable_mode(&listener->qf, qps_read_mnum, bgp_accept_action) ; sockunion_copy(&listener->su, su) ; listener->next = *bgp_listeners(sockunion_family(su)) ; *bgp_listeners(sockunion_family(su)) = listener ; return 0 ; } ; /*------------------------------------------------------------------------------ * 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)) ; #ifdef HAVE_IPV6 bgp_reset_listeners(bgp_listeners(AF_INET6)) ; #endif } ; 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 ; qps_remove_file(&listener->qf) ; close(qps_file_fd(&listener->qf)) ; XFREE(MTYPE_BGP_LISTENER, listener) ; } ; } ; /*------------------------------------------------------------------------------ * Prepare to accept() connection * * If the session has a password, then this is where the listener(s) for the * appropriate address family are told about the password. * * This is done shortly before the session is first enabled for accept(). * * The effect is (probably) that the peer's attempts to connect with MD5 signed * packets will simply have been ignored up to this point. From this point * forward they will be accepted, but closed until accept is enabled. * * NB: requires the session mutex LOCKED. */ extern void bgp_prepare_to_accept(bgp_connection connection) { int err ; /* This logs an error if setting the password fails. */ err = bgp_md5_set_listeners(connection->session->su_peer, connection->session->password) ; return ; } ; /*------------------------------------------------------------------------------ * No longer prepared to accept() connection * * If the session has a password, then this is where it is withdrawn from the * listener(s) for the appropriate address family. * * NB: requires the session mutex LOCKED. * * TODO -- should do this when peer is stopped or about to be deleted !! */ extern void bgp_not_prepared_to_accept(bgp_connection connection) { int err ; if (connection->session->password != NULL) { err = bgp_md5_set_listeners(connection->session->su_peer, NULL) ; } ; return ; } ; /*------------------------------------------------------------------------------ * 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 ; union sockunion su_remote ; union sockunion su_local ; bool exists ; int sock_fd ; int err ; /* Accept client connection. * * We arrange for an IPv4 listener *and* an IPv6 one (assuming have IPv6), * and we arrange for AF_INET6 listener to be IPV6_V6ONLY. This means that * should NOT get an IPv4 mapped address. However, should we get such an * address, the su_remote will be set to the actual IPv4 address. * * This means: the address family of su_remote is the address family of the * underlying connection, NOT NECESSARILY the socket -- should that matter. */ sock_fd = sockunion_accept(qps_file_fd(qf), &su_remote) ; if (sock_fd < 0) { err = errno ; if (sock_fd == -1) zlog_err("[Error] BGP socket accept failed (%s)", errtoa(err, 0).str) ; return ; /* have no connection to report this to */ } ; if (BGP_DEBUG(events, EVENTS)) zlog_debug("[Event] BGP connection from host %s", sutoa(&su_remote).str) ; /* See if we are ready to accept connections from the connecting party */ connection = bgp_peer_index_seek_accept(&su_remote, &exists) ; if (connection == NULL) { 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", sutoa(&su_remote).str) ; close(sock_fd) ; return ; /* quietly reject connection */ /* TODO: RFC recommends sending a NOTIFICATION when refusing accept() */ } ; /* 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 Routing Engine will not make any changes * except under the mutex, and will not destroy the session. */ BGP_CONNECTION_SESSION_LOCK(connection) ; /*<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-*/ /* Set the common socket options. * Does not set password -- that is inherited from the listener. * * At this point, su_remote is the value returned by sockunion_accept(), so * if we have an AF_INET6 socket with an IPv4 mapped address, then su_remote * is an AF_INET. */ err = bgp_socket_set_common_options(sock_fd, &su_remote, connection) ; /* Get the local and remote addresses -- noting that IPv6 mapped IPv4 * addresses are rendered as IPv4 addresses. */ if (err == 0) err = bgp_get_names(sock_fd, &su_local, &su_remote) ; /* If all is well, set up the accept connection, and set it ready * to go. Set session not to accept further inbound connections. */ if (err == 0) bgp_connection_open(connection, sock_fd) ; else close(sock_fd) ; BGP_CONNECTION_SESSION_UNLOCK(connection) ; /*->->->->->->->->->->->->->->->*/ /* Now kick the FSM in an appropriate fashion */ bgp_fsm_connect_completed(connection, err, &su_local, &su_remote) ; } ; /*============================================================================== * Open BGP Connection -- connect() to the other end */ static int bgp_md5_set_socket(int sock_fd, sockunion su, const char *password) ; static int bgp_bind_ifname(bgp_connection connection, int sock_fd) ; static int bgp_bind_ifaddress(bgp_connection connection, int sock_fd) ; /*------------------------------------------------------------------------------ * 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) { int sock_fd ; int err ; sockunion su = connection->session->su_peer ; err = 0 ; /* Make socket for the connect connection. */ sock_fd = sockunion_socket(su, SOCK_STREAM, 0) ; if (sock_fd < 0) err = errno ; if (BGP_DEBUG(events, EVENTS)) plog_debug(connection->log, "%s [Event] Connect start to %s socket %d%s", connection->host, connection->host, sock_fd, (sock_fd < 0) ? " -- failed" : "" ) ; /* Set the common options. */ if (err == 0) err = bgp_socket_set_common_options(sock_fd, su, connection) ; /* Set the TCP MD5 "password", if required. */ if (err== 0) if (connection->session->password != NULL) err = bgp_md5_set_socket(sock_fd, su, connection->session->password) ; /* Bind socket. */ if (err == 0) err = bgp_bind_ifname(connection, sock_fd) ; /* Update source bind. */ if (err == 0) err = bgp_bind_ifaddress(connection, sock_fd) ; /* Connect to the remote peer. */ if (err == 0) err = sockunion_connect(sock_fd, su, connection->session->port, connection->session->ifindex) ; /* does not report EINPROGRESS as an error. */ /* If not OK now, close the sock_fd and signal the error */ if (err != 0) { if (sock_fd >= 0) close(sock_fd) ; bgp_fsm_connect_completed(connection, err, NULL, NULL) ; 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 sock_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, sock_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. * * If becomes both readable and writable at the same time, then the first to * arrive here will disable the file for both read and write, which will * discard the other pending event -- so will not attempt to do this more than * once. * * 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 ; union sockunion su_remote ; union sockunion su_local ; connection = file_info ; /* See if connection successful or not. */ /* If successful, set the connection->su_local and ->su_remote */ len = sizeof(err) ; err = 0 ; ret = getsockopt(qps_file_fd(qf), SOL_SOCKET, SO_ERROR, &err, &len) ; if (ret != 0) { err = errno ; if (err == 0) /* cannot be and cannot continue */ zabort("Invalid return from getsockopt()") ; } else { if (len != sizeof(err)) zabort("getsockopt returned unexpected length") ; } ; if (err == 0) err = bgp_get_names(qps_file_fd(qf), &su_local, &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, err, &su_local, &su_remote) ; } ; /*============================================================================== * Set the TTL for the given connection (if any), if there is an sock_fd. */ extern void bgp_set_new_ttl(bgp_connection connection, int ttl, bool gtsm) { int sock_fd ; if (connection == NULL) return ; sock_fd = qps_file_fd(connection->qf) ; if (sock_fd < 0) return ; bgp_set_ttl(sock_fd, connection, ttl, gtsm) ; } ; /*------------------------------------------------------------------------------ * BGP set minttl (GTSM) and/or ttl. * * A ttl of <= 0 is treated as "turn off" -- effectively MAXTTL, forcing gtsm * *off*. * * If GTSM is not supported, then sets ttl. * * Returns: 0 : OK (so far so good) * != 0 : error number (from errno or otherwise) */ static int bgp_set_ttl(int sock_fd, bgp_connection connection, int ttl, bool gtsm) { int ret ; if (gtsm && (ttl > 0)) { ret = setsockopt_minttl(sock_fd, ttl) ; if (ret >= 0) { ttl = MAXTTL ; connection->gtsm = true ; } else if (errno != EOPNOTSUPP) return errno ; } else if (connection->gtsm) { ret = setsockopt_minttl(sock_fd, 0) ; /* turn off */ if (ret < 0) /* must have turned it on, so should not fail */ return errno ; connection->gtsm = false ; } ; ret = setsockopt_ttl(sock_fd, ttl) ; return (ret >= 0) ? 0 : errno ; } ; /*============================================================================== * Get local and remote address and port for connection. * * Returns: 0 => OK * != 0 : error number (from errno or otherwise) */ static int bgp_get_names(int sock_fd, union sockunion* su_local, union sockunion* su_remote) { int ret, err ; err = 0 ; ret = sockunion_getsockname(sock_fd, su_local) ; if (ret < 0) err = errno ; ret = sockunion_getpeername(sock_fd, su_remote) ; if ((ret < 0) && (err == 0)) err = errno ; return err ; } ; /*============================================================================== * Specific binding of outbound connections to interfaces... * */ /*------------------------------------------------------------------------------ * 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_ifname(bgp_connection connection, int sock_fd) { #ifdef SO_BINDTODEVICE int ret, err ; struct ifreq ifreq; if (connection->session->ifname == NULL) return 0; strncpy ((char *)&ifreq.ifr_name, connection->session->ifname, sizeof (ifreq.ifr_name)) ; err = 0 ; if (bgpd_privs.change (ZPRIVS_RAISE)) { err = errno ; zlog_err ("bgp_bind: could not raise privs: %s", errtoa(errno, 0).str); } ; ret = setsockopt (sock_fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof (ifreq)) ; if (ret < 0) err = errno ; if (bgpd_privs.change (ZPRIVS_LOWER) ) { if (err == 0) err = errno ; zlog_err ("bgp_bind: could not lower privs: %s", errtoa(errno, 0).str); } ; if (err != 0) { zlog (connection->log, LOG_INFO, "bind to interface %s failed (%s)", connection->session->ifname, errtoa(err, 0).str) ; return err ; } #endif /* SO_BINDTODEVICE */ return 0; } ; /*------------------------------------------------------------------------------ * Update source selection -- if connection specifies an IP address. * * If required, tries to bind the given socket to the given address. * * Returns: 0 : OK (so far so good) * != 0 : error number (from errno or otherwise) */ static int bgp_bind_ifaddress(bgp_connection connection, int sock_fd) { if (connection->session->ifaddress != NULL) { union sockunion su[1] ; int ret ; sockunion_new_sockaddr(su, &connection->session->ifaddress->sa) ; ret = sockunion_bind (sock_fd, su, 0, false) ; if (ret < 0) return errno ; } ; return 0 ; } ; /*============================================================================== * BGP Socket Option handling */ /*------------------------------------------------------------------------------ * Common socket options: * * * non-blocking -- at all times * * reuseaddr * * reuseport * * set security ttl (GTSM) and/or ttl -- if connection given. * * set TOS or equivalent if required * * These options are set on all sockets: listen/connect/accept * (except either form of ttl, which is not set on listen). * * Note that the family of the given sockunion is the *protocol*, not the * *socket* family. * * Returns: 0 => OK * != 0 == errno -- not that we really expect any errors here */ static int bgp_socket_set_common_options(int sock_fd, sockunion su, bgp_connection connection) { int err ; /* Make socket non-blocking and close-on-exec */ if (set_nonblocking(sock_fd) < 0) return errno ; /* WARNING logged */ if (set_close_on_exec(sock_fd) < 0) return errno ; /* WARNING logged */ /* Reuse addr and port */ if (setsockopt_reuseaddr(sock_fd) < 0) return errno ; /* WARNING logged */ if (setsockopt_reuseport(sock_fd) < 0) return errno ; /* WARNING logged */ /* Adjust ttl if required */ if (connection != NULL) { err = bgp_set_ttl(sock_fd, connection, connection->session->ttl, connection->session->gtsm) ; if (err != 0) return errno = err ; } ; /* Worry about TOS etc, as required. */ err = 0 ; /* so far, so good */ #ifdef IPTOS_PREC_INTERNETCONTROL if (bgpd_privs.change (ZPRIVS_RAISE)) zlog_err ("%s: could not raise privs", __func__); switch(sockunion_family(su)) { case AF_INET: if (setsockopt_ipv4_tos (sock_fd, IPTOS_PREC_INTERNETCONTROL) < 0) err = errno ; break ; # ifdef HAVE_IPV6 case AF_INET6: if (setsockopt_ipv6_tclass (sock_fd, IPTOS_PREC_INTERNETCONTROL) < 0) err = errno ; break ; # endif default: err = 0 ; break ; } if (bgpd_privs.change (ZPRIVS_LOWER)) zlog_err ("%s: could not lower privs", __func__); #endif return errno = err ; } ; /*------------------------------------------------------------------------------ * 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 EOPNOTSUPP 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_md5_set_socket(int sock_fd, sockunion su, const char *password) { int err, ret ; assert(sock_fd >= 0) ; err = 0 ; if (bgpd_privs.change(ZPRIVS_RAISE)) { err = errno ; zlog_err("%s: could not raise privs: %s", __func__, errtoa(errno, 0).str); } ; ret = setsockopt_tcp_signature(sock_fd, su, password) ; if (ret != 0) err = errno ; if (bgpd_privs.change(ZPRIVS_LOWER)) { if (err == 0) err = errno ; zlog_err("%s: could not lower privs: %s", __func__, errtoa(errno, 0).str); } ; return err ; } ; /*------------------------------------------------------------------------------ * Set (or clear) MD5 password for given peer in the listener(s) for the peer's * address family. * * This allows system to accept MD5 "signed" incoming connections from the * given address. * * NULL password clears the password for the given peer. * * Returns: 0 => OK * otherwise: errno -- the first error encountered. * * NB: peer address must be AF_INET or (if supported) AF_INET6 * * NB: does nothing and returns "OK" if there are no listeners in the * address family -- wanting to set MD5 makes no difference to this ! */ static int bgp_md5_set_listeners(union sockunion* su, const char* password) { bgp_listener listener ; int err ; #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 listener = *bgp_listeners(su->sa.sa_family) ; while (listener != NULL) { err = bgp_md5_set_socket(qps_file_fd(&listener->qf), su, password) ; if (err != 0) return err ; listener = listener->next ; } ; return 0 ; } ;