Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F116019280
D50099.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
15 KB
Referenced Files
None
Subscribers
None
D50099.diff
View Options
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd April 24, 2025
+.Dd May 1, 2025
.Dt IFCONFIG 8
.Os
.Sh NAME
@@ -451,7 +451,14 @@
Specify that the address configured is an anycast address,
as described in RFC 4291 section 2.6.
Anycast addresses will not be used as source address of any outgoing
-IPv6 packets unless an application explicitly binds to the address.
+IPv6 packets unless an application explicitly binds to the address,
+and by default may not be used as the source of outgoing network
+connections.
+See the
+.Dq ANYCAST ADDRESSES
+section of
+.Xr inet6 4
+for more details.
.It Cm arp
Enable the use of the Address Resolution Protocol
.Pq Xr arp 4
diff --git a/share/man/man4/inet6.4 b/share/man/man4/inet6.4
--- a/share/man/man4/inet6.4
+++ b/share/man/man4/inet6.4
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd February 22, 2023
+.Dd May 1, 2025
.Dt INET6 4
.Os
.Sh NAME
@@ -436,6 +436,48 @@
from IPv4 mapped address to
.Dv AF_INET6
sockets.
+.Sh ANYCAST ADDRESSES
+IPv6 supports the concept of an anycast address, defined in RFC 4291
+section 2.6.
+An anycast address is indistinguishable from a unicast address at the
+protocol level,
+but the same anycast address may be configured on several interfaces
+across multiple hosts,
+with a routing protocol (such as BGP or OSPF) used to route traffic from
+the source system to the nearest anycast node.
+.Pp
+An anycast address may be configured by setting the
+.Dv IN6_IFF_ANYCAST
+flag on an address, or using the
+.Dq anycast
+option to
+.Xr ifconfig 8 .
+.Pp
+By default, applications may bind a UDP or raw IPv6 socket to an anycast
+address and receive traffic,
+but are not permitted to send traffic from an anycast address or to
+either accept or initiate a TCP connection using a local anycast
+address.
+This is to avoid applications establishing stateful network connections
+over an anycast address,
+which may be unreliable if the underlying routing changes and the
+packets are redirected to a different anycast node.
+This is particularly an issue with TCP connections,
+but may also apply to UDP connections which keep state.
+.Pp
+To change this behaviour, set the sysctl variable
+.Va net.inet6.ip6.ip6_rfc4291_anycast
+to 1.
+Before doing this,
+ensure any necessary configuration has been done to make the application
+anycast-aware.
+.Pp
+Regardless of the setting of
+.Va ip6_rfc4291_anycast ,
+an anycast address will never be automatically chosen as the outgoing
+address of a connection;
+the application must set the source address explicitly,
+for example by binding the socket.
.Sh SEE ALSO
.Xr ioctl 2 ,
.Xr socket 2 ,
@@ -446,6 +488,13 @@
.Xr tcp 4 ,
.Xr udp 4
.Rs
+.%A R. Hinden
+.%A S. Deering
+.%R RFC 4291
+.%D February 2006
+.%T "IP Version 6 Addressing Architecture"
+.Re
+.Rs
.%A A. Conta
.%A S. Deering
.%A M. Gupta
diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c
--- a/sys/netinet/tcp_input.c
+++ b/sys/netinet/tcp_input.c
@@ -567,8 +567,6 @@
tcp6_input_with_port(struct mbuf **mp, int *offp, int proto, uint16_t port)
{
struct mbuf *m;
- struct in6_ifaddr *ia6;
- struct ip6_hdr *ip6;
m = *mp;
if (m->m_len < *offp + sizeof(struct tcphdr)) {
@@ -580,19 +578,6 @@
}
}
- /*
- * draft-itojun-ipv6-tcp-to-anycast
- * better place to put this in?
- */
- ip6 = mtod(m, struct ip6_hdr *);
- ia6 = in6ifa_ifwithaddr(&ip6->ip6_dst, 0 /* XXX */, false);
- if (ia6 && (ia6->ia6_flags & IN6_IFF_ANYCAST)) {
- icmp6_error(m, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR,
- (caddr_t)&ip6->ip6_dst - (caddr_t)ip6);
- *mp = NULL;
- return (IPPROTO_DONE);
- }
-
*mp = m;
return (tcp_input_with_port(mp, offp, proto, port));
}
@@ -630,6 +615,7 @@
struct m_tag *fwd_tag = NULL;
#ifdef INET6
struct ip6_hdr *ip6 = NULL;
+ struct in6_ifaddr *ia6;
int isipv6;
#else
const void *ip6 = NULL;
@@ -1234,40 +1220,40 @@
INP_RLOCK_ASSERT(inp);
#ifdef INET6
/*
- * If deprecated address is forbidden,
- * we do not accept SYN to deprecated interface
- * address to prevent any new inbound connection from
- * getting established.
- * When we do not accept SYN, we send a TCP RST,
- * with deprecated source address (instead of dropping
- * it). We compromise it as it is much better for peer
- * to send a RST, and RST will be the final packet
- * for the exchange.
- *
- * If we do not forbid deprecated addresses, we accept
- * the SYN packet. RFC2462 does not suggest dropping
- * SYN in this case.
- * If we decipher RFC2462 5.5.4, it says like this:
- * 1. use of deprecated addr with existing
- * communication is okay - "SHOULD continue to be
- * used"
- * 2. use of it with new communication:
- * (2a) "SHOULD NOT be used if alternate address
- * with sufficient scope is available"
- * (2b) nothing mentioned otherwise.
- * Here we fall into (2b) case as we have no choice in
- * our source address selection - we must obey the peer.
- *
- * The wording in RFC2462 is confusing, and there are
- * multiple description text for deprecated address
- * handling - worse, they are not exactly the same.
- * I believe 5.5.4 is the best one, so we follow 5.5.4.
+ * Check for deprecated or anycast addresses.
*/
- if (isipv6 && !V_ip6_use_deprecated) {
- struct in6_ifaddr *ia6;
-
- ia6 = in6ifa_ifwithaddr(&ip6->ip6_dst, 0 /* XXX */, false);
- if (ia6 != NULL &&
+ if (isipv6 && (!V_ip6_use_deprecated || !V_ip6_rfc4291_anycast) &&
+ (ia6 = in6ifa_ifwithaddr(&ip6->ip6_dst, 0, false))) {
+ /*
+ * If deprecated address is forbidden, we do not accept
+ * SYN to deprecated interface address to prevent any
+ * new inbound connection from getting established.
+ *
+ * When we do not accept SYN, we send a TCP RST, with
+ * deprecated source address (instead of dropping it).
+ * We compromise it as it is much better for peer to
+ * send a RST, and RST will be the final packet for the
+ * exchange.
+ *
+ * If we do not forbid deprecated addresses, we accept
+ * the SYN packet. RFC2462 does not suggest dropping
+ * SYN in this case.
+ * If we decipher RFC2462 5.5.4, it says like this:
+ * 1. use of deprecated addr with existing communication
+ * is okay - "SHOULD continue to be used"
+ * 2. use of it with new communication:
+ * (2a) "SHOULD NOT be used if alternate address
+ * with sufficient scope is available"
+ * (2b) nothing mentioned otherwise.
+ * Here we fall into (2b) case as we have no choice in
+ * our source address selection - we must obey the peer.
+ *
+ * The wording in RFC2462 is confusing, and there are
+ * multiple description text for deprecated address
+ * handling - worse, they are not exactly the same.
+ * I believe 5.5.4 is the best one, so we follow 5.5.4.
+ */
+ if (!V_ip6_use_deprecated &&
(ia6->ia6_flags & IN6_IFF_DEPRECATED)) {
if ((s = tcp_log_addrs(&inc, th, NULL, NULL)))
log(LOG_DEBUG, "%s; %s: Listen socket: "
@@ -1277,6 +1263,22 @@
rstreason = BANDLIM_RST_OPENPORT;
goto dropwithreset;
}
+
+ /*
+ * If rfc4291_anycast is not enabled, do not accept TCP
+ * connections to an anycast address. Sending a RST is
+ * fine in this case since RFC4291 permits it.
+ */
+ if (!V_ip6_rfc4291_anycast &&
+ (ia6->ia6_flags & IN6_IFF_ANYCAST)) {
+ if ((s = tcp_log_addrs(&inc, th, NULL, NULL)))
+ log(LOG_DEBUG, "%s; %s: Listen socket: "
+ "Connection attempt to anycast "
+ "IPv6 address rejected\n",
+ s, __func__);
+ rstreason = BANDLIM_RST_OPENPORT;
+ goto dropwithreset;
+ }
}
#endif /* INET6 */
/*
diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c
--- a/sys/netinet6/in6_pcb.c
+++ b/sys/netinet6/in6_pcb.c
@@ -197,33 +197,41 @@
if ((sooptions & (SO_REUSEADDR | SO_REUSEPORT_LB)) != 0)
reuseport_lb = SO_REUSEADDR | SO_REUSEPORT_LB;
} else if (!IN6_IS_ADDR_UNSPECIFIED(laddr)) {
- struct sockaddr_in6 sin6;
struct epoch_tracker et;
- struct ifaddr *ifa;
-
- memset(&sin6, 0, sizeof(sin6));
- sin6.sin6_family = AF_INET6;
- sin6.sin6_len = sizeof(sin6);
- sin6.sin6_addr = *laddr;
+ struct in6_ifaddr *ifa;
NET_EPOCH_ENTER(et);
- if ((ifa = ifa_ifwithaddr((const struct sockaddr *)&sin6)) ==
- NULL && (inp->inp_flags & INP_BINDANY) == 0) {
- NET_EPOCH_EXIT(et);
- return (EADDRNOTAVAIL);
- }
- /*
- * We used to prohibit binding to an anycast address here,
- * based on RFC3513, but that restriction was removed in
- * RFC4291.
- */
- if (ifa != NULL &&
- ((struct in6_ifaddr *)ifa)->ia6_flags &
- (IN6_IFF_NOTREADY | IN6_IFF_DETACHED)) {
- NET_EPOCH_EXIT(et);
- return (EADDRNOTAVAIL);
+ ifa = in6ifa_ifwithaddr(laddr, 0, false);
+ if (ifa) {
+ /*
+ * If rfc4291_anycast is disabled, do not allow binding
+ * a stream socket to an anycast address. For listen
+ * sockets, doing so is pointless because all incoming
+ * connections will be rejected anyway, and for outgoing
+ * connections, we don't want to permit TCP over anycast
+ * unless it's been explicitly enabled.
+ */
+ if ((inp->inp_socket->so_type == SOCK_STREAM) &&
+ (ifa->ia6_flags & IN6_IFF_ANYCAST) &&
+ !V_ip6_rfc4291_anycast) {
+ NET_EPOCH_EXIT(et);
+ return (EADDRNOTAVAIL);
+ }
+
+ /* Reject addresses which are not valid. */
+ if (ifa->ia6_flags &
+ (IN6_IFF_NOTREADY | IN6_IFF_DETACHED)) {
+ NET_EPOCH_EXIT(et);
+ return (EADDRNOTAVAIL);
+ }
+ } else {
+ if ((inp->inp_flags & INP_BINDANY) == 0) {
+ NET_EPOCH_EXIT(et);
+ return (EADDRNOTAVAIL);
+ }
}
+
NET_EPOCH_EXIT(et);
}
diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c
--- a/sys/netinet6/in6_proto.c
+++ b/sys/netinet6/in6_proto.c
@@ -172,6 +172,7 @@
VNET_DEFINE(int, ip6stealth) = 0;
#endif
VNET_DEFINE(bool, ip6_log_cannot_forward) = 1;
+VNET_DEFINE(bool, ip6_rfc4291_anycast) = 0;
/*
* BSDI4 defines these variables in in_proto.c...
@@ -345,3 +346,6 @@
log_cannot_forward, CTLFLAG_VNET | CTLFLAG_RW,
&VNET_NAME(ip6_log_cannot_forward), 1,
"Log packets that cannot be forwarded");
+SYSCTL_BOOL(_net_inet6_ip6, OID_AUTO, ip6_rfc4291_anycast,
+ CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_rfc4291_anycast), 0,
+ "Allow sending packets from an anycast address");
diff --git a/sys/netinet6/in6_src.c b/sys/netinet6/in6_src.c
--- a/sys/netinet6/in6_src.c
+++ b/sys/netinet6/in6_src.c
@@ -250,9 +250,21 @@
*/
if ((inp->inp_flags & INP_BINDANY) == 0) {
ia = in6ifa_ifwithaddr(&tmp, 0 /* XXX */, false);
- if (ia == NULL || (ia->ia6_flags & (IN6_IFF_ANYCAST |
- IN6_IFF_NOTREADY)))
+
+ if (ia == NULL || (ia->ia6_flags & IN6_IFF_NOTREADY))
+ return (EADDRNOTAVAIL);
+
+ /*
+ * Do not allow an anycast source address unless
+ * explicitly enabled by the administrator. This is to
+ * avoid people accidentally configuring stateful
+ * protocols over anycast without understanding the
+ * implications of that.
+ */
+ if ((ia->ia6_flags & IN6_IFF_ANYCAST) &&
+ !V_ip6_rfc4291_anycast)
return (EADDRNOTAVAIL);
+
bcopy(&ia->ia_addr.sin6_addr, srcp, sizeof(*srcp));
} else
bcopy(&tmp, srcp, sizeof(*srcp));
@@ -270,6 +282,18 @@
(error = prison_local_ip6(cred, &inp->in6p_laddr,
((inp->inp_flags & IN6P_IPV6_V6ONLY) != 0))) != 0)
return (error);
+
+ /*
+ * In the UDP case, we allow binding to an anycast address in
+ * order to receive packets sent to that address, but we don't
+ * want to allow sending packets from that address unless
+ * rfc4291_anycast is enabled.
+ */
+ ia = in6ifa_ifwithaddr(&inp->in6p_laddr, 0, false);
+ if ((ia != NULL) && (ia->ia6_flags & IN6_IFF_ANYCAST) &&
+ !V_ip6_rfc4291_anycast)
+ return (EADDRNOTAVAIL);
+
bcopy(&inp->in6p_laddr, srcp, sizeof(*srcp));
return (0);
}
diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h
--- a/sys/netinet6/ip6_var.h
+++ b/sys/netinet6/ip6_var.h
@@ -361,6 +361,9 @@
VNET_DECLARE(bool, ip6_log_cannot_forward);
#define V_ip6_log_cannot_forward VNET(ip6_log_cannot_forward)
+VNET_DECLARE(bool, ip6_rfc4291_anycast);
+#define V_ip6_rfc4291_anycast VNET(ip6_rfc4291_anycast)
+
extern struct pr_usrreqs rip6_usrreqs;
struct sockopt;
diff --git a/sys/netinet6/raw_ip6.c b/sys/netinet6/raw_ip6.c
--- a/sys/netinet6/raw_ip6.c
+++ b/sys/netinet6/raw_ip6.c
@@ -766,8 +766,7 @@
}
if (ifa != NULL &&
((struct in6_ifaddr *)ifa)->ia6_flags &
- (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY|
- IN6_IFF_DETACHED|IN6_IFF_DEPRECATED)) {
+ (IN6_IFF_NOTREADY|IN6_IFF_DETACHED|IN6_IFF_DEPRECATED)) {
NET_EPOCH_EXIT(et);
return (EADDRNOTAVAIL);
}
diff --git a/tests/sys/net/Makefile b/tests/sys/net/Makefile
--- a/tests/sys/net/Makefile
+++ b/tests/sys/net/Makefile
@@ -14,6 +14,7 @@
ATF_TESTS_SH+= if_tun_test
ATF_TESTS_SH+= if_vlan
ATF_TESTS_SH+= if_wg
+ATF_TESTS_SH+= anycast
TESTS_SUBDIRS+= bpf
TESTS_SUBDIRS+= if_ovpn
diff --git a/tests/sys/net/anycast.sh b/tests/sys/net/anycast.sh
new file mode 100755
--- /dev/null
+++ b/tests/sys/net/anycast.sh
@@ -0,0 +1,116 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2025 Lexi Winter
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "tcp_listen" "cleanup"
+tcp_listen_head()
+{
+ atf_set descr "listening on a TCP anycast address"
+ atf_set require.user root
+}
+
+tcp_listen_body()
+{
+ vnet_init
+
+ vnet_mkjail ajail
+
+ jexec ajail ifconfig lo0 inet6 2001:db8::1/128 anycast
+
+ # With ip6_rfc4291_anycast=0, binding to an anycast address should be
+ # disallowed.
+ jexec ajail sysctl net.inet6.ip6.ip6_rfc4291_anycast=0
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec ajail nc -l 2001:db8::1 8080
+
+ # With ip6_rfc4291_anycast=1, we should be able to bind and receive
+ # traffic.
+ jexec ajail sysctl net.inet6.ip6.ip6_rfc4291_anycast=1
+ jexec ajail nc -l 2001:db8::1 8080 & sleep 1
+ atf_check -s exit:0 -e ignore jexec ajail nc -z 2001:db8::1 8080
+}
+
+tcp_listen_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "tcp_connect" "cleanup"
+tcp_connect_head()
+{
+ atf_set descr "connecting from a TCP anycast address"
+ atf_set require.user root
+}
+
+tcp_connect_body()
+{
+ vnet_init
+
+ vnet_mkjail ajail
+
+ jexec ajail ifconfig lo0 inet6 2001:db8::1/128 anycast
+
+ # Start a listener we can connect to
+ jexec ajail nc -l ::1 8080 & sleep 1
+
+ # With ip6_rfc4291_anycast=0, connecting from an anycast address should
+ # be disallowed.
+ jexec ajail sysctl net.inet6.ip6.ip6_rfc4291_anycast=0
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec ajail nc -z -s 2001:db8::1 ::1 8080
+
+ # With ip6_rfc4291_anycast=1, we should be able to connect.
+ jexec ajail sysctl net.inet6.ip6.ip6_rfc4291_anycast=1
+ atf_check -s exit:0 -e ignore jexec ajail nc -z -s 2001:db8::1 ::1 8080
+}
+
+tcp_connect_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "ping" "cleanup"
+ping_head()
+{
+ atf_set descr "ping (raw socket) using an anycast address"
+}
+
+ping_body()
+{
+ vnet_init
+
+ vnet_mkjail ajail
+
+ jexec ajail ifconfig lo0 inet6 2001:db8::1/128 anycast
+
+ # With rfc4291_anycast disabled, we should be able to ping the anycast
+ # address, but not use it as a source address.
+ jexec ajail sysctl net.inet6.ip6.ip6_rfc4291_anycast=0
+
+ atf_check -s exit:0 -o ignore jexec ajail ping -c1 2001:db8::1
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec ajail ping -c1 -S 2001:db8::1 ::1
+
+ # With rfc4291_anycast enabled, we should be able to use the anycast
+ # address as a source address.
+ jexec ajail sysctl net.inet6.ip6.ip6_rfc4291_anycast=1
+
+ atf_check -s exit:0 -o ignore jexec ajail ping -c1 2001:db8::1
+ atf_check -s exit:0 -o ignore -e ignore \
+ jexec ajail ping -c1 -S 2001:db8::1 ::1
+}
+
+ping_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "tcp_listen"
+ atf_add_test_case "tcp_connect"
+ atf_add_test_case "ping"
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, May 2, 4:55 PM (13 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17908362
Default Alt Text
D50099.diff (15 KB)
Attached To
Mode
D50099: netinet6: make RFC4291 anycast conformance a sysctl
Attached
Detach File
Event Timeline
Log In to Comment