diff --git a/common/test/test_alog.cpp b/common/test/test_alog.cpp index bf0f6fbb..c0691a2f 100644 --- a/common/test/test_alog.cpp +++ b/common/test/test_alog.cpp @@ -19,7 +19,8 @@ limitations under the License. #include "../alog-functionptr.h" #include "../alog-audit.h" #include -#include "photon/thread/thread.h" +#include +#include #include #include #include @@ -556,6 +557,21 @@ TEST(ALog, LOG_LIMIT) { EXPECT_LE(x, suppose + 3); } +TEST(ALOG, IPAddr) { + log_output = &log_output_test; + DEFER(log_output = log_output_stdout); + + photon::net::IPAddr ip("192.168.12.34"); + EXPECT_STREQ(ip.to_string().c_str(), "192.168.12.34"); + LOG_INFO(ip); + EXPECT_STREQ("192.168.12.34", log_output_test.log_start()); + + ip = photon::net::IPAddr("abcd:1111:222:33:4:5:6:7"); + EXPECT_STREQ(ip.to_string().c_str(), "abcd:1111:222:33:4:5:6:7"); + LOG_INFO(ip); + EXPECT_STREQ("abcd:1111:222:33:4:5:6:7", log_output_test.log_start()); +} + int main(int argc, char **argv) { photon::vcpu_init(); diff --git a/net/basic_socket.cpp b/net/basic_socket.cpp index d583454d..b789d225 100644 --- a/net/basic_socket.cpp +++ b/net/basic_socket.cpp @@ -262,11 +262,11 @@ bool ISocketStream::skip_read(size_t count) { } int do_get_name(int fd, Getter getter, EndPoint& addr) { - struct sockaddr_in addr_in; - socklen_t len = sizeof(addr_in); - int ret = getter(fd, (struct sockaddr*) &addr_in, &len); - if (ret < 0 || len != sizeof(addr_in)) return -1; - addr.from_sockaddr_in(addr_in); + sockaddr_storage storage(addr); + socklen_t len = storage.get_socklen(); + int ret = getter(fd, storage.get_sockaddr(), &len); + if (ret < 0 || len > storage.get_socklen()) return -1; + addr = storage.to_endpoint(); return 0; } diff --git a/net/datagram_socket.cpp b/net/datagram_socket.cpp index dee6504e..1b58c6cd 100644 --- a/net/datagram_socket.cpp +++ b/net/datagram_socket.cpp @@ -139,14 +139,14 @@ class UDP : public DatagramSocketBase { virtual int connect(const Addr* addr, size_t addr_len) override { auto ep = (EndPoint*)addr; assert(ep && addr_len == sizeof(*ep)); - auto in = ep->to_sockaddr_in(); - return do_connect((sockaddr*)&in, sizeof(in)); + sockaddr_storage s(*ep); + return do_connect(s.get_sockaddr(), s.get_socklen()); } virtual int bind(const Addr* addr, size_t addr_len) override { auto ep = (EndPoint*)addr; assert(ep && addr_len == sizeof(*ep)); - auto in = ep->to_sockaddr_in(); - return do_bind((sockaddr*)&in, sizeof(in)); + sockaddr_storage s(*ep); + return do_bind(s.get_sockaddr(), s.get_socklen()); } virtual ssize_t send(const struct iovec* iov, int iovcnt, const Addr* addr, size_t addr_len, int flags = 0) override { @@ -154,8 +154,8 @@ class UDP : public DatagramSocketBase { if (likely(!ep) || unlikely(addr_len != sizeof(*ep))) return do_send(iov, iovcnt, nullptr, 0, flags); assert(addr_len == sizeof(*ep)); - auto in = ep->to_sockaddr_in(); - return do_send(iov, iovcnt, (sockaddr*)&in, sizeof(in), flags); + sockaddr_storage s(*ep); + return do_send(iov, iovcnt, s.get_sockaddr(), s.get_socklen(), flags); } virtual ssize_t recv(const struct iovec* iov, int iovcnt, Addr* addr, size_t* addr_len, int flags) override { @@ -164,12 +164,12 @@ class UDP : public DatagramSocketBase { return do_recv(iov, iovcnt, nullptr, 0, flags); } - sockaddr_in in; - size_t alen = sizeof(in); - auto ret = do_recv(iov, iovcnt, (sockaddr*)&in, &alen, flags); + sockaddr_storage s(*ep); + size_t alen = s.get_socklen(); + auto ret = do_recv(iov, iovcnt, s.get_sockaddr(), &alen, flags); if (ret >= 0) { - ep->from(in); - *addr_len = sizeof(*ep); + *ep = s.to_endpoint(); + *addr_len = alen; } return ret; } diff --git a/net/http/client.cpp b/net/http/client.cpp index a66d6e57..64567fec 100644 --- a/net/http/client.cpp +++ b/net/http/client.cpp @@ -77,7 +77,7 @@ ISocketStream* PooledDialer::dial(std::string_view host, uint16_t port, bool sec return sock; } LOG_DEBUG("connect ssl : ` ep : ` host : ` failed", secure, ep, host); - if (ipaddr.addr == 0) LOG_DEBUG("No connectable resolve result"); + if (ipaddr.undefined()) LOG_DEBUG("No connectable resolve result"); // When failed, remove resolved result from dns cache so that following retries can try // different ips. resolver->discard_cache(strhost.c_str()); diff --git a/net/kernel_socket.cpp b/net/kernel_socket.cpp index 6ce6cf5d..59a046f7 100644 --- a/net/kernel_socket.cpp +++ b/net/kernel_socket.cpp @@ -32,6 +32,7 @@ limitations under the License. #include #include +#include #include #include #include @@ -60,20 +61,22 @@ limitations under the License. #endif LogBuffer& operator<<(LogBuffer& log, const in_addr& iaddr) { - return log << photon::net::IPAddr(ntohl(iaddr.s_addr)); + return log << photon::net::IPAddr(iaddr); +} +LogBuffer& operator<<(LogBuffer& log, const in6_addr& iaddr) { + return log << photon::net::IPAddr(iaddr); } - LogBuffer& operator<<(LogBuffer& log, const sockaddr_in& addr) { - return log << photon::net::EndPoint(addr); + photon::net::sockaddr_storage s(addr); + return log << s.to_endpoint(); +} +LogBuffer& operator<<(LogBuffer& log, const sockaddr_in6& addr) { + photon::net::sockaddr_storage s(addr); + return log << s.to_endpoint(); } - LogBuffer& operator<<(LogBuffer& log, const sockaddr& addr) { - if (addr.sa_family == AF_INET) { - log << (const sockaddr_in&)addr; - } else { - log.printf(""); - } - return log; + photon::net::sockaddr_storage s(addr); + return log << s.to_endpoint(); } namespace photon { @@ -93,7 +96,7 @@ class KernelSocketStream : public SocketStreamBase { } else { fd = ::socket(socket_family, SOCK_STREAM, 0); } - if (fd >= 0 && socket_family == AF_INET) { + if (fd >= 0 && (socket_family == AF_INET || socket_family == AF_INET6)) { int val = 1; ::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t) sizeof(val)); } @@ -214,18 +217,16 @@ class KernelSocketClient : public SocketClientBase { ISocketStream* connect(const char* path, size_t count) override { struct sockaddr_un addr_un; if (fill_uds_path(addr_un, path, count) != 0) return nullptr; - return do_connect((const sockaddr*) &addr_un, nullptr, sizeof(addr_un)); + return do_connect((const sockaddr*) &addr_un, sizeof(addr_un)); } ISocketStream* connect(EndPoint remote, EndPoint local = EndPoint()) override { - sockaddr_in addr_remote = remote.to_sockaddr_in(); - auto r = (sockaddr*) &addr_remote; - if (local.empty()) { - return do_connect(r, nullptr, sizeof(addr_remote)); + sockaddr_storage r(remote); + if (local.undefined()) { + return do_connect(r.get_sockaddr(), r.get_socklen()); } - sockaddr_in addr_local = local.to_sockaddr_in(); - auto l = (sockaddr*) &addr_local; - return do_connect(r, l, sizeof(addr_remote)); + sockaddr_storage l(local); + return do_connect(r.get_sockaddr(), r.get_socklen(), l.get_sockaddr(), l.get_socklen()); } protected: @@ -240,7 +241,8 @@ class KernelSocketClient : public SocketClientBase { return net::connect(fd, remote, addrlen, m_timeout); } - ISocketStream* do_connect(const sockaddr* remote, const sockaddr* local, socklen_t addrlen) { + ISocketStream* do_connect(const sockaddr* remote, socklen_t len_remote, + const sockaddr* local = nullptr, socklen_t len_local = 0) { auto stream = create_stream(); std::unique_ptr ptr(stream); if (!ptr || ptr->fd < 0) { @@ -251,11 +253,11 @@ class KernelSocketClient : public SocketClientBase { } ptr->timeout(m_timeout); if (local != nullptr) { - if (::bind(ptr->fd, local, addrlen) != 0) { + if (::bind(ptr->fd, local, len_local) != 0) { LOG_ERRNO_RETURN(0, nullptr, "fail to bind socket"); } } - auto ret = fd_connect(ptr->fd, remote, addrlen); + auto ret = fd_connect(ptr->fd, remote, len_remote); if (ret < 0) { LOG_ERRNO_RETURN(0, nullptr, "Failed to connect socket"); } @@ -300,7 +302,7 @@ class KernelSocketServer : public SocketServerBase { if (m_listen_fd < 0) { LOG_ERRNO_RETURN(0, -1, "fail to setup listen fd"); } - if (m_socket_family == AF_INET) { + if (m_socket_family == AF_INET || m_socket_family == AF_INET6) { if (setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) != 0) { LOG_ERRNO_RETURN(EINVAL, -1, "failed to setsockopt of TCP_NODELAY"); } @@ -336,8 +338,11 @@ class KernelSocketServer : public SocketServerBase { } int bind(uint16_t port, IPAddr addr) override { - auto addr_in = EndPoint(addr, port).to_sockaddr_in(); - return ::bind(m_listen_fd, (struct sockaddr*)&addr_in, sizeof(addr_in)); + if (m_socket_family == AF_INET6 && addr.undefined()) { + addr = IPAddr::V6Any(); + } + sockaddr_storage s(EndPoint(addr, port)); + return ::bind(m_listen_fd, s.get_sockaddr(), s.get_socklen()); } int bind(const char* path, size_t count) override { @@ -419,11 +424,13 @@ class KernelSocketServer : public SocketServerBase { int do_accept() { return fd_accept(m_listen_fd, nullptr, nullptr); } int do_accept(EndPoint& remote_endpoint) { - struct sockaddr_in addr_in; - socklen_t len = sizeof(addr_in); - int cfd = fd_accept(m_listen_fd, (struct sockaddr*) &addr_in, &len); - if (cfd < 0 || len != sizeof(addr_in)) return -1; - remote_endpoint.from_sockaddr_in(addr_in); + sockaddr_storage s(remote_endpoint); + socklen_t len = s.get_socklen(); + + int cfd = fd_accept(m_listen_fd, s.get_sockaddr(), &len); + if (cfd < 0 || len > s.get_socklen()) + return -1; + remote_endpoint = s.to_endpoint(); return cfd; } @@ -900,7 +907,8 @@ class ETKernelSocketStream : public KernelSocketStream, public NotifyContext { if (fd >= 0) etpoller.register_notifier(fd, this); } - ETKernelSocketStream() : KernelSocketStream(AF_INET, true) { + ETKernelSocketStream(int socket_family, bool nonblocking) : + KernelSocketStream(socket_family, nonblocking) { if (fd >= 0) etpoller.register_notifier(fd, this); } @@ -947,7 +955,7 @@ class ETKernelSocketClient : public KernelSocketClient { protected: KernelSocketStream* create_stream() override { - return new ETKernelSocketStream(); + return new ETKernelSocketStream(m_socket_family, m_nonblocking); } }; @@ -981,19 +989,32 @@ class ETKernelSocketServer : public KernelSocketServer, public NotifyContext { /* ET Socket - End */ LogBuffer& operator<<(LogBuffer& log, const IPAddr addr) { - return log.printf(addr.d, '.', addr.c, '.', addr.b, '.', addr.a); + if (addr.is_ipv4()) + return log.printf(addr.a, '.', addr.b, '.', addr.c, '.', addr.d); + else { + return log.printf(addr.to_string()); + } } LogBuffer& operator<<(LogBuffer& log, const EndPoint ep) { - return log << ep.addr << ':' << ep.port; + if (ep.is_ipv4()) + return log << ep.addr << ':' << ep.port; + else + return log << '[' << ep.addr << "]:" << ep.port; } extern "C" ISocketClient* new_tcp_socket_client() { return new KernelSocketClient(AF_INET, true); } +extern "C" ISocketClient* new_tcp_socket_client_ipv6() { + return new KernelSocketClient(AF_INET6, true); +} extern "C" ISocketServer* new_tcp_socket_server() { return NewObj(AF_INET, false, true)->init(); } +extern "C" ISocketServer* new_tcp_socket_server_ipv6() { + return NewObj(AF_INET6, false, true)->init(); +} extern "C" ISocketClient* new_uds_client() { return new KernelSocketClient(AF_UNIX, true); } diff --git a/net/socket.h b/net/socket.h index 55b576fd..f9e9ef23 100644 --- a/net/socket.h +++ b/net/socket.h @@ -21,81 +21,217 @@ limitations under the License. #include #include #include +#include #include #include #include +#ifdef __linux__ +#define _in_addr_field s6_addr32 +#else // macOS +#define _in_addr_field __u6_addr.__u6_addr32 +#endif + struct LogBuffer; LogBuffer& operator << (LogBuffer& log, const in_addr& iaddr); LogBuffer& operator << (LogBuffer& log, const sockaddr_in& addr); +LogBuffer& operator << (LogBuffer& log, const in6_addr& iaddr); +LogBuffer& operator << (LogBuffer& log, const sockaddr_in6& addr); LogBuffer& operator << (LogBuffer& log, const sockaddr& addr); + namespace photon { -namespace net -{ - union IPAddr - { - uint32_t addr = 0; - struct { uint8_t a, b, c, d; }; - explicit IPAddr(uint32_t nl) - { - from_nl(nl); - } - explicit IPAddr(const char* s) - { - struct in_addr addr; - if (inet_aton(s, &addr) == 0) - return; // invalid IPv4 address - from_nl(addr.s_addr); - } - IPAddr() = default; - IPAddr(const IPAddr& rhs) = default; - uint32_t to_nl() const - { - return htonl(addr); - } - void from_nl(uint32_t nl) - { - addr = ntohl(nl); +namespace net { + + struct __attribute__ ((packed)) IPAddr { + public: + union { + in6_addr addr = {}; + struct { uint16_t _1, _2, _3, _4, _5, _6; uint8_t a, b, c, d; }; + }; + // For compatibility, the default constructor is still 0.0.0.0 (IPv4) + IPAddr() { + map_v4(htonl(INADDR_ANY)); + } + // V6 constructor (Internet Address) + explicit IPAddr(in6_addr internet_addr) { + addr = internet_addr; + } + // V6 constructor (Network byte order) + IPAddr(uint32_t nl1, uint32_t nl2, uint32_t nl3, uint32_t nl4) { + addr._in_addr_field[0] = nl1; + addr._in_addr_field[1] = nl2; + addr._in_addr_field[2] = nl3; + addr._in_addr_field[3] = nl4; + } + // V4 constructor (Internet Address) + explicit IPAddr(in_addr internet_addr) { + map_v4(internet_addr); + } + // V4 constructor (Network byte order) + explicit IPAddr(uint32_t nl) { + map_v4(nl); + } + // String constructor + explicit IPAddr(const char* s) { + if (inet_pton(AF_INET6, s, &addr) > 0) { + return; + } + in_addr v4_addr; + if (inet_pton(AF_INET, s, &v4_addr) > 0) { + map_v4(v4_addr); + return; + } + // Invalid string, make it a default value + *this = IPAddr(); + } + // Check if it's actually an IPv4 address mapped in IPV6 + bool is_ipv4() const { + if (ntohl(addr._in_addr_field[2]) != 0x0000ffff) { + return false; + } + if (addr._in_addr_field[0] != 0 || addr._in_addr_field[1] != 0) { + return false; + } + return true; + } + // We regard the default IPv4 0.0.0.0 as undefined + bool undefined() const { + return *this == V4Any(); + } + // Should ONLY be used for IPv4 address + uint32_t to_nl() const { + return addr._in_addr_field[3]; + } + bool is_loopback() const { + return is_ipv4() ? (*this == V4Loopback()) : (*this == V6Loopback()); + } + bool is_broadcast() const { + // IPv6 does not support broadcast + return is_ipv4() && (*this == V4Broadcast()); + } + bool is_link_local() const { + if (is_ipv4()) { + return (to_nl() & htonl(0xffff0000)) == htonl(0xa9fe0000); + } else { + return (addr._in_addr_field[0] & htonl(0xffc00000)) == htonl(0xfe800000); + } + } + bool operator==(const IPAddr& rhs) const { + return memcmp(this, &rhs, sizeof(rhs)) == 0; + } + bool operator!=(const IPAddr& rhs) const { + return !(*this == rhs); + } + std::string to_string() const { + std::string str; + char text[INET6_ADDRSTRLEN]; + if (is_ipv4()) { + in_addr ip4; + ip4.s_addr = to_nl(); + inet_ntop(AF_INET, &ip4, text, INET_ADDRSTRLEN); + } else { + inet_ntop(AF_INET6, &addr, text, INET6_ADDRSTRLEN); + } + str.assign(text, strlen(text)); + return str; + } + public: + static IPAddr V6None() { + return IPAddr(htonl(0xffffffff), htonl(0xffffffff), htonl(0xffffffff), htonl(0xffffffff)); + } + static IPAddr V6Any() { return IPAddr(in6addr_any); } + static IPAddr V6Loopback() { return IPAddr(in6addr_loopback); } + static IPAddr V4Broadcast() { return IPAddr(htonl(INADDR_BROADCAST)); } + static IPAddr V4Any() { return IPAddr(htonl(INADDR_ANY)); } + static IPAddr V4Loopback() { return IPAddr(htonl(INADDR_LOOPBACK)); } + private: + void map_v4(in_addr addr_) { + map_v4(addr_.s_addr); + } + void map_v4(uint32_t nl) { + addr._in_addr_field[0] = 0x00000000; + addr._in_addr_field[1] = 0x00000000; + addr._in_addr_field[2] = htonl(0xffff); + addr._in_addr_field[3] = nl; } }; - struct EndPoint - { + static_assert(sizeof(IPAddr) == 16, "IPAddr size incorrect"); + + struct __attribute__ ((packed)) EndPoint { IPAddr addr; uint16_t port = 0; EndPoint() = default; - EndPoint(IPAddr ip, uint16_t port): addr(ip), port(port) {} - EndPoint(const struct sockaddr_in& addr_in) - { - from(addr_in); - } - sockaddr_in to_sockaddr_in() const - { - struct sockaddr_in addr_in; - addr_in.sin_family = AF_INET; - addr_in.sin_addr.s_addr = addr.to_nl(); - addr_in.sin_port = htons(port); - return addr_in; - } - void from_sockaddr_in(const struct sockaddr_in& addr_in) - { - addr.from_nl(addr_in.sin_addr.s_addr); - port = ntohs(addr_in.sin_port); - } - void from(const struct sockaddr_in& addr_in) - { - from_sockaddr_in(addr_in); - } + EndPoint(IPAddr ip, uint16_t port) : addr(ip), port(port) {} + bool is_ipv4() const { + return addr.is_ipv4(); + }; bool operator==(const EndPoint& rhs) const { - return rhs.addr.addr == addr.addr && rhs.port == port; + return rhs.addr == addr && rhs.port == port; } bool operator!=(const EndPoint& rhs) const { return !operator==(rhs); } - bool empty() const { - return addr.addr == 0 && port == 0; + bool undefined() const { + return addr.undefined() && port == 0; + } + }; + + static_assert(sizeof(EndPoint) == 18, "Endpoint size incorrect"); + + struct sockaddr_storage { + sockaddr_storage() = default; + explicit sockaddr_storage(const EndPoint& ep) { + if (ep.is_ipv4()) { + auto* in4 = (sockaddr_in*) &store; + in4->sin_family = AF_INET; + in4->sin_port = htons(ep.port); + in4->sin_addr.s_addr = ep.addr.to_nl(); + } else { + auto* in6 = (sockaddr_in6*) &store; + in6->sin6_family = AF_INET6; + in6->sin6_port = htons(ep.port); + in6->sin6_addr = ep.addr.addr; + } + } + explicit sockaddr_storage(const sockaddr_in& addr) { + *((sockaddr_in*) &store) = addr; + } + explicit sockaddr_storage(const sockaddr_in6& addr) { + *((sockaddr_in6*) &store) = addr; } + explicit sockaddr_storage(const sockaddr& addr) { + *((sockaddr*) &store) = addr; + } + EndPoint to_endpoint() const { + EndPoint ep; + if (store.ss_family == AF_INET6) { + auto s6 = (sockaddr_in6*) &store; + ep.addr = IPAddr(s6->sin6_addr); + ep.port = ntohs(s6->sin6_port); + } else if (store.ss_family == AF_INET) { + auto s4 = (sockaddr_in*) &store; + ep.addr = IPAddr(s4->sin_addr); + ep.port = ntohs(s4->sin_port); + } + return ep; + } + sockaddr* get_sockaddr() const { + return (sockaddr*) &store; + } + socklen_t get_socklen() const { + switch (store.ss_family) { + case AF_INET: + return sizeof(sockaddr_in); + case AF_INET6: + return sizeof(sockaddr_in6); + default: + return 0; + } + } + // store must be zero initialized + ::sockaddr_storage store = {}; }; // operators to help with logging IP addresses @@ -187,6 +323,8 @@ namespace net extern "C" ISocketClient* new_tcp_socket_client(); extern "C" ISocketServer* new_tcp_socket_server(); + extern "C" ISocketClient* new_tcp_socket_client_ipv6(); + extern "C" ISocketServer* new_tcp_socket_server_ipv6(); extern "C" ISocketClient* new_uds_client(); extern "C" ISocketServer* new_uds_server(bool autoremove = false); extern "C" ISocketClient* new_tcp_socket_pool(ISocketClient* client, uint64_t expiration = -1UL, @@ -208,11 +346,21 @@ namespace net } namespace std { + template<> struct hash { - hash hasher; size_t operator()(const photon::net::EndPoint& x) const { - return hasher((x.addr.to_nl() << 16) | x.port); + hash hasher; + return hasher(std::string_view((const char*) &x, sizeof(x))); } }; + +template<> +struct hash { + size_t operator()(const photon::net::IPAddr& x) const { + hash hasher; + return hasher(std::string_view((const char*) &x, sizeof(x))); + } +}; + } diff --git a/net/test/CMakeLists.txt b/net/test/CMakeLists.txt index d287383a..93862a4f 100644 --- a/net/test/CMakeLists.txt +++ b/net/test/CMakeLists.txt @@ -22,4 +22,7 @@ add_executable(test-server test-server.cpp) target_link_libraries(test-server PRIVATE photon_shared ${testing_libs}) add_executable(test-client test-client.cpp) -target_link_libraries(test-client PRIVATE photon_shared ${testing_libs}) \ No newline at end of file +target_link_libraries(test-client PRIVATE photon_shared ${testing_libs}) + +add_executable(test-ipv6 test-ipv6.cpp) +target_link_libraries(test-ipv6 PRIVATE photon_shared ${testing_libs}) \ No newline at end of file diff --git a/net/test/test-ipv6.cpp b/net/test/test-ipv6.cpp new file mode 100644 index 00000000..d169902a --- /dev/null +++ b/net/test/test-ipv6.cpp @@ -0,0 +1,142 @@ +#include +#include + +#include +#include +#include +#include +#include + +TEST(ipv6, addr) { + EXPECT_NO_THROW(photon::net::IPAddr a("::1")); + EXPECT_NO_THROW(photon::net::IPAddr a("1.2.3.4")); + EXPECT_NO_THROW(photon::net::IPAddr a("fdbd:dc01:ff:312:9641:f71:10c4:2378")); + EXPECT_NO_THROW(photon::net::IPAddr a("fdbd:dc01:ff:312:9641:f71::2378")); + EXPECT_NO_THROW(photon::net::IPAddr a("fdbd:dc01:ff:312:9641::2378")); + + auto c = photon::net::IPAddr("zfdbd:dq01:8:165::158"); + EXPECT_TRUE(c.undefined()); + c = photon::net::IPAddr("fdbd::ff:312:9641:f71::2378"); + EXPECT_TRUE(c.undefined()); + c = photon::net::IPAddr("::1z"); + EXPECT_TRUE(c.undefined()); + c = photon::net::IPAddr("::ffffffff"); + EXPECT_TRUE(c.undefined()); + c = photon::net::IPAddr("1.256.3.4"); + EXPECT_TRUE(c.undefined()); + c = photon::net::IPAddr("1.2.3.zzzz"); + EXPECT_TRUE(c.undefined()); + + EXPECT_TRUE(photon::net::IPAddr() == photon::net::IPAddr::V4Any()); + EXPECT_TRUE(photon::net::IPAddr("0.0.0.0") == photon::net::IPAddr::V4Any()); + EXPECT_TRUE(photon::net::IPAddr("127.0.0.1") == photon::net::IPAddr::V4Loopback()); + EXPECT_TRUE(photon::net::IPAddr("255.255.255.255") == photon::net::IPAddr::V4Broadcast()); + + EXPECT_TRUE(photon::net::IPAddr("::") == photon::net::IPAddr::V6Any()); + EXPECT_TRUE(photon::net::IPAddr("::1") == photon::net::IPAddr::V6Loopback()); + EXPECT_TRUE(photon::net::IPAddr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") == photon::net::IPAddr::V6None()); + + photon::net::IPAddr b("fe80::216:3eff:fe7c:39e0"); + EXPECT_TRUE(b.is_link_local()); +} + +TEST(ipv6, get_host_by_peer) { + auto peer = photon::net::gethostbypeer(photon::net::IPAddr("2001:4860:4860::8888")); + ASSERT_TRUE(!peer.undefined()); + ASSERT_TRUE(!peer.is_ipv4()); + LOG_INFO(peer); +} + +TEST(ipv6, dns_lookup) { + std::vector ret; + int num = photon::net::gethostbyname("github.com", ret, true); + ASSERT_GT(num, 0); + ASSERT_TRUE(!ret[0].is_ipv4()); + LOG_INFO(ret[0]); +} + +class DualStackTest : public ::testing::Test { +public: + void run() { + auto server = photon::net::new_tcp_socket_server_ipv6(); + ASSERT_NE(nullptr, server); + DEFER(delete server); + int ret = server->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); + ASSERT_EQ(0, ret); + + ret = server->bind(9527, photon::net::IPAddr::V6Any()); + ASSERT_EQ(0, ret); + ret = server->listen(); + ASSERT_EQ(0, ret); + + photon::thread_create11([&] { + auto client = get_client(); + ASSERT_NE(nullptr, client); + DEFER(delete client); + + photon::net::EndPoint ep(get_server_ip(), 9527); + auto stream = client->connect(ep); + ASSERT_NE(nullptr, stream); + DEFER(delete stream); + LOG_INFO("Server endpoint: ", ep); + + photon::net::EndPoint ep2; + int ret = stream->getsockname(ep2); + ASSERT_EQ(0, ret); + ASSERT_TRUE(!ep2.undefined()); + LOG_INFO("Client endpoint: ", ep2); + ASSERT_TRUE(ep2.port > 10000); + + photon::net::EndPoint ep3; + ret = stream->getpeername(ep3); + ASSERT_EQ(0, ret); + ASSERT_TRUE(!ep3.undefined()); + ASSERT_EQ(ep3, ep); + }); + + photon::net::EndPoint ep; + auto stream = server->accept(&ep); + ASSERT_NE(nullptr, stream); + LOG_INFO("Client endpoint: ", ep); + DEFER(delete stream); + } +protected: + virtual photon::net::ISocketClient* get_client() = 0; + virtual photon::net::IPAddr get_server_ip() = 0; +}; + +class V6ToV6Test : public DualStackTest { +protected: + photon::net::ISocketClient* get_client() override { + return photon::net::new_tcp_socket_client_ipv6(); + } + photon::net::IPAddr get_server_ip() override { + return photon::net::IPAddr::V6Loopback(); + } +}; + +class V4ToV6Test : public DualStackTest { +protected: + photon::net::ISocketClient* get_client() override { + return photon::net::new_tcp_socket_client(); + } + photon::net::IPAddr get_server_ip() override { + return photon::net::IPAddr::V4Loopback(); + } +}; + +TEST_F(V6ToV6Test, run) { + run(); +} + +TEST_F(V4ToV6Test, run) { + run(); +} + +int main(int argc, char** arg) { + ::testing::InitGoogleTest(&argc, arg); + if (photon::init() != 0) + LOG_ERROR_RETURN(0, -1, "error init"); + DEFER(photon::fini()); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/net/test/test.cpp b/net/test/test.cpp index a2869a51..a61d6f72 100644 --- a/net/test/test.cpp +++ b/net/test/test.cpp @@ -180,18 +180,20 @@ class LogOutputTest final : public ILogOutput { TEST(Socket, endpoint) { EndPoint ep; struct in_addr inaddr; - struct sockaddr_in saddrin, rsai; + struct sockaddr_in saddrin; inet_aton("12.34.56.78", &inaddr); saddrin.sin_family = AF_INET; saddrin.sin_port = htons(4321); saddrin.sin_addr = inaddr; - ep.from_sockaddr_in(saddrin); - rsai = ep.to_sockaddr_in(); + photon::net::sockaddr_storage s(saddrin); + ep = s.to_endpoint(); + EXPECT_TRUE(ep == EndPoint(IPAddr("12.34.56.78"), 4321)); - EXPECT_EQ(saddrin.sin_addr.s_addr, rsai.sin_addr.s_addr); - EXPECT_EQ(saddrin.sin_family, rsai.sin_family); - EXPECT_EQ(saddrin.sin_port, rsai.sin_port); + auto rsai = (sockaddr_in*) s.get_sockaddr(); + EXPECT_EQ(saddrin.sin_addr.s_addr, rsai->sin_addr.s_addr); + EXPECT_EQ(saddrin.sin_family, rsai->sin_family); + EXPECT_EQ(saddrin.sin_port, rsai->sin_port); log_output = &log_output_test; LOG_DEBUG(ep); diff --git a/net/utils.cpp b/net/utils.cpp index 3820e65a..240c6640 100644 --- a/net/utils.cpp +++ b/net/utils.cpp @@ -41,21 +41,24 @@ IPAddr gethostbypeer(IPAddr remote) { // but let os select the interface to connect, // then get its ip constexpr uint16_t UDP_IP_DETECTE_PORT = 8080; + int sock_family = remote.is_ipv4() ? AF_INET : AF_INET6; - int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); + int sockfd = ::socket(sock_family, SOCK_DGRAM, 0); if (sockfd < 0) LOG_ERRNO_RETURN(0, IPAddr(), "Cannot create udp socket"); DEFER(::close(sockfd)); - struct sockaddr_in addr_in = - EndPoint{remote, UDP_IP_DETECTE_PORT}.to_sockaddr_in(); - auto ret = - ::connect(sockfd, (sockaddr *)&addr_in, sizeof(struct sockaddr_in)); + + EndPoint ep_remote(remote, UDP_IP_DETECTE_PORT); + sockaddr_storage s_remote(ep_remote); + + auto ret = ::connect(sockfd, s_remote.get_sockaddr(), s_remote.get_socklen()); if (ret < 0) LOG_ERRNO_RETURN(0, IPAddr(), "Cannot connect remote"); - struct sockaddr_in addr_local; - socklen_t len = sizeof(struct sockaddr_in); - ::getsockname(sockfd, (sockaddr *)&addr_local, &len); - IPAddr result; - result.from_nl(addr_local.sin_addr.s_addr); - return result; + + EndPoint ep_local; + sockaddr_storage s_local(ep_local); + socklen_t len = s_local.get_socklen(); + ::getsockname(sockfd, s_local.get_sockaddr(), &len); + + return s_local.to_endpoint().addr; } IPAddr gethostbypeer(const char *domain) { @@ -93,6 +96,32 @@ int _gethostbyname(const char *name, Delegate append_op) { return idx; } +int _gethostbyname_ipv6(const char* name, Delegate append_op) { + int idx = 0; + addrinfo* result = nullptr; + addrinfo* cur = nullptr; + addrinfo hints = {}; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_INET6; + + int ret = getaddrinfo(name, nullptr, &hints, &result); + if (ret != 0) { + LOG_ERROR_RETURN(0, -1, "Fail to getaddrinfo: `", gai_strerror(ret)); + } + for (cur = result; cur != nullptr; cur = cur->ai_next) { + if (cur->ai_family == AF_INET6) { + auto sock_addr = (sockaddr_in6*) cur->ai_addr; + if (append_op(IPAddr(sock_addr->sin6_addr)) < 0) { + break; + } + idx++; + } + } + freeaddrinfo(result); + return idx; +} + inline __attribute__((always_inline)) void base64_translate_3to4(const char *in, char *out) { struct xlator { unsigned char _; diff --git a/net/utils.h b/net/utils.h index 452944dc..43ec9e8b 100644 --- a/net/utils.h +++ b/net/utils.h @@ -58,6 +58,7 @@ IPAddr gethostbypeer(const char* domain); // Callback returns -1 means break int _gethostbyname(const char* name, Callback append_op); +int _gethostbyname_ipv6(const char* name, Callback append_op); // inline implemention for compatible @@ -69,13 +70,16 @@ int _gethostbyname(const char* name, Callback append_op); * @param name Host name to resolve * @return first resolved address. */ - inline IPAddr gethostbyname(const char* name) { + inline IPAddr gethostbyname(const char* name, bool ipv6 = false) { IPAddr ret; auto cb = [&](IPAddr addr) { ret = addr; return -1; }; - _gethostbyname(name, cb); + if (ipv6) + _gethostbyname_ipv6(name, cb); + else + _gethostbyname(name, cb); return ret; } @@ -88,15 +92,15 @@ int _gethostbyname(const char* name, Callback append_op); * @param name Host name to resolve * @param buf IPAddr buffer pointer * @param bufsize size of `buf`, takes `sizeof(IPAddr)` as unit - * @return sum of resolved address number. result will be filled into `buf` + * @return sum of resolved address number. -1 means error. result will be filled into `buf` */ -inline int gethostbyname(const char* name, IPAddr* buf, int bufsize = 1) { +inline int gethostbyname(const char* name, IPAddr* buf, int bufsize = 1, bool ip_v6 = false) { int i = 0; auto cb = [&](IPAddr addr) { if (i < bufsize) buf[i++] = addr; return 0; }; - return _gethostbyname(name, cb); + return ip_v6 ? _gethostbyname_ipv6(name, cb) : _gethostbyname(name, cb); } /** @@ -107,15 +111,15 @@ inline int gethostbyname(const char* name, IPAddr* buf, int bufsize = 1) { * * @param name Host name to resolve * @param ret `std::vector` reference to get results - * @return sum of resolved address number. + * @return sum of resolved address number. -1 means error. */ -inline int gethostbyname(const char* name, std::vector& ret) { +inline int gethostbyname(const char* name, std::vector& ret, bool ip_v6 = false) { ret.clear(); auto cb = [&](IPAddr addr) { ret.push_back(addr); return 0; }; - return _gethostbyname(name, cb); + return ip_v6 ? _gethostbyname_ipv6(name, cb) : _gethostbyname(name, cb); } /** diff --git a/rpc/rpc.cpp b/rpc/rpc.cpp index 18ac63aa..2ded983a 100644 --- a/rpc/rpc.cpp +++ b/rpc/rpc.cpp @@ -427,9 +427,9 @@ namespace rpc { class StubPoolImpl : public StubPool { public: - explicit StubPoolImpl(uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout) { + explicit StubPoolImpl(uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout, bool ipv6) { tls_ctx = net::new_tls_context(nullptr, nullptr, nullptr); - tcpclient = net::new_tcp_socket_client(); + tcpclient = ipv6 ? net::new_tcp_socket_client_ipv6() : net::new_tcp_socket_client(); tcpclient->timeout(connect_timeout); m_pool = new ObjectCache(expiration); m_rpc_timeout = rpc_timeout; @@ -489,7 +489,7 @@ namespace rpc { public: explicit UDSStubPoolImpl(const char* path, uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout) - : StubPoolImpl(expiration, connect_timeout, rpc_timeout), + : StubPoolImpl(expiration, connect_timeout, rpc_timeout, false), m_path(path), m_client(net::new_uds_client()) { m_client->timeout(connect_timeout); } @@ -515,8 +515,8 @@ namespace rpc { net::ISocketClient * m_client; }; - StubPool* new_stub_pool(uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout) { - return new StubPoolImpl(expiration, connect_timeout, rpc_timeout); + StubPool* new_stub_pool(uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout, bool ipv6) { + return new StubPoolImpl(expiration, connect_timeout, rpc_timeout, ipv6); } StubPool* new_uds_stub_pool(const char* path, uint64_t expiration, diff --git a/rpc/rpc.h b/rpc/rpc.h index 0b2b19fa..b5e6b083 100644 --- a/rpc/rpc.h +++ b/rpc/rpc.h @@ -234,7 +234,8 @@ namespace rpc }; extern "C" Stub* new_rpc_stub(IStream* stream, bool ownership = false); - extern "C" StubPool* new_stub_pool(uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout); + extern "C" StubPool* new_stub_pool(uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout, + bool ipv6 = false); extern "C" StubPool* new_uds_stub_pool(const char* path, uint64_t expiration, uint64_t connect_timeout, uint64_t rpc_timeout);