mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Add SocketAddress class.
PiperOrigin-RevId: 820261095
This commit is contained in:
committed by
Copybara-Service
parent
bffe543e49
commit
57dea973e0
@@ -300,6 +300,28 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "socket_address",
|
||||
srcs = [
|
||||
"socket_address.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"socket_address.h",
|
||||
],
|
||||
compatible_with = ["//buildenv/target:non_prod"],
|
||||
defines = [
|
||||
"_WIN32_WINNT=_WIN32_WINNT_WIN10",
|
||||
"WINVER=_WIN32_WINNT_WIN10",
|
||||
],
|
||||
tags = ["windows"],
|
||||
visibility = [
|
||||
"//:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//internal/platform:logging",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "test_utils",
|
||||
srcs = [
|
||||
@@ -427,3 +449,17 @@ cc_test(
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "socket_address_test",
|
||||
size = "small",
|
||||
timeout = "short",
|
||||
srcs = [
|
||||
"socket_address_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":socket_address",
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "internal/platform/implementation/windows/socket_address.h"
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "internal/platform/logging.h"
|
||||
|
||||
namespace nearby::windows {
|
||||
|
||||
SocketAddress::SocketAddress(const sockaddr_in& address, bool dual_stack)
|
||||
: dual_stack_(dual_stack) {
|
||||
std::memcpy(&address_, &address, sizeof(sockaddr_in));
|
||||
address_.ss_family = AF_INET;
|
||||
if (dual_stack_) {
|
||||
ToMappedIPv6();
|
||||
}
|
||||
}
|
||||
|
||||
SocketAddress::SocketAddress(const sockaddr_in6& address) : dual_stack_(true) {
|
||||
std::memcpy(&address_, &address, sizeof(sockaddr_in6));
|
||||
address_.ss_family = AF_INET6;
|
||||
}
|
||||
|
||||
SocketAddress::SocketAddress(const sockaddr_storage& address)
|
||||
: dual_stack_(true) {
|
||||
std::memcpy(&address_, &address, sizeof(sockaddr_storage));
|
||||
ToMappedIPv6();
|
||||
}
|
||||
|
||||
void SocketAddress::ToMappedIPv6() {
|
||||
if (address_.ss_family == AF_INET6) {
|
||||
return;
|
||||
}
|
||||
if (address_.ss_family != AF_INET) {
|
||||
LOG(ERROR) << "Unknown socket family: " << address_.ss_family;
|
||||
return;
|
||||
}
|
||||
sockaddr_in* v4_address = reinterpret_cast<sockaddr_in*>(&address_);
|
||||
in_addr orig_address;
|
||||
std::memcpy(&orig_address, &v4_address->sin_addr, sizeof(in_addr));
|
||||
int orig_port = v4_address->sin_port;
|
||||
address_.ss_family = AF_INET6;
|
||||
sockaddr_in6* v6_address = reinterpret_cast<sockaddr_in6*>(&address_);
|
||||
v6_address->sin6_port = orig_port;
|
||||
v6_address->sin6_flowinfo = 0;
|
||||
v6_address->sin6_scope_id = 0;
|
||||
v6_address->sin6_addr.u.Word[0] = 0;
|
||||
v6_address->sin6_addr.u.Word[1] = 0;
|
||||
v6_address->sin6_addr.u.Word[2] = 0;
|
||||
v6_address->sin6_addr.u.Word[3] = 0;
|
||||
v6_address->sin6_addr.u.Word[4] = 0;
|
||||
v6_address->sin6_addr.u.Word[5] = 0xffff;
|
||||
v6_address->sin6_addr.u.Word[6] = orig_address.S_un.S_un_w.s_w1;
|
||||
v6_address->sin6_addr.u.Word[7] = orig_address.S_un.S_un_w.s_w2;
|
||||
}
|
||||
|
||||
bool SocketAddress::FromString(SocketAddress& address,
|
||||
const std::string& address_string, int port) {
|
||||
if (address_string.empty()) {
|
||||
if (address.dual_stack_) {
|
||||
address.address_.ss_family = AF_INET6;
|
||||
sockaddr_in6* v6_address =
|
||||
reinterpret_cast<sockaddr_in6*>(&address.address_);
|
||||
v6_address->sin6_port = 0;
|
||||
v6_address->sin6_flowinfo = 0;
|
||||
v6_address->sin6_scope_id = 0;
|
||||
v6_address->sin6_addr = in6addr_any;
|
||||
address.set_port(port);
|
||||
return true;
|
||||
}
|
||||
address.address_.ss_family = AF_INET;
|
||||
sockaddr_in* v4_address = reinterpret_cast<sockaddr_in*>(&address.address_);
|
||||
v4_address->sin_port = 0;
|
||||
v4_address->sin_addr.s_addr = INADDR_ANY;
|
||||
address.set_port(port);
|
||||
return true;
|
||||
}
|
||||
// Try v4 address first.
|
||||
address.address_.ss_family = AF_INET;
|
||||
int sock_address_size = sizeof(sockaddr_storage);
|
||||
if (WSAStringToAddressA(const_cast<char*>(address_string.data()), AF_INET,
|
||||
/*lpProtocolInfo=*/nullptr,
|
||||
reinterpret_cast<sockaddr*>(&address.address_),
|
||||
&sock_address_size) == 0) {
|
||||
if (address.dual_stack_) {
|
||||
address.ToMappedIPv6();
|
||||
}
|
||||
address.set_port(port);
|
||||
return true;
|
||||
}
|
||||
// Try v6 address if v4 address is not supported.
|
||||
if (!address.dual_stack_) {
|
||||
return false;
|
||||
}
|
||||
address.address_.ss_family = AF_INET6;
|
||||
sock_address_size = sizeof(sockaddr_storage);
|
||||
if (WSAStringToAddressA(const_cast<char*>(address_string.data()), AF_INET6,
|
||||
/*lpProtocolInfo=*/nullptr,
|
||||
reinterpret_cast<sockaddr*>(&address.address_),
|
||||
&sock_address_size) == 0) {
|
||||
address.set_port(port);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int SocketAddress::port() const {
|
||||
DCHECK(address_.ss_family == AF_INET || address_.ss_family == AF_INET6);
|
||||
if (address_.ss_family == AF_INET) {
|
||||
const sockaddr_in* v4_address =
|
||||
reinterpret_cast<const sockaddr_in*>(&address_);
|
||||
return ntohs(v4_address->sin_port);
|
||||
}
|
||||
if (address_.ss_family == AF_INET6) {
|
||||
const sockaddr_in6* v6_address =
|
||||
reinterpret_cast<const sockaddr_in6*>(&address_);
|
||||
return ntohs(v6_address->sin6_port);
|
||||
}
|
||||
LOG(ERROR) << "Unknown socket family: " << address_.ss_family;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SocketAddress::set_port(int port) {
|
||||
DCHECK(address_.ss_family == AF_INET || address_.ss_family == AF_INET6);
|
||||
if (port < 0 || port > 65535) {
|
||||
LOG(ERROR) << "Invalid port: " << port;
|
||||
return false;
|
||||
}
|
||||
if (address_.ss_family == AF_INET) {
|
||||
sockaddr_in* v4_address = reinterpret_cast<sockaddr_in*>(&address_);
|
||||
v4_address->sin_port = htons(port);
|
||||
return true;
|
||||
}
|
||||
if (address_.ss_family == AF_INET6) {
|
||||
sockaddr_in6* v6_address = reinterpret_cast<sockaddr_in6*>(&address_);
|
||||
v6_address->sin6_port = htons(port);
|
||||
return true;
|
||||
}
|
||||
LOG(ERROR) << "Unknown socket family: " << address_.ss_family;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string SocketAddress::ToString() const {
|
||||
std::string address_string;
|
||||
DWORD size = INET6_ADDRSTRLEN; // Max IP address length.
|
||||
address_string.resize(size);
|
||||
if (WSAAddressToStringA(
|
||||
const_cast<sockaddr*>(reinterpret_cast<const sockaddr*>(&address_)),
|
||||
sizeof(sockaddr_storage),
|
||||
/*lpProtocolInfo=*/nullptr, address_string.data(), &size) != 0) {
|
||||
LOG(ERROR) << __func__
|
||||
<< ": Cannot convert address to string: " << WSAGetLastError();
|
||||
return "";
|
||||
}
|
||||
// size includes the null terminator.
|
||||
address_string.resize(size - 1);
|
||||
return address_string;
|
||||
}
|
||||
|
||||
} // namespace nearby::windows
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SOCKET_ADDRESS_H_
|
||||
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SOCKET_ADDRESS_H_
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace nearby::windows {
|
||||
|
||||
// A helper class that simplifies handling of both IPv4 and IPv6 addresses.
|
||||
// IPv6 support needs to be enabled by setting `dual_stack` to true.
|
||||
class SocketAddress {
|
||||
public:
|
||||
explicit SocketAddress(bool dual_stack = false) : dual_stack_(dual_stack) {
|
||||
std::memset(&address_, 0, sizeof(address_));
|
||||
if (dual_stack_) {
|
||||
address_.ss_family = AF_INET6;
|
||||
} else {
|
||||
address_.ss_family = AF_INET;
|
||||
}
|
||||
}
|
||||
explicit SocketAddress(const sockaddr_in& address, bool dual_stack = false);
|
||||
explicit SocketAddress(const sockaddr_in6& address);
|
||||
explicit SocketAddress(const sockaddr_storage& address);
|
||||
|
||||
~SocketAddress() = default;
|
||||
|
||||
// The `dual_stack` state of `address` determines whether the address is
|
||||
// can be parsed as IPv6.
|
||||
// If dual_stack is enabled, an IPv4 string will be returned as a mapped IPv6
|
||||
// address (e.g. [::ffff:192.0.2.1]).
|
||||
// Use empty `address_string` to create and unspecified address ie. ADDR_ANY.
|
||||
static bool FromString(SocketAddress& address,
|
||||
const std::string& address_string, int port = 0);
|
||||
|
||||
// `Returns port in host byte order.
|
||||
int port() const;
|
||||
// `port` is in host byte order.
|
||||
bool set_port(int port);
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
const sockaddr_in* v4_address() const {
|
||||
return reinterpret_cast<const sockaddr_in*>(&address_);
|
||||
}
|
||||
const sockaddr_in6* v6_address() const {
|
||||
return reinterpret_cast<const sockaddr_in6*>(&address_);
|
||||
}
|
||||
sockaddr* address() {
|
||||
return reinterpret_cast<sockaddr*>(&address_);
|
||||
}
|
||||
|
||||
private:
|
||||
// If `address_` is AF_INET, then rewrite into mapped ipv6 address, e.g.
|
||||
// [::ffff:192.0.2.1].
|
||||
void ToMappedIPv6();
|
||||
|
||||
const bool dual_stack_ = false;
|
||||
sockaddr_storage address_;
|
||||
};
|
||||
|
||||
} // namespace nearby::windows
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SOCKET_ADDRESS_H_
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "internal/platform/implementation/windows/socket_address.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace nearby::windows {
|
||||
namespace {
|
||||
|
||||
TEST(SocketAddressTest, CreateFromSockAddrIn) {
|
||||
sockaddr_in address;
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_port = htons(8080);
|
||||
address.sin_addr.s_addr = inet_addr("192.168.1.1");
|
||||
SocketAddress socket_address(address, /*dual_stack=*/false);
|
||||
EXPECT_EQ(socket_address.ToString(), "192.168.1.1:8080");
|
||||
EXPECT_EQ(socket_address.port(), 8080);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, CreateFromSockAddrInDualStack) {
|
||||
sockaddr_in address;
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_port = htons(8080);
|
||||
address.sin_addr.s_addr = inet_addr("192.168.1.1");
|
||||
SocketAddress socket_address(address, /*dual_stack=*/true);
|
||||
EXPECT_EQ(socket_address.ToString(), "[::ffff:192.168.1.1]:8080");
|
||||
EXPECT_EQ(socket_address.port(), 8080);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, CreateFromSockAddrIn6) {
|
||||
sockaddr_in6 address;
|
||||
address.sin6_family = AF_INET6;
|
||||
address.sin6_port = htons(8080);
|
||||
address.sin6_flowinfo = 0;
|
||||
address.sin6_scope_id = 0;
|
||||
address.sin6_addr.u.Word[0] = 0xFDDF;
|
||||
address.sin6_addr.u.Word[1] = 0x253D;
|
||||
address.sin6_addr.u.Word[2] = 0x0BA3;
|
||||
address.sin6_addr.u.Word[3] = 0x51A2;
|
||||
address.sin6_addr.u.Word[4] = 0x51ED;
|
||||
address.sin6_addr.u.Word[5] = 0xEE8F;
|
||||
address.sin6_addr.u.Word[6] = 0x8F1C;
|
||||
address.sin6_addr.u.Word[7] = 0xB30C;
|
||||
SocketAddress socket_address(address);
|
||||
EXPECT_EQ(socket_address.ToString(),
|
||||
"[dffd:3d25:a30b:a251:ed51:8fee:1c8f:cb3]:8080");
|
||||
EXPECT_EQ(socket_address.port(), 8080);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv4) {
|
||||
SocketAddress address;
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "192.168.1.1", 8080));
|
||||
EXPECT_EQ(address.ToString(), "192.168.1.1:8080");
|
||||
EXPECT_EQ(address.port(), 8080);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv4DualStack) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "192.168.1.1", 8080));
|
||||
EXPECT_EQ(address.ToString(), "[::ffff:192.168.1.1]:8080");
|
||||
EXPECT_EQ(address.port(), 8080);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv4NoPort) {
|
||||
SocketAddress address;
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "192.168.1.1"));
|
||||
EXPECT_EQ(address.ToString(), "192.168.1.1");
|
||||
EXPECT_EQ(address.port(), 0);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv4AnyAddress) {
|
||||
SocketAddress address;
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, ""));
|
||||
EXPECT_EQ(address.ToString(), "0.0.0.0");
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv4NoPortDualStack) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "192.168.1.1"));
|
||||
EXPECT_EQ(address.ToString(), "::ffff:192.168.1.1");
|
||||
EXPECT_EQ(address.port(), 0);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv6) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "2001:db8::1", 8080));
|
||||
EXPECT_EQ(address.ToString(), "[2001:db8::1]:8080");
|
||||
EXPECT_EQ(address.port(), 8080);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv6NoPort) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "2001:db8::1"));
|
||||
EXPECT_EQ(address.ToString(), "2001:db8::1");
|
||||
EXPECT_EQ(address.port(), 0);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringIPv6AnyAddress) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, ""));
|
||||
EXPECT_EQ(address.ToString(), "::");
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringMappedIPv4) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "::ffff:192.168.1.1", 8080));
|
||||
EXPECT_EQ(address.port(), 8080);
|
||||
EXPECT_EQ(address.ToString(), "[::ffff:192.168.1.1]:8080");
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringInvalid) {
|
||||
SocketAddress address;
|
||||
EXPECT_FALSE(SocketAddress::FromString(address, "invalid", 8080));
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, FromStringInvalidDualStack) {
|
||||
SocketAddress address(/*dual_stack=*/true);
|
||||
EXPECT_FALSE(SocketAddress::FromString(address, "invalid", 8080));
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, SetPort) {
|
||||
SocketAddress address;
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "127.0.0.1", 8080));
|
||||
EXPECT_EQ(address.port(), 8080);
|
||||
EXPECT_TRUE(address.set_port(9090));
|
||||
EXPECT_EQ(address.port(), 9090);
|
||||
}
|
||||
|
||||
TEST(SocketAddressTest, SetInvalidPort) {
|
||||
SocketAddress address;
|
||||
EXPECT_TRUE(SocketAddress::FromString(address, "127.0.0.1", 8080));
|
||||
EXPECT_FALSE(address.set_port(-1));
|
||||
EXPECT_FALSE(address.set_port(65536));
|
||||
EXPECT_EQ(address.port(), 8080);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace nearby::windows
|
||||
Reference in New Issue
Block a user