diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c index 4153da5a64..6a5c2c4b38 100644 --- a/bgpd/bgp_network.c +++ b/bgpd/bgp_network.c @@ -64,7 +64,7 @@ struct bgp_listener { * If the password is NULL or zero-length, the option will be disabled. */ static int bgp_md5_set_socket(int socket, union sockunion *su, - const char *password) + uint16_t prefixlen, const char *password) { int ret = -1; int en = ENOSYS; @@ -81,27 +81,49 @@ static int bgp_md5_set_socket(int socket, union sockunion *su, su2.sin.sin_port = 0; else su2.sin6.sin6_port = 0; - ret = sockopt_tcp_signature(socket, &su2, password); + + /* For addresses, use the non-extended signature functionality */ + if ((su2.sa.sa_family == AF_INET && prefixlen == IPV4_MAX_PREFIXLEN) + || (su2.sa.sa_family == AF_INET6 + && prefixlen == IPV6_MAX_PREFIXLEN)) + ret = sockopt_tcp_signature(socket, &su2, password); + else + ret = sockopt_tcp_signature_ext(socket, &su2, prefixlen, + password); en = errno; #endif /* HAVE_TCP_MD5SIG */ - if (ret < 0) - flog_warn(EC_BGP_NO_TCP_MD5, - "can't set TCP_MD5SIG option on socket %d: %s", - socket, safe_strerror(en)); + if (ret < 0) { + char sabuf[SU_ADDRSTRLEN]; + sockunion2str(su, sabuf, sizeof(sabuf)); + + switch (ret) { + case -2: + flog_warn( + EC_BGP_NO_TCP_MD5, + "Unable to set TCP MD5 option on socket for peer %s (sock=%d): This platform does not support MD5 auth for prefixes", + sabuf, socket); + break; + default: + flog_warn( + EC_BGP_NO_TCP_MD5, + "Unable to set TCP MD5 option on socket for peer %s (sock=%d): %s", + sabuf, socket, safe_strerror(en)); + } + } return ret; } /* Helper for bgp_connect */ static int bgp_md5_set_connect(int socket, union sockunion *su, - const char *password) + uint16_t prefixlen, const char *password) { int ret = -1; #if HAVE_DECL_TCP_MD5SIG frr_elevate_privs(&bgpd_privs) { - ret = bgp_md5_set_socket(socket, su, password); + ret = bgp_md5_set_socket(socket, su, prefixlen, password); } #endif /* HAVE_TCP_MD5SIG */ @@ -114,21 +136,57 @@ static int bgp_md5_set_password(struct peer *peer, const char *password) int ret = 0; struct bgp_listener *listener; - frr_elevate_privs(&bgpd_privs) { - /* Set or unset the password on the listen socket(s). Outbound + /* + * Set or unset the password on the listen socket(s). Outbound * connections are taken care of in bgp_connect() below. */ + frr_elevate_privs(&bgpd_privs) + { for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) if (listener->su.sa.sa_family == peer->su.sa.sa_family) { + uint16_t prefixlen = + peer->su.sa.sa_family == AF_INET + ? IPV4_MAX_PREFIXLEN + : IPV6_MAX_PREFIXLEN; + ret = bgp_md5_set_socket(listener->fd, - &peer->su, password); + &peer->su, prefixlen, + password); break; } } return ret; } +int bgp_md5_set_prefix(struct prefix *p, const char *password) +{ + int ret = 0; + union sockunion su; + struct listnode *node; + struct bgp_listener *listener; + + /* Set or unset the password on the listen socket(s). */ + frr_elevate_privs(&bgpd_privs) + { + for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) + if (listener->su.sa.sa_family == p->family) { + prefix2sockunion(p, &su); + ret = bgp_md5_set_socket(listener->fd, &su, + p->prefixlen, + password); + break; + } + } + + return ret; +} + +int bgp_md5_unset_prefix(struct prefix *p) +{ + return bgp_md5_set_prefix(p, NULL); +} + int bgp_md5_set(struct peer *peer) { /* Set the password from listen socket. */ @@ -577,8 +635,14 @@ int bgp_connect(struct peer *peer) } #endif - if (peer->password) - bgp_md5_set_connect(peer->fd, &peer->su, peer->password); + if (peer->password) { + uint16_t prefixlen = peer->su.sa.sa_family == AF_INET + ? IPV4_MAX_PREFIXLEN + : IPV6_MAX_PREFIXLEN; + + bgp_md5_set_connect(peer->fd, &peer->su, prefixlen, + peer->password); + } /* Update source bind. */ if (bgp_update_source(peer) < 0) { diff --git a/bgpd/bgp_network.h b/bgpd/bgp_network.h index f18484e000..59b18f9376 100644 --- a/bgpd/bgp_network.h +++ b/bgpd/bgp_network.h @@ -30,6 +30,8 @@ extern void bgp_close(void); extern int bgp_connect(struct peer *); extern int bgp_getsockname(struct peer *); +extern int bgp_md5_set_prefix(struct prefix *p, const char *password); +extern int bgp_md5_unset_prefix(struct prefix *p); extern int bgp_md5_set(struct peer *); extern int bgp_md5_unset(struct peer *); extern int bgp_set_socket_ttl(struct peer *, int fd); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 225f119908..b2925cd512 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -2645,6 +2645,11 @@ int peer_group_listen_range_add(struct peer_group *group, struct prefix *range) prefix = prefix_new(); prefix_copy(prefix, range); listnode_add(group->listen_range[afi], prefix); + + /* Update passwords for new ranges */ + if (group->conf->password) + bgp_md5_set_prefix(prefix, group->conf->password); + return 0; } @@ -2689,6 +2694,10 @@ int peer_group_listen_range_del(struct peer_group *group, struct prefix *range) /* Get rid of the listen range */ listnode_delete(group->listen_range[afi], prefix); + /* Remove passwords for deleted ranges */ + if (group->conf->password) + bgp_md5_unset_prefix(prefix); + return 0; } @@ -5519,6 +5528,15 @@ int peer_password_set(struct peer *peer, const char *password) ret = BGP_ERR_TCPSIG_FAILED; } + /* Set flag and configuration on all peer-group listen ranges */ + struct listnode *ln; + struct prefix *lr; + + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP], ln, lr)) + bgp_md5_set_prefix(lr, password); + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP6], ln, lr)) + bgp_md5_set_prefix(lr, password); + return ret; } @@ -5583,6 +5601,15 @@ int peer_password_unset(struct peer *peer) bgp_md5_unset(member); } + /* Set flag and configuration on all peer-group listen ranges */ + struct listnode *ln; + struct prefix *lr; + + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP], ln, lr)) + bgp_md5_unset_prefix(lr); + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP6], ln, lr)) + bgp_md5_unset_prefix(lr); + return 0; } diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index be331ffb99..35e42d95cb 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -826,6 +826,30 @@ Defining Peers peers ASN is the same as mine as specified under the :clicmd:`router bgp ASN` command the connection will be denied. +.. index:: [no] bgp listen range peer-group WORD +.. clicmd:: [no] bgp listen range peer-group WORD + + Accept connections from any peers in the specified prefix. Configuration + from the specified peer-group is used to configure these peers. + +.. note:: + + When using BGP listen ranges, if the associated peer group has TCP MD5 + authentication configured, your kernel must support this on prefixes. On + Linux, this support was added in kernel version 4.14. If your kernel does + not support this feature you will get a warning in the log file, and the + listen range will only accept connections from peers without MD5 configured. + + Additionally, we have observed that when using this option at scale (several + hundred peers) the kernel may hit its option memory limit. In this situation + you will see error messages like: + + ``bgpd: sockopt_tcp_signature: setsockopt(23): Cannot allocate memory`` + + In this case you need to increase the value of the sysctl + ``net.core.optmem_max`` to allow the kernel to allocate the necessary option + memory. + .. _bgp-configuring-peers: Configuring Peers diff --git a/lib/sockopt.c b/lib/sockopt.c index ea04f2a43e..89f3d5b594 100644 --- a/lib/sockopt.c +++ b/lib/sockopt.c @@ -587,10 +587,30 @@ int sockopt_tcp_rtt(int sock) #endif } -int sockopt_tcp_signature(int sock, union sockunion *su, const char *password) +int sockopt_tcp_signature_ext(int sock, union sockunion *su, uint16_t prefixlen, + const char *password) { +#ifndef HAVE_DECL_TCP_MD5SIG + /* + * We have been asked to enable MD5 auth for an address, but our + * platform doesn't support that + */ + return -2; +#endif + +#ifndef TCP_MD5SIG_EXT + /* + * We have been asked to enable MD5 auth for a prefix, but our platform + * doesn't support that + */ + if (prefixlen > 0) + return -2; +#endif + #if HAVE_DECL_TCP_MD5SIG int ret; + + int optname = TCP_MD5SIG; #ifndef GNU_LINUX /* * XXX Need to do PF_KEY operation here to add/remove an SA entry, @@ -643,12 +663,29 @@ int sockopt_tcp_signature(int sock, union sockunion *su, const char *password) memset(&md5sig, 0, sizeof(md5sig)); memcpy(&md5sig.tcpm_addr, su2, sizeof(*su2)); + md5sig.tcpm_keylen = keylen; if (keylen) memcpy(md5sig.tcpm_key, password, keylen); sockunion_free(susock); + + /* + * Handle support for MD5 signatures on prefixes, if available and + * requested. Technically the #ifdef check below is not needed because + * if prefixlen > 0 and we don't have support for this feature we would + * have already returned by now, but leaving it there to be explicit. + */ +#ifdef TCP_MD5SIG_EXT + if (prefixlen > 0) { + md5sig.tcpm_prefixlen = prefixlen; + md5sig.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX; + optname = TCP_MD5SIG_EXT; + } +#endif /* TCP_MD5SIG_EXT */ + #endif /* GNU_LINUX */ - if ((ret = setsockopt(sock, IPPROTO_TCP, TCP_MD5SIG, &md5sig, + + if ((ret = setsockopt(sock, IPPROTO_TCP, optname, &md5sig, sizeof md5sig)) < 0) { /* ENOENT is harmless. It is returned when we clear a password @@ -663,7 +700,10 @@ int sockopt_tcp_signature(int sock, union sockunion *su, const char *password) sock, safe_strerror(errno)); } return ret; -#else /* HAVE_TCP_MD5SIG */ - return -2; -#endif /* !HAVE_TCP_MD5SIG */ +#endif /* HAVE_TCP_MD5SIG */ +} + +int sockopt_tcp_signature(int sock, union sockunion *su, const char *password) +{ + return sockopt_tcp_signature_ext(sock, su, 0, password); } diff --git a/lib/sockopt.h b/lib/sockopt.h index 8fa5987cff..732fec92aa 100644 --- a/lib/sockopt.h +++ b/lib/sockopt.h @@ -100,9 +100,43 @@ extern void sockopt_iphdrincl_swab_htosys(struct ip *iph); extern void sockopt_iphdrincl_swab_systoh(struct ip *iph); extern int sockopt_tcp_rtt(int); + +/* + * TCP MD5 signature option. This option allows TCP MD5 to be enabled on + * addresses. + * + * sock + * Socket to enable option on. + * + * su + * Sockunion specifying address to enable option on. + * + * password + * MD5 auth password + */ extern int sockopt_tcp_signature(int sock, union sockunion *su, const char *password); +/* + * Extended TCP MD5 signature option. This option allows TCP MD5 to be enabled + * on prefixes. + * + * sock + * Socket to enable option on. + * + * su + * Sockunion specifying address (or prefix) to enable option on. + * + * prefixlen + * 0 - su is an address; fall back to non-extended mode + * Else - su is a prefix; prefixlen is the mask length + * + * password + * MD5 auth password + */ +extern int sockopt_tcp_signature_ext(int sock, union sockunion *su, + uint16_t prefixlen, const char *password); + #ifdef __cplusplus } #endif