diff --git a/include/seastar/net/api.hh b/include/seastar/net/api.hh index d7197342a26..c282d7fffe6 100644 --- a/include/seastar/net/api.hh +++ b/include/seastar/net/api.hh @@ -495,7 +495,8 @@ class network_stack { public: virtual ~network_stack() {} virtual server_socket listen(socket_address sa, listen_options opts) = 0; - // FIXME: local parameter assumes ipv4 for now, fix when adding other AF + // `local` may be of either family; unspecified means the wildcard address + // of the destination's family, with a port the system picks. future connect(socket_address sa, socket_address = {}, transport proto = transport::TCP); virtual ::seastar::socket socket() = 0; diff --git a/include/seastar/net/socket_defs.hh b/include/seastar/net/socket_defs.hh index 28928c34fd7..9e31a70049a 100644 --- a/include/seastar/net/socket_defs.hh +++ b/include/seastar/net/socket_defs.hh @@ -120,7 +120,8 @@ struct ipv4_addr { ipv4_addr(const std::string &addr, uint16_t port); // throws if not an ipv4 addr ipv4_addr(const net::inet_address&, uint16_t); - ipv4_addr(const socket_address &) noexcept; + // throws if the address is IPv6 and not IPv4-mapped + ipv4_addr(const socket_address &); ipv4_addr(const ::in_addr&, uint16_t = 0) noexcept; bool is_ip_unspecified() const noexcept { @@ -160,6 +161,10 @@ std::ostream& operator<<(std::ostream&, const ipv6_addr&); inline bool operator==(const ipv4_addr &lhs, const ipv4_addr& rhs) noexcept { return lhs.ip == rhs.ip && lhs.port == rhs.port; } + +inline bool operator==(const ipv6_addr &lhs, const ipv6_addr& rhs) noexcept { + return lhs.ip == rhs.ip && lhs.port == rhs.port; +} } namespace std { @@ -172,6 +177,10 @@ struct hash { size_t operator()(const seastar::ipv4_addr&) const; }; template<> +struct hash { + size_t operator()(const seastar::ipv6_addr&) const; +}; +template<> struct hash { size_t operator()(const seastar::unix_domain_addr&) const; }; diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc index 8aadc7e9726..84480e02730 100644 --- a/src/net/inet_address.cc +++ b/src/net/inet_address.cc @@ -368,6 +368,7 @@ ::in_port_t seastar::socket_address::port() const noexcept { bool seastar::socket_address::is_wildcard() const noexcept { switch (family()) { case AF_INET: { + // cannot throw: the address is AF_INET here ipv4_addr addr(*this); return addr.is_ip_unspecified() && addr.is_port_unspecified(); } @@ -432,3 +433,9 @@ size_t std::hash::operator()(const seastar::ipv4_addr& x) co boost::hash_combine(h, x.port); return h; } + +size_t std::hash::operator()(const seastar::ipv6_addr& x) const { + size_t h = boost::hash_range(x.ip.begin(), x.ip.end()); + boost::hash_combine(h, x.port); + return h; +} diff --git a/src/net/native-stack.cc b/src/net/native-stack.cc index fcbf89b22e2..bb4ebadde1c 100644 --- a/src/net/native-stack.cc +++ b/src/net/native-stack.cc @@ -210,6 +210,9 @@ net::datagram_channel native_network_stack::make_unbound_datagram_channel(sa_fam } net::datagram_channel native_network_stack::make_bound_datagram_channel(const socket_address& local) { + if (local.family() != AF_INET && !local.is_unspecified()) { + throw std::runtime_error("Unsupported address family"); + } return _inet.get_udp().make_channel(local); } diff --git a/src/net/net.cc b/src/net/net.cc index a25db26a479..cf8e5395760 100644 --- a/src/net/net.cc +++ b/src/net/net.cc @@ -66,7 +66,7 @@ ipv4_addr::ipv4_addr(const net::inet_address& a, uint16_t port) : ipv4_addr(::in_addr(a), port) {} -ipv4_addr::ipv4_addr(const socket_address &sa) noexcept +ipv4_addr::ipv4_addr(const socket_address &sa) : ipv4_addr(sa.addr(), sa.port()) {} diff --git a/tests/unit/ipv6_test.cc b/tests/unit/ipv6_test.cc index d582ae0181f..f23c791f802 100644 --- a/tests/unit/ipv6_test.cc +++ b/tests/unit/ipv6_test.cc @@ -161,6 +161,21 @@ SEASTAR_TEST_CASE(dual_stack_listen_test) { } } +// ipv6_addr compares and hashes like ipv4_addr. +SEASTAR_TEST_CASE(ipv6_addr_value_type_test) { + const ipv6_addr a("2001:db8::1", 9092); + const ipv6_addr same("2001:db8::1", 9092); + const ipv6_addr other_port("2001:db8::1", 9093); + const ipv6_addr other_addr("2001:db8::2", 9092); + + BOOST_REQUIRE(a == same); + BOOST_REQUIRE(!(a == other_port)); + BOOST_REQUIRE(!(a == other_addr)); + BOOST_REQUIRE_EQUAL(std::hash()(a), std::hash()(same)); + BOOST_REQUIRE_NE(std::hash()(a), std::hash()(other_port)); + return make_ready_future(); +} + SEASTAR_TEST_CASE(ipv6_equal_test) { const uint16_t port{8080}; const uint16_t port2{8088}; diff --git a/tests/unit/network_interface_test.cc b/tests/unit/network_interface_test.cc index 2ab63387d0a..143d6bc782b 100644 --- a/tests/unit/network_interface_test.cc +++ b/tests/unit/network_interface_test.cc @@ -194,6 +194,19 @@ SEASTAR_TEST_CASE(test_ipv6_addr_string_forms) { return make_ready_future(); } +// The socket_address -> ipv4_addr conversion is reachable with an address of +// either family (it is implicit), so a non-mapped IPv6 one has to report an +// error rather than terminate the process from a noexcept function. +SEASTAR_TEST_CASE(test_ipv4_addr_from_socket_address) { + BOOST_REQUIRE_EQUAL(ipv4_addr(socket_address(ipv4_addr("10.0.0.1", 9092))).port, 9092); + // IPv4-mapped is an IPv4 address in an IPv6 shape, so it converts + BOOST_REQUIRE_EQUAL(ipv4_addr(socket_address(ipv6_addr("::ffff:10.0.0.1", 9092))), + ipv4_addr("10.0.0.1", 9092)); + BOOST_CHECK_THROW(ipv4_addr(socket_address(ipv6_addr("2001:db8::1", 9092))), std::invalid_argument); + BOOST_CHECK_THROW(ipv4_addr(socket_address(ipv6_addr("::1", 0))), std::invalid_argument); + return make_ready_future(); +} + SEASTAR_TEST_CASE(test_inet_address_format) { const std::string tests[] = { // IPv4 addresses