From 28d3632d1c39fb710022b52ac58833ea701c1e17 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Wed, 12 Nov 2025 11:04:24 +0200 Subject: [PATCH 01/20] fix(ssl): fix missing hostname verification Fix the issue because of which there was no hostname verification during the TLS handshake. refactor(dns-windows): improve DNS server discovery Filter out DNS servers received from disconnected adapters, loopback, and APIPA addresses. test(dns-tls-windows): add test to test DNS discovery Added tests to test DNS server discovery in a controlled Windows environment (manual). --- openssl/pbpal_connect_openssl.c | 15 + windows/pubnub_dns_system_servers.c | 111 ++++- windows/tests/pubnub_dns_windows_test.cpp | 529 +++++++++++++++++++++ windows/tests/pubnub_tls_security_test.cpp | 408 ++++++++++++++++ 4 files changed, 1046 insertions(+), 17 deletions(-) create mode 100644 windows/tests/pubnub_dns_windows_test.cpp create mode 100644 windows/tests/pubnub_tls_security_test.cpp diff --git a/openssl/pbpal_connect_openssl.c b/openssl/pbpal_connect_openssl.c index 508d2095..4829ef0d 100644 --- a/openssl/pbpal_connect_openssl.c +++ b/openssl/pbpal_connect_openssl.c @@ -204,6 +204,21 @@ enum pbpal_tls_result pbpal_start_tls(pubnub_t* pb) PUBNUB_LOG_ERROR("pb=%p SSL_new failed\n", pb); return pbtlsResourceFailure; } + + /** Enable hostname verification. */ + X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + if (param != NULL) { + char const* hostname = pb->origin; + if (!X509_VERIFY_PARAM_set1_host(param, hostname, 0)) { + PUBNUB_LOG_WARNING( + "pb=%p: X509_VERIFY_PARAM_set1_host() failed to set hostname '%s' for verification\n", + pb, + hostname); + return pbtlsFailed; + } + PUBNUB_LOG_DEBUG("pb=%p: Hostname verification configured for '%s'\n", + pb, hostname); + } PUBNUB_LOG_TRACE("pb=%p: Got SSL\n", pb); SSL_set_fd(ssl, pb->pal.socket); WATCH_ENUM(pb->options.use_blocking_io); diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index 2731228b..f652530a 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -18,7 +18,7 @@ #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) /** Copy to the local address endianness from the network address endianness. */ -static bool copy_ipv4_bytes_from_be_dword( +static bool pubnub_copy_ipv4_bytes_from_be_dword( const struct pubnub_ipv4_address* array, const size_t count, DWORD n_addr, @@ -32,6 +32,10 @@ static bool copy_ipv4_bytes_from_be_dword( temp_ip[2] = (unsigned char)((host_addr >> 8) & 0xFF); temp_ip[3] = (unsigned char)(host_addr & 0xFF); + /* Filter out loopback and APIPA addresses from `DnsQueryConfig`. */ + if (127 == temp_ip[0] || (169 == temp_ip[0] && 254 == temp_ip[1])) + return false; + for (size_t i = 0; i < count; i++) { if (memcmp(array[i].ipv4, temp_ip, 4) == 0) return false; } @@ -44,24 +48,66 @@ static bool copy_ipv4_bytes_from_be_dword( return true; } -int fallback_get_dns_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) +/** Check whether the DNS address is retrieved for the active network adapter + or not. + */ +static bool pubnub_dns_from_active_adapter(DWORD n_addr, IP_ADAPTER_ADDRESSES* addrs) +{ + if (!addrs) return true; + bool found = false; + + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa; aa = aa->Next) { + if (aa->OperStatus != IfOperStatusUp) continue; + + /** Search for "online" adapters */ + bool has_gateway = false; + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { + if (NULL != gw->Address.lpSockaddr) { + has_gateway = true; + break; + } + } + if (!has_gateway) continue; + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds; ds = ds->Next) { + if (!ds->Address.lpSockaddr || + ds->Address.lpSockaddr->sa_family != AF_INET) { + continue; + } + + const struct sockaddr_in* sin = (const struct sockaddr_in*)ds->Address.lpSockaddr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + if (net_addr == 0) continue; + + if (net_addr == n_addr) { + found = true; + break; + } + } + if (found) break; + } + + return found; +} + +/** Retrieve DNS servers from "online" network adapters. */ +int pubnub_dns_get_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) { - ULONG buflen; - DWORD ret; IP_ADAPTER_ADDRESSES* addrs; + unsigned char temp_ip[4]; DWORD net_addr; unsigned j = 0; - unsigned char temp_ip[4]; + ULONG buflen; + DWORD ret; /* Get required buffer size */ ret = GetAdaptersAddresses( AF_INET, - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, /* keep DNS servers */ - NULL, NULL, &buflen - ); + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, NULL, &buflen); if (ret != ERROR_BUFFER_OVERFLOW || buflen == 0) { - PUBNUB_LOG_ERROR("GetAdaptersAddresses preflight failed: %lu\n", (unsigned long)ret); + PUBNUB_LOG_DEBUG("GetAdaptersAddresses can't retrieve any adapters: %lu\n", (unsigned long)ret); return (int)j; } @@ -74,9 +120,8 @@ int fallback_get_dns_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) /* Get adapter information */ ret = GetAdaptersAddresses( AF_INET, - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, /* keep DNS servers */ - NULL, addrs, &buflen - ); + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); if (ret != NO_ERROR) { PUBNUB_LOG_ERROR("GetAdaptersAddresses failed: %lu\n", (unsigned long)ret); FREE(addrs); @@ -92,6 +137,16 @@ int fallback_get_dns_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) continue; } + /** Search for "online" adapters */ + bool has_gateway = false; + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { + if (NULL != gw->Address.lpSockaddr) { + has_gateway = true; + break; + } + } + if (!has_gateway) continue; + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && j < n; ds = ds->Next) { if (!ds->Address.lpSockaddr || ds->Address.lpSockaddr->sa_family != AF_INET) { @@ -100,9 +155,9 @@ int fallback_get_dns_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) const struct sockaddr_in* sin = (const struct sockaddr_in*)ds->Address.lpSockaddr; net_addr = sin->sin_addr.S_un.S_addr; - if (net_addr == 0) continue; /* skip 0.0.0.0 */ + if (net_addr == 0) continue; - if (copy_ipv4_bytes_from_be_dword(o_ipv4, j, net_addr, o_ipv4[j].ipv4)) + if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, j, net_addr, o_ipv4[j].ipv4)) ++j; } } @@ -129,12 +184,34 @@ int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size 0, NULL, NULL, ip4_list, &buflen); if (status == ERROR_SUCCESS) { + IP_ADAPTER_ADDRESSES* active_adapters = NULL; + ULONG adapter_buflen = 0; + + if (GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, NULL, &adapter_buflen) == ERROR_BUFFER_OVERFLOW) { + active_adapters = (IP_ADAPTER_ADDRESSES*)MALLOC(adapter_buflen); + if (active_adapters) { + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, active_adapters, &adapter_buflen); + if (NO_ERROR != ret) { + PUBNUB_LOG_WARNING("GetAdaptersAddresses failed: %lu - can't validate DNS servers\n", + (unsigned long)ret); + FREE(active_adapters); + active_adapters = NULL; + } + } + } + for (DWORD i = 0; i < ip4_list->AddrCount && j < n; ++i) { - if (ip4_list->AddrArray[i] == 0) continue; + if (ip4_list->AddrArray[i] == 0 || + !pubnub_dns_from_active_adapter(ip4_list->AddrArray[i], active_adapters)) { + continue; + } - if (copy_ipv4_bytes_from_be_dword(o_ipv4, j, ip4_list->AddrArray[i], o_ipv4[j].ipv4)) + if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, j, ip4_list->AddrArray[i], o_ipv4[j].ipv4)) ++j; } + if (active_adapters) FREE(active_adapters); } LocalFree(ip4_list); @@ -143,5 +220,5 @@ int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size if (j > 0) return (int)j; } - return fallback_get_dns_via_adapters(o_ipv4, n); + return pubnub_dns_get_via_adapters(o_ipv4, n); } \ No newline at end of file diff --git a/windows/tests/pubnub_dns_windows_test.cpp b/windows/tests/pubnub_dns_windows_test.cpp new file mode 100644 index 00000000..4d281b07 --- /dev/null +++ b/windows/tests/pubnub_dns_windows_test.cpp @@ -0,0 +1,529 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +/** + * @file pubnub_dns_windows_test.cpp + * @brief Comprehensive Windows DNS resolution tests for PubNub C-core SDK + * + * Tests cover: + * - DNS server enumeration with various adapter states + * - Filtering of invalid/disabled adapters + * - Thread safety of DNS resolution + * - Fallback mechanisms + * - Edge cases with VPN/virtual adapters + */ + +#include "core/pubnub_dns_servers.h" +#include "core/pubnub_log.h" +#include "core/pubnub_assert.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "dnsapi.lib") + +// Test framework macros +#define TEST_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + std::cerr << "FAIL: " << msg << " at " << __FILE__ << ":" << __LINE__ << std::endl; \ + return false; \ + } \ + } while(0) + +#define TEST_LOG(msg) \ + std::cout << "[TEST] " << msg << std::endl + +#define TEST_PASS(name) \ + std::cout << "[PASS] " << name << std::endl + +// Helper to format IPv4 address +std::string format_ipv4(const pubnub_ipv4_address& addr) { + std::ostringstream oss; + oss << (int)addr.ipv4[0] << "." + << (int)addr.ipv4[1] << "." + << (int)addr.ipv4[2] << "." + << (int)addr.ipv4[3]; + return oss.str(); +} + +// Helper to check if DNS server is reachable +bool is_dns_reachable(const pubnub_ipv4_address& dns_server, int timeout_ms = 2000) { + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) { + return false; + } + + sockaddr_in dns_addr = {}; + dns_addr.sin_family = AF_INET; + dns_addr.sin_port = htons(53); + memcpy(&dns_addr.sin_addr.s_addr, dns_server.ipv4, 4); + + // Set socket timeout + DWORD timeout = timeout_ms; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); + + // Simple DNS query for "test.local" - type A + unsigned char query[] = { + 0x00, 0x01, // Transaction ID + 0x01, 0x00, // Flags: standard query + 0x00, 0x01, // Questions: 1 + 0x00, 0x00, // Answer RRs: 0 + 0x00, 0x00, // Authority RRs: 0 + 0x00, 0x00, // Additional RRs: 0 + // Query: test.local + 0x04, 't', 'e', 's', 't', + 0x05, 'l', 'o', 'c', 'a', 'l', + 0x00, + 0x00, 0x01, // Type A + 0x00, 0x01 // Class IN + }; + + int sent = sendto(sock, (const char*)query, sizeof(query), 0, + (sockaddr*)&dns_addr, sizeof(dns_addr)); + + bool reachable = false; + if (sent > 0) { + unsigned char response[512]; + int received = recvfrom(sock, (char*)response, sizeof(response), 0, NULL, NULL); + // We got a response (even if it's NXDOMAIN), server is reachable + reachable = (received > 0); + } + + closesocket(sock); + return reachable; +} + +// Test 1: Basic DNS server enumeration +bool test_dns_enumeration_basic() { + TEST_LOG("Test 1: Basic DNS server enumeration"); + + pubnub_ipv4_address servers[10]; + memset(servers, 0, sizeof(servers)); + + int count = pubnub_dns_read_system_servers_ipv4(servers, 10); + + TEST_ASSERT(count >= 0, "DNS enumeration should not fail"); + TEST_ASSERT(count <= 10, "Should not exceed array bounds"); + + TEST_LOG("Found " << count << " DNS servers:"); + for (int i = 0; i < count; i++) { + TEST_LOG(" [" << i << "] " << format_ipv4(servers[i])); + + // Verify not 0.0.0.0 + TEST_ASSERT(servers[i].ipv4[0] != 0 || servers[i].ipv4[1] != 0 || + servers[i].ipv4[2] != 0 || servers[i].ipv4[3] != 0, + "DNS server should not be 0.0.0.0"); + } + + TEST_PASS("test_dns_enumeration_basic"); + return true; +} + +// Test 2: DNS server uniqueness (no duplicates) +bool test_dns_no_duplicates() { + TEST_LOG("Test 2: Verify no duplicate DNS servers returned"); + + pubnub_ipv4_address servers[16]; + memset(servers, 0, sizeof(servers)); + + int count = pubnub_dns_read_system_servers_ipv4(servers, 16); + TEST_ASSERT(count >= 0, "DNS enumeration should succeed"); + + // Check for duplicates + for (int i = 0; i < count; i++) { + for (int j = i + 1; j < count; j++) { + bool is_duplicate = (memcmp(servers[i].ipv4, servers[j].ipv4, 4) == 0); + TEST_ASSERT(!is_duplicate, + "Found duplicate DNS server: " << format_ipv4(servers[i])); + } + } + + TEST_PASS("test_dns_no_duplicates"); + return true; +} + +// Test 3: Verify only UP adapters are used +bool test_dns_only_up_adapters() { + TEST_LOG("Test 3: Verify DNS servers come from UP adapters only"); + + // Get DNS servers from our function + pubnub_ipv4_address our_servers[16]; + memset(our_servers, 0, sizeof(our_servers)); + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + // Manually enumerate all adapters including DOWN ones + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); + + // Collect DNS servers from DOWN adapters + std::vector down_adapter_dns; + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (aa->OperStatus != IfOperStatusUp) { + TEST_LOG("Found DOWN adapter: " << aa->AdapterName << " status=" << aa->OperStatus); + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; + ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = (sockaddr_in*)ds->Address.lpSockaddr; + pubnub_ipv4_address addr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + addr.ipv4[0] = (host_addr >> 24) & 0xFF; + addr.ipv4[1] = (host_addr >> 16) & 0xFF; + addr.ipv4[2] = (host_addr >> 8) & 0xFF; + addr.ipv4[3] = host_addr & 0xFF; + down_adapter_dns.push_back(format_ipv4(addr)); + } + } + } + } + + // Verify none of the DOWN adapter DNS servers are in our list + for (const auto& down_dns : down_adapter_dns) { + for (int i = 0; i < our_count; i++) { + std::string our_dns = format_ipv4(our_servers[i]); + TEST_ASSERT(our_dns != down_dns, + "DNS server from DOWN adapter found: " << down_dns); + } + } + + free(addrs); + TEST_PASS("test_dns_only_up_adapters"); + return true; +} + +// Test 4: Thread safety +bool test_dns_thread_safety() { + TEST_LOG("Test 4: Thread safety of DNS enumeration"); + + const int num_threads = 10; + const int iterations = 5; + std::vector threads; + std::atomic failures(0); + + auto worker = [&]() { + for (int i = 0; i < iterations; i++) { + pubnub_ipv4_address servers[16]; + int count = pubnub_dns_read_system_servers_ipv4(servers, 16); + if (count < 0) { + failures++; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + }; + + for (int i = 0; i < num_threads; i++) { + threads.emplace_back(worker); + } + + for (auto& t : threads) { + t.join(); + } + + TEST_ASSERT(failures == 0, "Thread safety test had " << failures << " failures"); + TEST_PASS("test_dns_thread_safety"); + return true; +} + +// Test 5: Verify no loopback or tunnel adapters +bool test_dns_no_invalid_adapter_types() { + TEST_LOG("Test 5: Verify no loopback/tunnel adapter DNS servers"); + + pubnub_ipv4_address our_servers[16]; + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); + + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (aa->IfType == IF_TYPE_SOFTWARE_LOOPBACK || aa->IfType == IF_TYPE_TUNNEL) { + TEST_LOG("Found invalid adapter type: " << aa->AdapterName + << " type=" << aa->IfType); + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; + ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = (sockaddr_in*)ds->Address.lpSockaddr; + pubnub_ipv4_address bad_addr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + bad_addr.ipv4[0] = (host_addr >> 24) & 0xFF; + bad_addr.ipv4[1] = (host_addr >> 16) & 0xFF; + bad_addr.ipv4[2] = (host_addr >> 8) & 0xFF; + bad_addr.ipv4[3] = host_addr & 0xFF; + + // Ensure it's not in our list + for (int i = 0; i < our_count; i++) { + bool found = (memcmp(our_servers[i].ipv4, bad_addr.ipv4, 4) == 0); + TEST_ASSERT(!found, + "DNS from invalid adapter found: " << format_ipv4(bad_addr)); + } + } + } + } + } + + free(addrs); + TEST_PASS("test_dns_no_invalid_adapter_types"); + return true; +} + +// Test 6: DNS server reachability (optional - may require network) +bool test_dns_reachability() { + TEST_LOG("Test 6: Check if returned DNS servers are reachable"); + + pubnub_ipv4_address servers[10]; + int count = pubnub_dns_read_system_servers_ipv4(servers, 10); + + TEST_ASSERT(count > 0, "Should have at least one DNS server"); + + int reachable_count = 0; + for (int i = 0; i < count; i++) { + std::string ip = format_ipv4(servers[i]); + TEST_LOG("Testing reachability of " << ip); + + bool reachable = is_dns_reachable(servers[i], 3000); + if (reachable) { + TEST_LOG(" [REACHABLE] " << ip); + reachable_count++; + } else { + TEST_LOG(" [TIMEOUT] " << ip << " (may be behind firewall)"); + } + } + + // At least one should be reachable in normal conditions + // But we make this a warning, not a failure, as corporate firewalls may block + if (reachable_count == 0) { + TEST_LOG("WARNING: No DNS servers reachable - this may indicate network issues"); + } + + TEST_PASS("test_dns_reachability"); + return true; +} + +// Test 7: Boundary conditions +bool test_dns_boundary_conditions() { + TEST_LOG("Test 7: Boundary conditions"); + + // Test with n=0 + int count = pubnub_dns_read_system_servers_ipv4(NULL, 0); + TEST_ASSERT(count == 0, "Should return 0 for n=0"); + + // Test with n=1 + pubnub_ipv4_address one_server[1]; + count = pubnub_dns_read_system_servers_ipv4(one_server, 1); + TEST_ASSERT(count <= 1, "Should return at most 1 server"); + + // Test with large n + pubnub_ipv4_address many_servers[100]; + count = pubnub_dns_read_system_servers_ipv4(many_servers, 100); + TEST_ASSERT(count >= 0 && count <= 100, "Should respect array bounds"); + + TEST_PASS("test_dns_boundary_conditions"); + return true; +} + +// Test 8: IPv4-enabled flag verification +bool test_dns_ipv4_enabled_only() { + TEST_LOG("Test 8: Verify DNS servers only from IPv4-enabled adapters"); + + pubnub_ipv4_address our_servers[16]; + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); + + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (!(aa->Flags & IP_ADAPTER_IPV4_ENABLED)) { + TEST_LOG("Found adapter without IPv4: " << aa->AdapterName); + + // This adapter's DNS servers should not be in our list + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; + ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr->sa_family == AF_INET) { + TEST_LOG(" WARNING: Adapter has IPv4 DNS but IPv4 not enabled"); + } + } + } + } + + free(addrs); + TEST_PASS("test_dns_ipv4_enabled_only"); + return true; +} + +// Test 9: Verify no DNS from adapters without gateway (CRITICAL TEST) +bool test_dns_only_adapters_with_gateway() { + TEST_LOG("Test 9: Verify DNS servers only from adapters with gateway"); + TEST_LOG("This test validates the fix for disconnected VPN adapters"); + + pubnub_ipv4_address our_servers[16]; + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); + + // Find adapters that are UP but have NO gateway + std::vector no_gateway_dns; + int adapters_without_gateway = 0; + + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (aa->OperStatus == IfOperStatusUp) { + // Check if adapter has gateway + bool has_gateway = false; + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; + gw != NULL; gw = gw->Next) { + if (gw->Address.lpSockaddr != NULL) { + has_gateway = true; + break; + } + } + + if (!has_gateway) { + adapters_without_gateway++; + TEST_LOG("Found UP adapter WITHOUT gateway: " << aa->AdapterName + << " (likely disconnected VPN or disabled connection)"); + + // Collect DNS servers from this adapter + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; + ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr && + ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = (sockaddr_in*)ds->Address.lpSockaddr; + pubnub_ipv4_address addr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + addr.ipv4[0] = (host_addr >> 24) & 0xFF; + addr.ipv4[1] = (host_addr >> 16) & 0xFF; + addr.ipv4[2] = (host_addr >> 8) & 0xFF; + addr.ipv4[3] = host_addr & 0xFF; + + std::string dns_str = format_ipv4(addr); + no_gateway_dns.push_back(dns_str); + TEST_LOG(" Found DNS on no-gateway adapter: " << dns_str); + } + } + } + } + } + + TEST_LOG("Found " << adapters_without_gateway << " adapters without gateway"); + TEST_LOG("Found " << no_gateway_dns.size() << " DNS servers from no-gateway adapters"); + + // CRITICAL CHECK: None of these DNS servers should be in our returned list + for (const auto& bad_dns : no_gateway_dns) { + for (int i = 0; i < our_count; i++) { + std::string our_dns = format_ipv4(our_servers[i]); + TEST_ASSERT(our_dns != bad_dns, + "CRITICAL: DNS from no-gateway adapter found: " << bad_dns + << " - this indicates the gateway check is not working!"); + } + } + + if (adapters_without_gateway > 0) { + TEST_LOG("✓ Gateway check working: " << adapters_without_gateway + << " adapters without gateway were properly filtered"); + } else { + TEST_LOG("INFO: No adapters without gateway found (all connections have valid routes)"); + } + + free(addrs); + TEST_PASS("test_dns_only_adapters_with_gateway"); + return true; +} + +// Test 10: Verify loopback (127.x.x.x) and APIPA (169.254.x.x) filtering +bool test_dns_no_loopback_or_apipa() { + TEST_LOG("Test 10: Verify no loopback (127.x.x.x) or APIPA (169.254.x.x) DNS servers"); + + pubnub_ipv4_address servers[16]; + int count = pubnub_dns_read_system_servers_ipv4(servers, 16); + + for (int i = 0; i < count; i++) { + std::string ip = format_ipv4(servers[i]); + + // Check for loopback (127.x.x.x) + TEST_ASSERT(servers[i].ipv4[0] != 127, + "Found loopback DNS server: " << ip); + + // Check for APIPA (169.254.x.x) + bool is_apipa = (servers[i].ipv4[0] == 169 && servers[i].ipv4[1] == 254); + TEST_ASSERT(!is_apipa, + "Found APIPA DNS server: " << ip); + } + + TEST_PASS("test_dns_no_loopback_or_apipa"); + return true; +} + +// Main test runner +int main(int argc, char* argv[]) { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + std::cerr << "WSAStartup failed" << std::endl; + return 1; + } + + std::cout << "===========================================\n"; + std::cout << "PubNub DNS Windows Tests\n"; + std::cout << "===========================================\n\n"; + + bool all_passed = true; + + all_passed &= test_dns_enumeration_basic(); + all_passed &= test_dns_no_duplicates(); + all_passed &= test_dns_only_up_adapters(); + all_passed &= test_dns_thread_safety(); + all_passed &= test_dns_no_invalid_adapter_types(); + all_passed &= test_dns_reachability(); + all_passed &= test_dns_boundary_conditions(); + all_passed &= test_dns_ipv4_enabled_only(); + all_passed &= test_dns_only_adapters_with_gateway(); + all_passed &= test_dns_no_loopback_or_apipa(); + + std::cout << "\n===========================================\n"; + if (all_passed) { + std::cout << "✓ ALL TESTS PASSED\n"; + } else { + std::cout << "✗ SOME TESTS FAILED\n"; + } + std::cout << "===========================================\n"; + + WSACleanup(); + return all_passed ? 0 : 1; +} \ No newline at end of file diff --git a/windows/tests/pubnub_tls_security_test.cpp b/windows/tests/pubnub_tls_security_test.cpp new file mode 100644 index 00000000..9394bd92 --- /dev/null +++ b/windows/tests/pubnub_tls_security_test.cpp @@ -0,0 +1,408 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +/** + * @file pubnub_tls_security_test.cpp + * @brief TLS/SSL security validation tests for PubNub C-core SDK on Windows + * + * Tests cover: + * - Certificate validation + * - Hostname verification (currently missing - this test will fail!) + * - Protocol version negotiation + * - System certificate store integration + * - SSL session reuse security + * - Certificate expiration handling + */ + +#include "pubnub_sync.h" +#include "core/pubnub_ssl.h" +#include "core/pubnub_helper.h" +#include "core/pubnub_timers.h" +#include "core/pubnub_log.h" + +#include +#include +#include +#include +#include +#include + +#define TEST_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + std::cerr << "FAIL: " << msg << " at " << __FILE__ << ":" << __LINE__ << std::endl; \ + return false; \ + } \ + } while(0) + +#define TEST_LOG(msg) \ + std::cout << "[TEST] " << msg << std::endl + +#define TEST_PASS(name) \ + std::cout << "[PASS] " << name << std::endl + +#define TEST_FAIL(name, reason) \ + std::cout << "[FAIL] " << name << ": " << reason << std::endl + +// Test helper: create context +pubnub_t* create_test_context() { + pubnub_t* pb = pubnub_alloc(); + if (pb != NULL) { + pubnub_init(pb, "demo", "demo"); + pubnub_set_ssl_options(pb, true, false); // SSL required, no fallback + } + return pb; +} + +// Test 1: Verify SSL is enabled and working +bool test_ssl_basic_connection() { + TEST_LOG("Test 1: Basic SSL connection to PubNub"); + + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + pubnub_set_transaction_timeout(pb, 15000); + + // Attempt time request over SSL + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + TEST_ASSERT(res == PNR_OK, "SSL connection failed: " << pubnub_res_2_string(res)); + + std::string timetoken = pubnub_get(pb); + TEST_LOG("Received timetoken: " << timetoken); + TEST_ASSERT(!timetoken.empty(), "Timetoken should not be empty"); + + pubnub_free(pb); + TEST_PASS("test_ssl_basic_connection"); + return true; +} + +// Test 2: Verify certificate validation (should reject self-signed certs) +bool test_certificate_validation() { + TEST_LOG("Test 2: Certificate validation with invalid certificate"); + + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Try connecting to a known self-signed cert site + // Note: This requires the ability to set custom origin +#if PUBNUB_ORIGIN_SETTABLE + pubnub_set_origin(pb, "self-signed.badssl.com"); + pubnub_set_transaction_timeout(pb, 10000); + + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + // Should fail with IO_ERROR or similar due to certificate validation failure + TEST_ASSERT(res != PNR_OK, "Should reject self-signed certificate"); + TEST_LOG("Correctly rejected invalid certificate with error: " << pubnub_res_2_string(res)); +#else + TEST_LOG("SKIP: Origin setting not enabled (PUBNUB_ORIGIN_SETTABLE=0)"); +#endif + + pubnub_free(pb); + TEST_PASS("test_certificate_validation"); + return true; +} + +// Test 3: CRITICAL - Hostname verification +bool test_hostname_verification() { + TEST_LOG("Test 3: CRITICAL - Hostname verification"); + TEST_LOG("WARNING: Current implementation may NOT verify hostname!"); + +#if PUBNUB_ORIGIN_SETTABLE + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Try connecting to wrong-host.badssl.com + // This has a valid certificate but for the wrong hostname + pubnub_set_origin(pb, "wrong.host.badssl.com"); + pubnub_set_transaction_timeout(pb, 10000); + + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + if (res == PNR_OK) { + TEST_FAIL("test_hostname_verification", + "SECURITY VULNERABILITY: Accepted certificate with wrong hostname!"); + std::cerr << "\n===========================================\n"; + std::cerr << "CRITICAL SECURITY ISSUE DETECTED!\n"; + std::cerr << "The library does not verify hostname in certificates!\n"; + std::cerr << "This allows Man-in-the-Middle attacks!\n"; + std::cerr << "===========================================\n\n"; + pubnub_free(pb); + return false; + } else { + TEST_LOG("Correctly rejected certificate with wrong hostname"); + } + + pubnub_free(pb); + TEST_PASS("test_hostname_verification"); + return true; +#else + TEST_LOG("SKIP: Origin setting not enabled (PUBNUB_ORIGIN_SETTABLE=0)"); + TEST_LOG("NOTE: Hostname verification cannot be tested without custom origin support"); + return true; +#endif +} + +// Test 4: System certificate store usage +bool test_system_certificate_store() { + TEST_LOG("Test 4: System certificate store integration"); + + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Enable system certificate store + int res_cert = pubnub_ssl_use_system_certificate_store(pb); + TEST_ASSERT(res_cert == 0, "Failed to enable system cert store"); + + pubnub_set_transaction_timeout(pb, 15000); + + // This should work with system certs + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + TEST_ASSERT(res == PNR_OK, "Connection failed with system certs: " << pubnub_res_2_string(res)); + + pubnub_free(pb); + TEST_PASS("test_system_certificate_store"); + return true; +} + +// Test 5: SSL protocol version (should use TLS 1.2+, not SSLv3) +bool test_protocol_version() { + TEST_LOG("Test 5: SSL/TLS protocol version negotiation"); + +#if PUBNUB_ORIGIN_SETTABLE + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Try connecting to TLS 1.2 only server + // tls-v1-2.badssl.com requires TLS 1.2 or higher + pubnub_set_origin(pb, "tls-v1-2.badssl.com"); + pubnub_set_transaction_timeout(pb, 10000); + + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + // Should succeed if we support TLS 1.2+ + if (res == PNR_OK) { + TEST_LOG("Successfully negotiated TLS 1.2+"); + } else { + TEST_LOG("WARNING: Failed to connect to TLS 1.2 server: " << pubnub_res_2_string(res)); + } + + pubnub_free(pb); +#else + TEST_LOG("SKIP: Origin setting not enabled"); +#endif + + TEST_PASS("test_protocol_version"); + return true; +} + +// Test 6: Certificate expiration handling +bool test_expired_certificate() { + TEST_LOG("Test 6: Expired certificate handling"); + +#if PUBNUB_ORIGIN_SETTABLE + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Try connecting to expired.badssl.com + pubnub_set_origin(pb, "expired.badssl.com"); + pubnub_set_transaction_timeout(pb, 10000); + + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + // Should fail due to expired certificate + TEST_ASSERT(res != PNR_OK, "Should reject expired certificate"); + TEST_LOG("Correctly rejected expired certificate"); + + pubnub_free(pb); +#else + TEST_LOG("SKIP: Origin setting not enabled"); +#endif + + TEST_PASS("test_expired_certificate"); + return true; +} + +// Test 7: SSL session reuse +bool test_ssl_session_reuse() { + TEST_LOG("Test 7: SSL session reuse"); + + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Enable SSL session reuse + pubnub_set_reuse_ssl_session(pb, true); + pubnub_set_transaction_timeout(pb, 15000); + + // First connection + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + TEST_ASSERT(res == PNR_OK, "First connection failed"); + + // Second connection (should reuse session) + res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + TEST_ASSERT(res == PNR_OK, "Second connection (session reuse) failed"); + + TEST_LOG("SSL session reuse appears to be working"); + + pubnub_free(pb); + TEST_PASS("test_ssl_session_reuse"); + return true; +} + +// Test 8: Concurrent SSL connections (thread safety) +bool test_concurrent_ssl_connections() { + TEST_LOG("Test 8: Concurrent SSL connections"); + + const int num_contexts = 5; + pubnub_t* contexts[num_contexts]; + + // Create multiple contexts + for (int i = 0; i < num_contexts; i++) { + contexts[i] = create_test_context(); + TEST_ASSERT(contexts[i] != NULL, "Failed to create context " << i); + pubnub_set_transaction_timeout(contexts[i], 20000); + } + + // Start all time requests + for (int i = 0; i < num_contexts; i++) { + enum pubnub_res res = pubnub_time(contexts[i]); + TEST_ASSERT(res == PNR_STARTED || res == PNR_OK, + "Failed to start request " << i); + } + + // Wait for all to complete + bool all_ok = true; + for (int i = 0; i < num_contexts; i++) { + enum pubnub_res res = pubnub_await(contexts[i]); + if (res != PNR_OK) { + TEST_LOG("Context " << i << " failed: " << pubnub_res_2_string(res)); + all_ok = false; + } + } + + // Cleanup + for (int i = 0; i < num_contexts; i++) { + pubnub_free(contexts[i]); + } + + TEST_ASSERT(all_ok, "Some concurrent connections failed"); + TEST_PASS("test_concurrent_ssl_connections"); + return true; +} + +// Test 9: Custom CA certificate +bool test_custom_ca_certificate() { + TEST_LOG("Test 9: Custom CA certificate handling"); + + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // Try setting a non-existent CA file (should be handled gracefully) + int res = pubnub_set_ssl_verify_locations(pb, "nonexistent.pem", NULL); + TEST_ASSERT(res == 0, "set_ssl_verify_locations should not fail for missing file"); + + // The actual error should occur during connection + pubnub_set_transaction_timeout(pb, 10000); + enum pubnub_res conn_res = pubnub_time(pb); + if (conn_res == PNR_STARTED) { + conn_res = pubnub_await(pb); + } + + // With invalid CA, connection might fail (or succeed if it falls back to hardcoded certs) + TEST_LOG("Connection result with invalid CA: " << pubnub_res_2_string(conn_res)); + + pubnub_free(pb); + TEST_PASS("test_custom_ca_certificate"); + return true; +} + +// Test 10: Verify no cleartext fallback +bool test_no_cleartext_fallback() { + TEST_LOG("Test 10: Verify no cleartext fallback"); + + pubnub_t* pb = create_test_context(); + TEST_ASSERT(pb != NULL, "Failed to create context"); + + // SSL required, no fallback + pubnub_set_ssl_options(pb, true, false); + pubnub_set_transaction_timeout(pb, 15000); + + enum pubnub_res res = pubnub_time(pb); + if (res == PNR_STARTED) { + res = pubnub_await(pb); + } + + // Should succeed over SSL + TEST_ASSERT(res == PNR_OK, "SSL connection should succeed"); + + // Verify we're actually using SSL (not cleartext) + // This is implicit - if useSSL=true and we succeed, we used SSL + + pubnub_free(pb); + TEST_PASS("test_no_cleartext_fallback"); + return true; +} + +int main(int argc, char* argv[]) { + std::cout << "===========================================\n"; + std::cout << "PubNub TLS/SSL Security Tests (Windows)\n"; + std::cout << "===========================================\n\n"; + + std::cout << "NOTE: These tests connect to real servers!\n"; + std::cout << "Some tests use badssl.com to verify security\n"; + std::cout << "Internet connection required.\n\n"; + + bool all_passed = true; + + all_passed &= test_ssl_basic_connection(); + all_passed &= test_system_certificate_store(); + all_passed &= test_certificate_validation(); + + // This is the critical test - may expose security vulnerability + std::cout << "\n*** CRITICAL SECURITY TEST ***\n"; + all_passed &= test_hostname_verification(); + std::cout << "*** END CRITICAL TEST ***\n\n"; + + all_passed &= test_protocol_version(); + all_passed &= test_expired_certificate(); + all_passed &= test_ssl_session_reuse(); + all_passed &= test_concurrent_ssl_connections(); + all_passed &= test_custom_ca_certificate(); + all_passed &= test_no_cleartext_fallback(); + + std::cout << "\n===========================================\n"; + if (all_passed) { + std::cout << "✓ ALL TESTS PASSED\n"; + } else { + std::cout << "✗ SOME TESTS FAILED\n"; + std::cout << "\nPLEASE REVIEW FAILURES CAREFULLY!\n"; + std::cout << "Security issues require immediate attention.\n"; + } + std::cout << "===========================================\n"; + + return all_passed ? 0 : 1; +} \ No newline at end of file From a61d3bae94e2ace7150f106cb05f442983585abb Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Thu, 13 Nov 2025 12:00:24 +0200 Subject: [PATCH 02/20] test: addtress Codacy warnings --- windows/tests/pubnub_dns_windows_test.cpp | 95 +++++++++++++++++------ 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/windows/tests/pubnub_dns_windows_test.cpp b/windows/tests/pubnub_dns_windows_test.cpp index 4d281b07..88c73879 100644 --- a/windows/tests/pubnub_dns_windows_test.cpp +++ b/windows/tests/pubnub_dns_windows_test.cpp @@ -48,10 +48,10 @@ // Helper to format IPv4 address std::string format_ipv4(const pubnub_ipv4_address& addr) { std::ostringstream oss; - oss << (int)addr.ipv4[0] << "." - << (int)addr.ipv4[1] << "." - << (int)addr.ipv4[2] << "." - << (int)addr.ipv4[3]; + oss << static_cast(addr.ipv4[0]) << "." + << static_cast(addr.ipv4[1]) << "." + << static_cast(addr.ipv4[2]) << "." + << static_cast(addr.ipv4[3]); return oss.str(); } @@ -69,7 +69,7 @@ bool is_dns_reachable(const pubnub_ipv4_address& dns_server, int timeout_ms = 20 // Set socket timeout DWORD timeout = timeout_ms; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof(timeout)); // Simple DNS query for "test.local" - type A unsigned char query[] = { @@ -87,13 +87,13 @@ bool is_dns_reachable(const pubnub_ipv4_address& dns_server, int timeout_ms = 20 0x00, 0x01 // Class IN }; - int sent = sendto(sock, (const char*)query, sizeof(query), 0, - (sockaddr*)&dns_addr, sizeof(dns_addr)); + int sent = sendto(sock, reinterpret_cast(query), sizeof(query), 0, + reinterpret_cast(&dns_addr), sizeof(dns_addr)); bool reachable = false; if (sent > 0) { unsigned char response[512]; - int received = recvfrom(sock, (char*)response, sizeof(response), 0, NULL, NULL); + int received = recvfrom(sock, reinterpret_cast(response), sizeof(response), 0, NULL, NULL); // We got a response (even if it's NXDOMAIN), server is reachable reachable = (received > 0); } @@ -165,7 +165,7 @@ bool test_dns_only_up_adapters() { GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, NULL, &buflen); - IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, @@ -180,8 +180,9 @@ bool test_dns_only_up_adapters() { for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { - if (ds->Address.lpSockaddr->sa_family == AF_INET) { - sockaddr_in* sin = (sockaddr_in*)ds->Address.lpSockaddr; + if (ds->Address.lpSockaddr && + ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); pubnub_ipv4_address addr; DWORD net_addr = sin->sin_addr.S_un.S_addr; DWORD host_addr = ntohl(net_addr); @@ -253,9 +254,12 @@ bool test_dns_no_invalid_adapter_types() { GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, NULL, &buflen); - IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); - GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, addrs, &buflen); + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { if (aa->IfType == IF_TYPE_SOFTWARE_LOOPBACK || aa->IfType == IF_TYPE_TUNNEL) { @@ -264,8 +268,9 @@ bool test_dns_no_invalid_adapter_types() { for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { - if (ds->Address.lpSockaddr->sa_family == AF_INET) { - sockaddr_in* sin = (sockaddr_in*)ds->Address.lpSockaddr; + if (ds->Address.lpSockaddr && + ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); pubnub_ipv4_address bad_addr; DWORD net_addr = sin->sin_addr.S_un.S_addr; DWORD host_addr = ntohl(net_addr); @@ -356,24 +361,64 @@ bool test_dns_ipv4_enabled_only() { GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, NULL, &buflen); - IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); - GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, addrs, &buflen); + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); + + // Collect DNS servers from adapters without IPv4 enabled + std::vector non_ipv4_adapter_dns; + int adapters_without_ipv4 = 0; for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { if (!(aa->Flags & IP_ADAPTER_IPV4_ENABLED)) { - TEST_LOG("Found adapter without IPv4: " << aa->AdapterName); + adapters_without_ipv4++; + TEST_LOG("Found adapter without IPv4 enabled: " << aa->AdapterName); - // This adapter's DNS servers should not be in our list + // Collect DNS servers from this adapter for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { - if (ds->Address.lpSockaddr->sa_family == AF_INET) { - TEST_LOG(" WARNING: Adapter has IPv4 DNS but IPv4 not enabled"); + if (ds->Address.lpSockaddr && + ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); + pubnub_ipv4_address addr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + addr.ipv4[0] = (host_addr >> 24) & 0xFF; + addr.ipv4[1] = (host_addr >> 16) & 0xFF; + addr.ipv4[2] = (host_addr >> 8) & 0xFF; + addr.ipv4[3] = host_addr & 0xFF; + + std::string dns_str = format_ipv4(addr); + non_ipv4_adapter_dns.push_back(dns_str); + TEST_LOG(" Found DNS on non-IPv4 adapter: " << dns_str); } } } } + TEST_LOG("Found " << adapters_without_ipv4 << " adapters without IPv4 enabled"); + TEST_LOG("Found " << non_ipv4_adapter_dns.size() << " DNS servers from non-IPv4 adapters"); + + // CRITICAL CHECK: None of these DNS servers should be in our returned list + for (const auto& bad_dns : non_ipv4_adapter_dns) { + for (int i = 0; i < our_count; i++) { + std::string our_dns = format_ipv4(our_servers[i]); + TEST_ASSERT(our_dns != bad_dns, + "CRITICAL: DNS from non-IPv4-enabled adapter found: " << bad_dns + << " - this indicates the IPv4-enabled check is not working!"); + } + } + + if (adapters_without_ipv4 > 0) { + TEST_LOG("✓ IPv4-enabled check working: " << adapters_without_ipv4 + << " adapters without IPv4 were properly filtered"); + } else { + TEST_LOG("INFO: All adapters have IPv4 enabled"); + } + free(addrs); TEST_PASS("test_dns_ipv4_enabled_only"); return true; @@ -391,7 +436,7 @@ bool test_dns_only_adapters_with_gateway() { GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, NULL, &buflen); - IP_ADAPTER_ADDRESSES* addrs = (IP_ADAPTER_ADDRESSES*)malloc(buflen); + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, @@ -424,7 +469,7 @@ bool test_dns_only_adapters_with_gateway() { ds != NULL; ds = ds->Next) { if (ds->Address.lpSockaddr && ds->Address.lpSockaddr->sa_family == AF_INET) { - sockaddr_in* sin = (sockaddr_in*)ds->Address.lpSockaddr; + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); pubnub_ipv4_address addr; DWORD net_addr = sin->sin_addr.S_un.S_addr; DWORD host_addr = ntohl(net_addr); From b7c6bd9728331960020db1add59c1e060a2c3c50 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Thu, 13 Nov 2025 14:18:05 +0100 Subject: [PATCH 03/20] Fix DNS enumeration on Windows by accepting adapters with valid IPs when gateway info is unavailable. --- windows/pubnub_dns_system_servers.c | 56 ++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index f652530a..b24b2254 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -59,7 +59,11 @@ static bool pubnub_dns_from_active_adapter(DWORD n_addr, IP_ADAPTER_ADDRESSES* a for (IP_ADAPTER_ADDRESSES* aa = addrs; aa; aa = aa->Next) { if (aa->OperStatus != IfOperStatusUp) continue; - /** Search for "online" adapters */ + /** Search for "online" adapters with gateway or valid unicast address. + * Note: FirstGatewayAddress can be NULL even for working adapters + * on some Windows configurations (WSL, Hyper-V, certain VPNs), so we + * also check if the adapter has a valid unicast IP address as a fallback. + */ bool has_gateway = false; for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { if (NULL != gw->Address.lpSockaddr) { @@ -67,7 +71,27 @@ static bool pubnub_dns_from_active_adapter(DWORD n_addr, IP_ADAPTER_ADDRESSES* a break; } } - if (!has_gateway) continue; + + /* Fallback: if no gateway, check if adapter has valid unicast address */ + bool has_valid_unicast = false; + if (!has_gateway) { + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + if (ua->Address.lpSockaddr && + ua->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; + DWORD addr = ntohl(sin->sin_addr.S_un.S_addr); + /* Valid if not 0.0.0.0, not loopback (127.x), not APIPA (169.254.x.x) */ + if (addr != 0 && + ((addr >> 24) & 0xFF) != 127 && + !(((addr >> 24) & 0xFF) == 169 && ((addr >> 16) & 0xFF) == 254)) { + has_valid_unicast = true; + break; + } + } + } + } + + if (!has_gateway && !has_valid_unicast) continue; for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds; ds = ds->Next) { if (!ds->Address.lpSockaddr || @@ -137,7 +161,11 @@ int pubnub_dns_get_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) continue; } - /** Search for "online" adapters */ + /** Search for "online" adapters with gateway or valid unicast address. + * Note: FirstGatewayAddress can be NULL even for working adapters + * on some Windows configurations (WSL, Hyper-V, certain VPNs), so we + * also check if the adapter has a valid unicast IP address as a fallback. + */ bool has_gateway = false; for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { if (NULL != gw->Address.lpSockaddr) { @@ -145,7 +173,27 @@ int pubnub_dns_get_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) break; } } - if (!has_gateway) continue; + + /* Fallback: if no gateway, check if adapter has valid unicast address */ + bool has_valid_unicast = false; + if (!has_gateway) { + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + if (ua->Address.lpSockaddr && + ua->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; + DWORD addr = ntohl(sin->sin_addr.S_un.S_addr); + /* Valid if not 0.0.0.0, not loopback (127.x), not APIPA (169.254.x.x) */ + if (addr != 0 && + ((addr >> 24) & 0xFF) != 127 && + !(((addr >> 24) & 0xFF) == 169 && ((addr >> 16) & 0xFF) == 254)) { + has_valid_unicast = true; + break; + } + } + } + } + + if (!has_gateway && !has_valid_unicast) continue; for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && j < n; ds = ds->Next) { if (!ds->Address.lpSockaddr || From 1eaa1c73f550c2baacdf186717c5a1f423cb4078 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Fri, 14 Nov 2025 14:42:03 +0200 Subject: [PATCH 04/20] refactor(dns-windows): replace `DnsQueryConfig` with `GetBestInterface` Refactor code to identify the currently used adapter and retrieve list of DNS servers that are reachable from it. --- windows/pubnub_dns_system_servers.c | 578 +++++++++++++++------- windows/tests/pubnub_dns_windows_test.cpp | 575 ++++++++++++++++++--- 2 files changed, 891 insertions(+), 262 deletions(-) diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index b24b2254..a5aaf116 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -8,16 +8,155 @@ #include #include #include -#include +#include #include +#include #pragma comment(lib, "IPHLPAPI.lib") -#pragma comment(lib, "dnsapi.lib") +#pragma comment(lib, "ws2_32.lib") #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) -/** Copy to the local address endianness from the network address endianness. */ +/* Maximum retry attempts for GetAdaptersAddresses buffer allocation */ +#define MAX_ADAPTER_ADDRESS_RETRIES 3 + +/* Public DNS servers to use for GetBestInterface routing test */ +#define PUBLIC_DNS_TEST_IP_1 "8.8.8.8" /* Google Public DNS */ +#define PUBLIC_DNS_TEST_IP_2 "1.1.1.1" /* Cloudflare DNS */ + +/* Timeout for DNS server connectivity check (milliseconds) */ +#define DNS_CONNECTIVITY_TIMEOUT_MS 2000 + +/** Helper structure to track adapters with their metrics for sorting */ +struct adapter_with_metric { + IP_ADAPTER_ADDRESSES* adapter; + ULONG metric; + bool is_best_route; +}; + +/** Check whether provided address is valid (not loopback, not APIPA, not zero). + * + * @param host_addr Address in host byte order. + * @return @c true if proper non-internal address has been provided, + * @c false otherwise. + */ +static bool pubnub_ipv4_address_valid(DWORD host_addr) +{ + if (host_addr == 0 || + ((host_addr >> 24) & 0xFF) == 127 || + (((host_addr >> 24) & 0xFF) == 169 && ((host_addr >> 16) & 0xFF) == 254)) { + return false; + } + + return true; +} + +/** Comparison function for qsort to sort adapters by priority. + * Priority order: + * 1. Adapters on the best route (lowest priority number) + * 2. Lower interface metric + * + * @param a Left-hand @c adapter_with_metric which will be used in comparison. + * @param b Right-hand @c adapter_with_metric which will be used in comparison. + * @return @b -1 when the left-hand adapter has higher priority than the + * right-hand; @b 0 when both adapters have the same priority; @b 1 when the + * right-hand adapter has higher priority than the left-hand. + */ +static int compare_adapters_by_priority(const void* a, const void* b) +{ + const struct adapter_with_metric* aa = (const struct adapter_with_metric*)a; + const struct adapter_with_metric* bb = (const struct adapter_with_metric*)b; + + /* Best route adapters come first */ + if (aa->is_best_route && !bb->is_best_route) return -1; + if (!aa->is_best_route && bb->is_best_route) return 1; + + /* Then sort by metric (lower is better) */ + if (aa->metric < bb->metric) return -1; + if (aa->metric > bb->metric) return 1; + + return 0; +} + +/** Test if a DNS server is reachable by attempting a TCP connection to port 53. + * This is used to detect stale VPN connections where the adapter appears UP + * but the DNS server is actually unreachable. + * + * @param dns_addr_net DNS server address in network byte order. + * @return @c true if DNS server is reachable, @c false otherwise. + */ +static bool pubnub_dns_server_reachable(DWORD dns_addr_net) +{ + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == INVALID_SOCKET) return false; + + /* Set socket to non-blocking mode for timeout control */ + u_long mode = 1; + if (ioctlsocket(sock, FIONBIO, &mode) != 0) { + closesocket(sock); + return false; + } + + struct sockaddr_in dns_addr; + memset(&dns_addr, 0, sizeof(dns_addr)); + dns_addr.sin_family = AF_INET; + dns_addr.sin_port = htons(53); + dns_addr.sin_addr.s_addr = dns_addr_net; + + int result = connect(sock, (struct sockaddr*)&dns_addr, sizeof(dns_addr)); + + if (result == SOCKET_ERROR) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + closesocket(sock); + return false; + } + + /* Wait for socket's in-progress connection complete with timeout. */ + struct timeval timeout; + timeout.tv_sec = DNS_CONNECTIVITY_TIMEOUT_MS / 1000; + timeout.tv_usec = (DNS_CONNECTIVITY_TIMEOUT_MS % 1000) * 1000; + + fd_set writefds, exceptfds; + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(sock, &writefds); + FD_SET(sock, &exceptfds); + + result = select(0, NULL, &writefds, &exceptfds, &timeout); + + if (result == SOCKET_ERROR || result == 0 || + FD_ISSET(sock, &exceptfds) || !FD_ISSET(sock, &writefds)) { + closesocket(sock); + return false; + } + + /* Ensure that connect didn't end up with an error. */ + int so_error = 0; + int len = sizeof(so_error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&so_error, &len) != 0 || + so_error != 0) { + closesocket(sock); + return false; + } + } + + closesocket(sock); + return true; +} + +/** Copy IPv4 address from network byte order DWORD to array, checking for + * duplicates. + * + * @param array Pointer to the array of @c pubnub_ipv4_address structures with + * previously discovered DNS server addresses. + * @param count Current number of entries in @b array. + * @param n_addr DNS server address in network byte order. + * @param out Target entry in @b array where @b n_addr should be copied (if not + * present yet in @b array). + * @return @c true if the provided address in network byte order was acceptable + * and copied to the list of discovered DNS server addresses, @c false otherwise. + */ static bool pubnub_copy_ipv4_bytes_from_be_dword( const struct pubnub_ipv4_address* array, const size_t count, @@ -32,10 +171,10 @@ static bool pubnub_copy_ipv4_bytes_from_be_dword( temp_ip[2] = (unsigned char)((host_addr >> 8) & 0xFF); temp_ip[3] = (unsigned char)(host_addr & 0xFF); - /* Filter out loopback and APIPA addresses from `DnsQueryConfig`. */ - if (127 == temp_ip[0] || (169 == temp_ip[0] && 254 == temp_ip[1])) - return false; + /* Filter out invalid addresses */ + if (!pubnub_ipv4_address_valid(host_addr)) return false; + /* Check for duplicates */ for (size_t i = 0; i < count; i++) { if (memcmp(array[i].ipv4, temp_ip, 4) == 0) return false; } @@ -48,225 +187,290 @@ static bool pubnub_copy_ipv4_bytes_from_be_dword( return true; } -/** Check whether the DNS address is retrieved for the active network adapter - or not. +/** Get adapter addresses with retry logic to handle race conditions. + * + * @param aa Pointer to the list of @c IP_ADAPTER_ADDRESSES structs. + * @return @c true if @p aa has been populated with active adapter, + * @c false otherwise. */ -static bool pubnub_dns_from_active_adapter(DWORD n_addr, IP_ADAPTER_ADDRESSES* addrs) +static bool pubnub_adapter_addresses_get(IP_ADAPTER_ADDRESSES** aa) { - if (!addrs) return true; - bool found = false; - - for (IP_ADAPTER_ADDRESSES* aa = addrs; aa; aa = aa->Next) { - if (aa->OperStatus != IfOperStatusUp) continue; - - /** Search for "online" adapters with gateway or valid unicast address. - * Note: FirstGatewayAddress can be NULL even for working adapters - * on some Windows configurations (WSL, Hyper-V, certain VPNs), so we - * also check if the adapter has a valid unicast IP address as a fallback. - */ - bool has_gateway = false; - for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { - if (NULL != gw->Address.lpSockaddr) { - has_gateway = true; - break; - } - } - - /* Fallback: if no gateway, check if adapter has valid unicast address */ - bool has_valid_unicast = false; - if (!has_gateway) { - for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { - if (ua->Address.lpSockaddr && - ua->Address.lpSockaddr->sa_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; - DWORD addr = ntohl(sin->sin_addr.S_un.S_addr); - /* Valid if not 0.0.0.0, not loopback (127.x), not APIPA (169.254.x.x) */ - if (addr != 0 && - ((addr >> 24) & 0xFF) != 127 && - !(((addr >> 24) & 0xFF) == 169 && ((addr >> 16) & 0xFF) == 254)) { - has_valid_unicast = true; - break; - } - } - } + if (NULL == aa) return false; + + /* Free existing allocation if present */ + if (*aa) { + FREE(*aa); + *aa = NULL; + } + + ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS; + ULONG buflen = 15000; /* Start with reasonable size to avoid extra call */ + DWORD ret; + int retries = 0; + + /* Retry loop to handle race conditions where adapters change between calls */ + do { + IP_ADAPTER_ADDRESSES* buffer = (IP_ADAPTER_ADDRESSES*)MALLOC(buflen); + if (NULL == buffer) { + PUBNUB_LOG_ERROR("OOM allocating %lu bytes for GetAdaptersAddresses\n", + (unsigned long)buflen); + return false; } - - if (!has_gateway && !has_valid_unicast) continue; - for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds; ds = ds->Next) { - if (!ds->Address.lpSockaddr || - ds->Address.lpSockaddr->sa_family != AF_INET) { - continue; - } + ret = GetAdaptersAddresses(AF_INET, flags, NULL, buffer, &buflen); - const struct sockaddr_in* sin = (const struct sockaddr_in*)ds->Address.lpSockaddr; - DWORD net_addr = sin->sin_addr.S_un.S_addr; - if (net_addr == 0) continue; + if (ret == NO_ERROR) { + *aa = buffer; + return true; + } - if (net_addr == n_addr) { - found = true; - break; + FREE(buffer); + + if (ret == ERROR_BUFFER_OVERFLOW) { + if (++retries >= MAX_ADAPTER_ADDRESS_RETRIES) { + PUBNUB_LOG_ERROR("GetAdaptersAddresses buffer overflow after " + "%d retries\n", retries); + return false; } + /* `GetAdaptersAddresses` call failed but updated `buflen` value. */ + continue; } - if (found) break; - } - return found; + PUBNUB_LOG_ERROR("GetAdaptersAddresses failed: %lu\n", (unsigned long)ret); + return false; + + } while (ret == ERROR_BUFFER_OVERFLOW && retries < MAX_ADAPTER_ADDRESS_RETRIES); + + return false; } -/** Retrieve DNS servers from "online" network adapters. */ -int pubnub_dns_get_via_adapters(struct pubnub_ipv4_address* o_ipv4, size_t n) +/** Check if an adapter is suitable for DNS queries. + * An adapter is suitable if it is: + * - Operationally UP, AND + * - Not loopback or tunnel, AND + * - IPv4 enabled, AND + * - Has a valid gateway OR physical adapters with valid unicast address. + * + * @param aa Pointer to the adapter structure which should be checked. + * @return @c true if provided adapter fulfill requirements ("alive"), + * @c false otherwise. + */ +static bool pubnub_adapter_is_suitable(IP_ADAPTER_ADDRESSES* aa) { - IP_ADAPTER_ADDRESSES* addrs; - unsigned char temp_ip[4]; - DWORD net_addr; - unsigned j = 0; - ULONG buflen; - DWORD ret; - - /* Get required buffer size */ - ret = GetAdaptersAddresses( - AF_INET, - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, NULL, &buflen); + if (NULL == aa) return false; - if (ret != ERROR_BUFFER_OVERFLOW || buflen == 0) { - PUBNUB_LOG_DEBUG("GetAdaptersAddresses can't retrieve any adapters: %lu\n", (unsigned long)ret); - return (int)j; + if (aa->OperStatus != IfOperStatusUp || + aa->IfType == IF_TYPE_SOFTWARE_LOOPBACK || + aa->IfType == IF_TYPE_TUNNEL || + !(aa->Flags & IP_ADAPTER_IPV4_ENABLED)) { + return false; } - addrs = (IP_ADAPTER_ADDRESSES*)MALLOC(buflen); - if (!addrs) { - PUBNUB_LOG_ERROR("OOM allocating %lu for GetAdaptersAddresses\n", (unsigned long)buflen); - return (int)j; + /* Check for valid gateway. */ + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { + if (gw->Address.lpSockaddr && gw->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)gw->Address.lpSockaddr; + if (pubnub_ipv4_address_valid(ntohl(sin->sin_addr.S_un.S_addr))) + return true; + } } - /* Get adapter information */ - ret = GetAdaptersAddresses( - AF_INET, - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, addrs, &buflen); - if (ret != NO_ERROR) { - PUBNUB_LOG_ERROR("GetAdaptersAddresses failed: %lu\n", (unsigned long)ret); - FREE(addrs); - return (int)j; + /* Fallback: Accept physical Ethernet or WiFi adapters with valid unicast address + * This handles WSL2, Hyper-V, Docker scenarios where gateway info may be missing + * but the adapter is still functional for local DNS. + */ + if (aa->IfType == IF_TYPE_ETHERNET_CSMACD || + aa->IfType == IF_TYPE_IEEE80211 || + aa->IfType == 71) { + + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + if (ua->Address.lpSockaddr && ua->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; + if (pubnub_ipv4_address_valid(ntohl(sin->sin_addr.S_un.S_addr))) + return true; + } + } } - /* Enumerate adapters and collect unique DNS servers */ - for (IP_ADAPTER_ADDRESSES* aa = addrs; aa && j < n; aa = aa->Next) { - if (aa->OperStatus != IfOperStatusUp || - aa->IfType == IF_TYPE_SOFTWARE_LOOPBACK || - aa->IfType == IF_TYPE_TUNNEL || - !(aa->Flags & IP_ADAPTER_IPV4_ENABLED)) { - continue; + return false; +} + +/** Get the best interface for reaching the public Internet (for DNS queries). + * Detect the index of the adapter that is used by Windows for request routing. + * + * @return Non-zero index of the adapter that can route to the public Internet, + * zero otherwise. + */ +static DWORD pubnub_get_best_interface_index(void) +{ + DWORD best_if_index = 0; + struct in_addr dest_addr; + + if (inet_pton(AF_INET, PUBLIC_DNS_TEST_IP_1, &dest_addr) == 1) { + if (GetBestInterface(dest_addr.S_un.S_addr, &best_if_index) == NO_ERROR && best_if_index != 0) { + return best_if_index; } + } - /** Search for "online" adapters with gateway or valid unicast address. - * Note: FirstGatewayAddress can be NULL even for working adapters - * on some Windows configurations (WSL, Hyper-V, certain VPNs), so we - * also check if the adapter has a valid unicast IP address as a fallback. - */ - bool has_gateway = false; - for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { - if (NULL != gw->Address.lpSockaddr) { - has_gateway = true; - break; - } + if (inet_pton(AF_INET, PUBLIC_DNS_TEST_IP_2, &dest_addr) == 1) { + if (GetBestInterface(dest_addr.S_un.S_addr, &best_if_index) == NO_ERROR && best_if_index != 0) { + return best_if_index; } - - /* Fallback: if no gateway, check if adapter has valid unicast address */ - bool has_valid_unicast = false; - if (!has_gateway) { - for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { - if (ua->Address.lpSockaddr && - ua->Address.lpSockaddr->sa_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; - DWORD addr = ntohl(sin->sin_addr.S_un.S_addr); - /* Valid if not 0.0.0.0, not loopback (127.x), not APIPA (169.254.x.x) */ - if (addr != 0 && - ((addr >> 24) & 0xFF) != 127 && - !(((addr >> 24) & 0xFF) == 169 && ((addr >> 16) & 0xFF) == 254)) { - has_valid_unicast = true; - break; - } + } + + return 0; +} + +/** Check if DNS server is reachable from the adapter. +* A DNS server is reachable if: +* 1. It's on the same subnet as one of the adapter's unicast addresses, OR +* 2. The adapter has a valid gateway (can route to it) +* +* @param dns_addr_net DNS address in network byte order. +* @param aa Pointer to the adapter that should be tested for subnet unicast +* match with provided DNS server address. +* @return @c true if the adapter can route the request to the provided DNS +* server address, @c false otherwise. +*/ +static bool pubnub_dns_reachable_from_adapter(DWORD dns_addr_net, IP_ADAPTER_ADDRESSES* aa) +{ + DWORD dns_addr = ntohl(dns_addr_net); + + /* Check if DNS is on the same subnet as any unicast address */ + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; + ua != NULL; + ua = ua->Next) { + if (ua->Address.lpSockaddr && ua->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; + DWORD if_addr = ntohl(sin->sin_addr.S_un.S_addr); + + /* Get subnet mask from prefix length */ + ULONG prefix_len = ua->OnLinkPrefixLength; + if (prefix_len > 0 && prefix_len <= 32) { + DWORD mask = (prefix_len == 32) ? 0xFFFFFFFF : ~((1UL << (32 - prefix_len)) - 1); + + /* Check if DNS is on same subnet */ + if ((dns_addr & mask) == (if_addr & mask)) { + return true; } } } - - if (!has_gateway && !has_valid_unicast) continue; - - for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && j < n; ds = ds->Next) { - if (!ds->Address.lpSockaddr || - ds->Address.lpSockaddr->sa_family != AF_INET) { - continue; - } - - const struct sockaddr_in* sin = (const struct sockaddr_in*)ds->Address.lpSockaddr; - net_addr = sin->sin_addr.S_un.S_addr; - if (net_addr == 0) continue; + } - if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, j, net_addr, o_ipv4[j].ipv4)) - ++j; + /* Check if adapter has a valid gateway (can route off-subnet) */ + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { + if (gw->Address.lpSockaddr && gw->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)gw->Address.lpSockaddr; + if (pubnub_ipv4_address_valid(ntohl(sin->sin_addr.S_un.S_addr))) + return true; } } - FREE(addrs); - return (int)j; + return false; } -int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size_t n) +/** Retrieve DNS servers from network adapters, prioritized by routing metrics. +* + * @param o_ipv4 Pointer to the array of @c pubnub_ipv4_address structures with + * previously discovered DNS server addresses. + * @param n Maximum number of discovered DNS server addresses. + * @return Number of discovered system DNS servers, @c zero otherwise. + */ +int pubnub_dns_read_system_servers_ipv4( + struct pubnub_ipv4_address* o_ipv4, + size_t n) { if (!o_ipv4 || n == 0) return 0; - DWORD buflen = 0; - DNS_STATUS status = DnsQueryConfig( - DnsConfigDnsServerList, - 0, NULL, NULL, NULL, &buflen); - - if (status == ERROR_SUCCESS && buflen >= sizeof(IP4_ARRAY)) { - unsigned j = 0; - PIP4_ARRAY ip4_list = (PIP4_ARRAY)LocalAlloc(LMEM_FIXED, buflen); - if (ip4_list) { - status = DnsQueryConfig( - DnsConfigDnsServerList, - 0, NULL, NULL, ip4_list, &buflen); - - if (status == ERROR_SUCCESS) { - IP_ADAPTER_ADDRESSES* active_adapters = NULL; - ULONG adapter_buflen = 0; - - if (GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, NULL, &adapter_buflen) == ERROR_BUFFER_OVERFLOW) { - active_adapters = (IP_ADAPTER_ADDRESSES*)MALLOC(adapter_buflen); - if (active_adapters) { - DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, active_adapters, &adapter_buflen); - if (NO_ERROR != ret) { - PUBNUB_LOG_WARNING("GetAdaptersAddresses failed: %lu - can't validate DNS servers\n", - (unsigned long)ret); - FREE(active_adapters); - active_adapters = NULL; - } - } - } + IP_ADAPTER_ADDRESSES* addrs = NULL; + if (!pubnub_adapter_addresses_get(&addrs)) return 0; - for (DWORD i = 0; i < ip4_list->AddrCount && j < n; ++i) { - if (ip4_list->AddrArray[i] == 0 || - !pubnub_dns_from_active_adapter(ip4_list->AddrArray[i], active_adapters)) { - continue; - } + /* Get the best interface index for routing to public Internet. */ + DWORD best_if_index = pubnub_get_best_interface_index(); - if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, j, ip4_list->AddrArray[i], o_ipv4[j].ipv4)) - ++j; - } - if (active_adapters) FREE(active_adapters); - } + /* Count suitable adapters. */ + size_t adapter_count = 0; + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (pubnub_adapter_is_suitable(aa)) adapter_count++; + } + + if (adapter_count == 0) { + FREE(addrs); + return 0; + } + + /* Build array of adapters with their metrics for sorting. */ + struct adapter_with_metric* adapter_list = + (struct adapter_with_metric*)MALLOC(adapter_count * sizeof(struct adapter_with_metric)); - LocalFree(ip4_list); + if (!adapter_list) { + FREE(addrs); + return 0; + } + + size_t idx = 0; + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (pubnub_adapter_is_suitable(aa)) { + adapter_list[idx].adapter = aa; + adapter_list[idx].metric = aa->Ipv4Metric; + adapter_list[idx].is_best_route = + (best_if_index != 0 && aa->IfIndex == best_if_index); + idx++; } + } - if (j > 0) return (int)j; + /* Sort adapters by priority. */ + qsort(adapter_list, adapter_count, sizeof(struct adapter_with_metric), + compare_adapters_by_priority); + + /* Extract DNS servers from adapters in priority order. */ + unsigned j = 0; + for (size_t i = 0; i < adapter_count && j < n; i++) { + IP_ADAPTER_ADDRESSES* aa = adapter_list[i].adapter; + + PUBNUB_LOG_TRACE("Processing adapter: Index=%lu, Metric=%lu, IsBestRoute=%d\n", + (unsigned long)aa->IfIndex, + (unsigned long)adapter_list[i].metric, + adapter_list[i].is_best_route); + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && j < n; ds = ds->Next) { + if (!ds->Address.lpSockaddr || ds->Address.lpSockaddr->sa_family != AF_INET) + continue; + + const struct sockaddr_in* sin = (const struct sockaddr_in*)ds->Address.lpSockaddr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + if (net_addr == 0) continue; + + /* Verifying adapter can be used to reach DNS server. */ + if (!pubnub_dns_reachable_from_adapter(net_addr, aa)) { + PUBNUB_LOG_DEBUG("Skipping DNS server - not reachable from " + "adapter\n"); + continue; + } + + /* Verify DNS server is actually reachable via TCP connection test. + * This detects stale VPN connections where the adapter appears UP but + * the DNS server is unreachable. This is critical because GetBestInterface + * may return a stale VPN adapter if Windows routing table hasn't been updated. + * We test ALL adapters to catch this scenario. + */ + if (!pubnub_dns_server_reachable(net_addr)) { + PUBNUB_LOG_WARNING("Skipping DNS server - TCP connection test " + "failed (possibly stale VPN)\n"); + continue; + } + + /* Add to output if not duplicate */ + if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, j, net_addr, o_ipv4[j].ipv4)) { + PUBNUB_LOG_TRACE("Added DNS server from adapter index %lu\n", + (unsigned long)aa->IfIndex); + ++j; + } + } } - return pubnub_dns_get_via_adapters(o_ipv4, n); -} \ No newline at end of file + FREE(adapter_list); + FREE(addrs); + + return (int)j; +} diff --git a/windows/tests/pubnub_dns_windows_test.cpp b/windows/tests/pubnub_dns_windows_test.cpp index 88c73879..98868759 100644 --- a/windows/tests/pubnub_dns_windows_test.cpp +++ b/windows/tests/pubnub_dns_windows_test.cpp @@ -55,51 +55,82 @@ std::string format_ipv4(const pubnub_ipv4_address& addr) { return oss.str(); } -// Helper to check if DNS server is reachable +// Helper to check if DNS server is reachable via TCP connection +// This matches the implementation in pubnub_dns_system_servers.c bool is_dns_reachable(const pubnub_ipv4_address& dns_server, int timeout_ms = 2000) { - SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { return false; } + // Set socket to non-blocking mode for timeout control + u_long mode = 1; + if (ioctlsocket(sock, FIONBIO, &mode) != 0) { + closesocket(sock); + return false; + } + sockaddr_in dns_addr = {}; dns_addr.sin_family = AF_INET; dns_addr.sin_port = htons(53); memcpy(&dns_addr.sin_addr.s_addr, dns_server.ipv4, 4); - // Set socket timeout - DWORD timeout = timeout_ms; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof(timeout)); - - // Simple DNS query for "test.local" - type A - unsigned char query[] = { - 0x00, 0x01, // Transaction ID - 0x01, 0x00, // Flags: standard query - 0x00, 0x01, // Questions: 1 - 0x00, 0x00, // Answer RRs: 0 - 0x00, 0x00, // Authority RRs: 0 - 0x00, 0x00, // Additional RRs: 0 - // Query: test.local - 0x04, 't', 'e', 's', 't', - 0x05, 'l', 'o', 'c', 'a', 'l', - 0x00, - 0x00, 0x01, // Type A - 0x00, 0x01 // Class IN - }; + // Attempt connection (will return immediately with WSAEWOULDBLOCK) + int result = connect(sock, reinterpret_cast(&dns_addr), sizeof(dns_addr)); + + if (result == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { + // Immediate failure (connection refused, no route, etc.) + closesocket(sock); + return false; + } + + // Connection in progress - wait for completion with timeout + fd_set writefds; + fd_set exceptfds; + timeval timeout; + + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(sock, &writefds); + FD_SET(sock, &exceptfds); + + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + result = select(0, NULL, &writefds, &exceptfds, &timeout); + + if (result == SOCKET_ERROR || result == 0) { + // select failed or timeout + closesocket(sock); + return false; + } + + if (FD_ISSET(sock, &exceptfds)) { + // Connection failed + closesocket(sock); + return false; + } - int sent = sendto(sock, reinterpret_cast(query), sizeof(query), 0, - reinterpret_cast(&dns_addr), sizeof(dns_addr)); + if (!FD_ISSET(sock, &writefds)) { + // Should not happen, but handle it + closesocket(sock); + return false; + } - bool reachable = false; - if (sent > 0) { - unsigned char response[512]; - int received = recvfrom(sock, reinterpret_cast(response), sizeof(response), 0, NULL, NULL); - // We got a response (even if it's NXDOMAIN), server is reachable - reachable = (received > 0); + // Verify connection succeeded (check SO_ERROR) + int so_error = 0; + int len = sizeof(so_error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), &len) != 0 || so_error != 0) { + closesocket(sock); + return false; + } } + // Connection successful closesocket(sock); - return reachable; + return true; } // Test 1: Basic DNS server enumeration @@ -295,9 +326,10 @@ bool test_dns_no_invalid_adapter_types() { return true; } -// Test 6: DNS server reachability (optional - may require network) +// Test 6: DNS server reachability (CRITICAL TEST) bool test_dns_reachability() { - TEST_LOG("Test 6: Check if returned DNS servers are reachable"); + TEST_LOG("Test 6: Verify all returned DNS servers are reachable"); + TEST_LOG("This is the PRIMARY test - we should NOT return unreachable DNS servers"); pubnub_ipv4_address servers[10]; int count = pubnub_dns_read_system_servers_ipv4(servers, 10); @@ -305,25 +337,40 @@ bool test_dns_reachability() { TEST_ASSERT(count > 0, "Should have at least one DNS server"); int reachable_count = 0; + std::vector unreachable_servers; + for (int i = 0; i < count; i++) { std::string ip = format_ipv4(servers[i]); - TEST_LOG("Testing reachability of " << ip); + TEST_LOG("Testing reachability of " << ip << "..."); bool reachable = is_dns_reachable(servers[i], 3000); if (reachable) { - TEST_LOG(" [REACHABLE] " << ip); + TEST_LOG(" ✓ [REACHABLE] " << ip); reachable_count++; } else { - TEST_LOG(" [TIMEOUT] " << ip << " (may be behind firewall)"); + TEST_LOG(" ✗ [UNREACHABLE] " << ip); + unreachable_servers.push_back(ip); } } - // At least one should be reachable in normal conditions - // But we make this a warning, not a failure, as corporate firewalls may block - if (reachable_count == 0) { - TEST_LOG("WARNING: No DNS servers reachable - this may indicate network issues"); + TEST_LOG("Summary: " << reachable_count << "/" << count << " DNS servers reachable"); + + // CRITICAL: ALL returned DNS servers MUST be reachable + // This is the core requirement - we should filter out unreachable servers + if (!unreachable_servers.empty()) { + TEST_LOG("FAILED: Found " << unreachable_servers.size() << " unreachable DNS server(s):"); + for (const auto& ip : unreachable_servers) { + TEST_LOG(" - " << ip); + } + TEST_ASSERT(false, + "CRITICAL: Returned " << unreachable_servers.size() << " unreachable DNS server(s). " + "Our implementation should only return DNS servers from active, routable adapters."); } + TEST_ASSERT(reachable_count == count, + "All " << count << " DNS servers should be reachable"); + + TEST_LOG("✓ All DNS servers are reachable!"); TEST_PASS("test_dns_reachability"); return true; } @@ -424,51 +471,78 @@ bool test_dns_ipv4_enabled_only() { return true; } -// Test 9: Verify no DNS from adapters without gateway (CRITICAL TEST) +// Test 9: Verify DNS from adapters with valid gateway or unicast (UPDATED TEST) bool test_dns_only_adapters_with_gateway() { - TEST_LOG("Test 9: Verify DNS servers only from adapters with gateway"); - TEST_LOG("This test validates the fix for disconnected VPN adapters"); + TEST_LOG("Test 9: Verify DNS servers come from properly routable adapters"); + TEST_LOG("NOTE: Our implementation accepts adapters with EITHER valid gateway OR valid unicast"); + TEST_LOG("This test verifies we filter based on actual reachability, not just gateway presence"); pubnub_ipv4_address our_servers[16]; int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + // Use GAA_FLAG_INCLUDE_GATEWAYS to get actual gateway information ULONG buflen = 0; - GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, NULL, NULL, &buflen); IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); - DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, NULL, addrs, &buflen); TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); - // Find adapters that are UP but have NO gateway - std::vector no_gateway_dns; - int adapters_without_gateway = 0; + // Helper function to check if address is valid (not loopback, not APIPA) + auto is_valid_ip = [](DWORD host_addr) -> bool { + return host_addr != 0 && + ((host_addr >> 24) & 0xFF) != 127 && + !(((host_addr >> 24) & 0xFF) == 169 && ((host_addr >> 16) & 0xFF) == 254); + }; + + // Find adapters that are UP but have NEITHER valid gateway NOR valid unicast + std::vector invalid_adapter_dns; + int adapters_without_valid_addresses = 0; for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { - if (aa->OperStatus == IfOperStatusUp) { - // Check if adapter has gateway - bool has_gateway = false; - for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; - gw != NULL; gw = gw->Next) { - if (gw->Address.lpSockaddr != NULL) { - has_gateway = true; - break; + if (aa->OperStatus == IfOperStatusUp && + aa->IfType != IF_TYPE_SOFTWARE_LOOPBACK && + aa->IfType != IF_TYPE_TUNNEL && + (aa->Flags & IP_ADAPTER_IPV4_ENABLED)) { + + // Check if adapter has valid gateway + bool has_valid_gateway = false; + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { + if (gw->Address.lpSockaddr && gw->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(gw->Address.lpSockaddr); + DWORD gw_addr = ntohl(sin->sin_addr.S_un.S_addr); + if (is_valid_ip(gw_addr)) { + has_valid_gateway = true; + break; + } } } - if (!has_gateway) { - adapters_without_gateway++; - TEST_LOG("Found UP adapter WITHOUT gateway: " << aa->AdapterName - << " (likely disconnected VPN or disabled connection)"); + // Check if adapter has valid unicast address + bool has_valid_unicast = false; + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + if (ua->Address.lpSockaddr && ua->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ua->Address.lpSockaddr); + DWORD ua_addr = ntohl(sin->sin_addr.S_un.S_addr); + if (is_valid_ip(ua_addr)) { + has_valid_unicast = true; + break; + } + } + } + + // If adapter has NEITHER valid gateway NOR valid unicast, it shouldn't contribute DNS + if (!has_valid_gateway && !has_valid_unicast) { + adapters_without_valid_addresses++; + TEST_LOG("Found UP adapter with NO valid addresses: " << aa->AdapterName); // Collect DNS servers from this adapter - for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; - ds != NULL; ds = ds->Next) { - if (ds->Address.lpSockaddr && - ds->Address.lpSockaddr->sa_family == AF_INET) { + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr && ds->Address.lpSockaddr->sa_family == AF_INET) { sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); pubnub_ipv4_address addr; DWORD net_addr = sin->sin_addr.S_un.S_addr; @@ -479,32 +553,32 @@ bool test_dns_only_adapters_with_gateway() { addr.ipv4[3] = host_addr & 0xFF; std::string dns_str = format_ipv4(addr); - no_gateway_dns.push_back(dns_str); - TEST_LOG(" Found DNS on no-gateway adapter: " << dns_str); + invalid_adapter_dns.push_back(dns_str); + TEST_LOG(" Found DNS on invalid adapter: " << dns_str); } } } } } - TEST_LOG("Found " << adapters_without_gateway << " adapters without gateway"); - TEST_LOG("Found " << no_gateway_dns.size() << " DNS servers from no-gateway adapters"); + TEST_LOG("Found " << adapters_without_valid_addresses << " adapters without valid gateway or unicast"); + TEST_LOG("Found " << invalid_adapter_dns.size() << " DNS servers from invalid adapters"); // CRITICAL CHECK: None of these DNS servers should be in our returned list - for (const auto& bad_dns : no_gateway_dns) { + for (const auto& bad_dns : invalid_adapter_dns) { for (int i = 0; i < our_count; i++) { std::string our_dns = format_ipv4(our_servers[i]); TEST_ASSERT(our_dns != bad_dns, - "CRITICAL: DNS from no-gateway adapter found: " << bad_dns - << " - this indicates the gateway check is not working!"); + "CRITICAL: DNS from invalid adapter found: " << bad_dns + << " - adapter has neither valid gateway nor valid unicast!"); } } - if (adapters_without_gateway > 0) { - TEST_LOG("✓ Gateway check working: " << adapters_without_gateway - << " adapters without gateway were properly filtered"); + if (adapters_without_valid_addresses > 0) { + TEST_LOG("✓ Adapter filtering working: " << adapters_without_valid_addresses + << " adapters without valid addresses were properly filtered"); } else { - TEST_LOG("INFO: No adapters without gateway found (all connections have valid routes)"); + TEST_LOG("INFO: All adapters have valid addresses (normal case)"); } free(addrs); @@ -536,6 +610,346 @@ bool test_dns_no_loopback_or_apipa() { return true; } +// Test 11: Verify metric-based prioritization (NEW) +bool test_dns_metric_prioritization() { + TEST_LOG("Test 11: Verify DNS servers are returned in metric priority order"); + TEST_LOG("Lower metric adapters should have their DNS servers returned first"); + + pubnub_ipv4_address our_servers[16]; + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + if (our_count == 0) { + TEST_LOG("INFO: No DNS servers found (this is OK for testing)"); + TEST_PASS("test_dns_metric_prioritization"); + return true; + } + + // Get all adapters with metrics + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); + + // Find which adapter each DNS belongs to and its metric + std::vector dns_adapter_metrics; + + for (int i = 0; i < our_count; i++) { + std::string our_dns = format_ipv4(our_servers[i]); + ULONG found_metric = 0xFFFFFFFF; // Max value if not found + + // Find this DNS in the adapters + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (aa->OperStatus != IfOperStatusUp) continue; + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr && ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); + pubnub_ipv4_address addr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + addr.ipv4[0] = (host_addr >> 24) & 0xFF; + addr.ipv4[1] = (host_addr >> 16) & 0xFF; + addr.ipv4[2] = (host_addr >> 8) & 0xFF; + addr.ipv4[3] = host_addr & 0xFF; + + if (format_ipv4(addr) == our_dns) { + found_metric = aa->Ipv4Metric; + TEST_LOG("DNS " << our_dns << " from adapter metric " << found_metric); + break; + } + } + } + if (found_metric != 0xFFFFFFFF) break; + } + + dns_adapter_metrics.push_back(found_metric); + } + + // Verify metrics are in non-decreasing order (lower or equal metrics come first) + bool metrics_sorted = true; + for (size_t i = 1; i < dns_adapter_metrics.size(); i++) { + if (dns_adapter_metrics[i] < dns_adapter_metrics[i-1]) { + TEST_LOG("WARNING: DNS server " << i << " has lower metric (" << dns_adapter_metrics[i] + << ") than previous (" << dns_adapter_metrics[i-1] << ")"); + metrics_sorted = false; + } + } + + if (metrics_sorted) { + TEST_LOG("✓ DNS servers are correctly prioritized by adapter metric"); + } else { + TEST_LOG("NOTE: Metrics not strictly sorted - this is OK if GetBestInterface prioritized differently"); + } + + free(addrs); + TEST_PASS("test_dns_metric_prioritization"); + return true; +} + +// Test 12: Verify GetBestInterface awareness (NEW) +bool test_dns_best_interface_prioritization() { + TEST_LOG("Test 12: Verify DNS servers from best route adapter are prioritized"); + TEST_LOG("This tests that GetBestInterface logic is working"); + + pubnub_ipv4_address our_servers[16]; + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + if (our_count == 0) { + TEST_LOG("INFO: No DNS servers found"); + TEST_PASS("test_dns_best_interface_prioritization"); + return true; + } + + // Get best interface for reaching public DNS (8.8.8.8) + DWORD best_if_index = 0; + struct in_addr dest; + if (inet_pton(AF_INET, "8.8.8.8", &dest) == 1) { + GetBestInterface(dest.S_un.S_addr, &best_if_index); + } + + if (best_if_index == 0) { + TEST_LOG("INFO: Could not determine best interface (no Internet connection?)"); + TEST_PASS("test_dns_best_interface_prioritization"); + return true; + } + + TEST_LOG("Best interface index for reaching 8.8.8.8: " << best_if_index); + + // Get all adapters + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); + + // Find DNS servers from the best interface adapter + std::vector best_if_dns; + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (aa->IfIndex == best_if_index) { + TEST_LOG("Found best interface adapter: " << aa->AdapterName); + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr && ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); + pubnub_ipv4_address addr; + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + addr.ipv4[0] = (host_addr >> 24) & 0xFF; + addr.ipv4[1] = (host_addr >> 16) & 0xFF; + addr.ipv4[2] = (host_addr >> 8) & 0xFF; + addr.ipv4[3] = host_addr & 0xFF; + + // Validate not loopback/APIPA + if (host_addr != 0 && + ((host_addr >> 24) & 0xFF) != 127 && + !(((host_addr >> 24) & 0xFF) == 169 && ((host_addr >> 16) & 0xFF) == 254)) { + std::string dns_str = format_ipv4(addr); + best_if_dns.push_back(dns_str); + TEST_LOG(" DNS from best interface: " << dns_str); + } + } + } + break; + } + } + + // Verify that DNS servers from best interface appear in our returned list + if (!best_if_dns.empty()) { + bool found_best_if_dns = false; + for (const auto& best_dns : best_if_dns) { + for (int i = 0; i < our_count; i++) { + if (format_ipv4(our_servers[i]) == best_dns) { + found_best_if_dns = true; + TEST_LOG("✓ Found DNS from best interface in returned list: " << best_dns); + break; + } + } + if (found_best_if_dns) break; + } + + if (!found_best_if_dns) { + TEST_LOG("WARNING: No DNS from best interface found in returned list"); + TEST_LOG("This might be OK if the best interface has no DNS configured"); + } + } else { + TEST_LOG("INFO: Best interface has no DNS servers configured"); + } + + free(addrs); + TEST_PASS("test_dns_best_interface_prioritization"); + return true; +} + +// Test 13: Verify DNS reachability validation (NEW) +bool test_dns_subnet_reachability() { + TEST_LOG("Test 13: Verify returned DNS servers are on same subnet OR adapter has gateway"); + TEST_LOG("This tests the DNS reachability validation logic"); + + pubnub_ipv4_address our_servers[16]; + int our_count = pubnub_dns_read_system_servers_ipv4(our_servers, 16); + + if (our_count == 0) { + TEST_LOG("INFO: No DNS servers found"); + TEST_PASS("test_dns_subnet_reachability"); + return true; + } + + // Get all adapters + ULONG buflen = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, + NULL, NULL, &buflen); + + IP_ADAPTER_ADDRESSES* addrs = static_cast(malloc(buflen)); + TEST_ASSERT(addrs != NULL, "Failed to allocate memory"); + + DWORD ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS, + NULL, addrs, &buflen); + TEST_ASSERT(ret == NO_ERROR, "GetAdaptersAddresses failed"); + + // For each returned DNS, verify reachability + for (int i = 0; i < our_count; i++) { + std::string dns_str = format_ipv4(our_servers[i]); + DWORD dns_addr = (our_servers[i].ipv4[0] << 24) | + (our_servers[i].ipv4[1] << 16) | + (our_servers[i].ipv4[2] << 8) | + our_servers[i].ipv4[3]; + + bool is_reachable = false; + std::string reason; + + // Find which adapter has this DNS + for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { + if (aa->OperStatus != IfOperStatusUp) continue; + + // Check if this adapter has this DNS + bool adapter_has_dns = false; + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds != NULL; ds = ds->Next) { + if (ds->Address.lpSockaddr && ds->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ds->Address.lpSockaddr); + DWORD net_addr = sin->sin_addr.S_un.S_addr; + DWORD host_addr = ntohl(net_addr); + + pubnub_ipv4_address check_addr; + check_addr.ipv4[0] = (host_addr >> 24) & 0xFF; + check_addr.ipv4[1] = (host_addr >> 16) & 0xFF; + check_addr.ipv4[2] = (host_addr >> 8) & 0xFF; + check_addr.ipv4[3] = host_addr & 0xFF; + + if (format_ipv4(check_addr) == dns_str) { + adapter_has_dns = true; + break; + } + } + } + + if (!adapter_has_dns) continue; + + // Check if DNS is on same subnet + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + if (ua->Address.lpSockaddr && ua->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(ua->Address.lpSockaddr); + DWORD if_addr = ntohl(sin->sin_addr.S_un.S_addr); + ULONG prefix_len = ua->OnLinkPrefixLength; + + if (prefix_len > 0 && prefix_len <= 32) { + DWORD mask = (prefix_len == 32) ? 0xFFFFFFFF : ~((1UL << (32 - prefix_len)) - 1); + if ((dns_addr & mask) == (if_addr & mask)) { + is_reachable = true; + reason = "on same subnet"; + break; + } + } + } + } + + // Check if adapter has gateway + if (!is_reachable) { + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { + if (gw->Address.lpSockaddr && gw->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sin = reinterpret_cast(gw->Address.lpSockaddr); + DWORD gw_addr = ntohl(sin->sin_addr.S_un.S_addr); + // Valid gateway (not 0.0.0.0, not loopback, not APIPA) + if (gw_addr != 0 && + ((gw_addr >> 24) & 0xFF) != 127 && + !(((gw_addr >> 24) & 0xFF) == 169 && ((gw_addr >> 16) & 0xFF) == 254)) { + is_reachable = true; + reason = "adapter has valid gateway"; + break; + } + } + } + } + + if (is_reachable) break; + } + + TEST_ASSERT(is_reachable, + "DNS server " << dns_str << " is not reachable - no subnet match and no gateway"); + + TEST_LOG("✓ DNS " << dns_str << " is reachable: " << reason); + } + + free(addrs); + TEST_PASS("test_dns_subnet_reachability"); + return true; +} + +// Test 14: Verify no DnsQueryConfig usage (thread safety check) (NEW) +bool test_no_dnsqueryconfig_crashes() { + TEST_LOG("Test 14: Verify multi-threaded stability (no DnsQueryConfig crashes)"); + TEST_LOG("This is a stress test - calling DNS enumeration from many threads simultaneously"); + + const int num_threads = 20; + const int iterations = 10; + std::vector threads; + std::atomic failures(0); + std::atomic successes(0); + + auto worker = [&]() { + for (int i = 0; i < iterations; i++) { + pubnub_ipv4_address servers[16]; + int count = pubnub_dns_read_system_servers_ipv4(servers, 16); + if (count < 0) { + failures++; + } else { + successes++; + } + // Small random delay to increase contention + std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + } + }; + + TEST_LOG("Starting " << num_threads << " threads, " << iterations << " iterations each..."); + + for (int i = 0; i < num_threads; i++) { + threads.emplace_back(worker); + } + + for (auto& t : threads) { + t.join(); + } + + TEST_LOG("Completed: " << successes << " successes, " << failures << " failures"); + + TEST_ASSERT(failures == 0, + "Thread safety test had " << failures << " failures - possible crash/corruption"); + + TEST_LOG("✓ No crashes or failures in multi-threaded stress test"); + TEST_PASS("test_no_dnsqueryconfig_crashes"); + return true; +} + // Main test runner int main(int argc, char* argv[]) { WSADATA wsaData; @@ -545,11 +959,12 @@ int main(int argc, char* argv[]) { } std::cout << "===========================================\n"; - std::cout << "PubNub DNS Windows Tests\n"; + std::cout << "PubNub DNS Windows Tests (Enhanced)\n"; std::cout << "===========================================\n\n"; bool all_passed = true; + // Original tests all_passed &= test_dns_enumeration_basic(); all_passed &= test_dns_no_duplicates(); all_passed &= test_dns_only_up_adapters(); @@ -561,6 +976,16 @@ int main(int argc, char* argv[]) { all_passed &= test_dns_only_adapters_with_gateway(); all_passed &= test_dns_no_loopback_or_apipa(); + std::cout << "\n===========================================\n"; + std::cout << "NEW ROUTING-AWARE TESTS\n"; + std::cout << "===========================================\n\n"; + + // New tests for routing-aware functionality + all_passed &= test_dns_metric_prioritization(); + all_passed &= test_dns_best_interface_prioritization(); + all_passed &= test_dns_subnet_reachability(); + all_passed &= test_no_dnsqueryconfig_crashes(); + std::cout << "\n===========================================\n"; if (all_passed) { std::cout << "✓ ALL TESTS PASSED\n"; From b51e0344485b174977c624c1f3ef65184022209c Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Fri, 14 Nov 2025 20:04:36 +0200 Subject: [PATCH 05/20] Update windows/pubnub_dns_system_servers.c Co-authored-by: Mateusz Dahlke <39696234+Xavrax@users.noreply.github.com> --- windows/pubnub_dns_system_servers.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index a5aaf116..a9e25bf4 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -43,13 +43,8 @@ struct adapter_with_metric { */ static bool pubnub_ipv4_address_valid(DWORD host_addr) { - if (host_addr == 0 || - ((host_addr >> 24) & 0xFF) == 127 || - (((host_addr >> 24) & 0xFF) == 169 && ((host_addr >> 16) & 0xFF) == 254)) { - return false; - } - - return true; + return !(host_addr == 0 || ((host_addr >> 24) & 0xFF) == 127 || + (((host_addr >> 24) & 0xFF) == 169 && ((host_addr >> 16) & 0xFF) == 254)); } /** Comparison function for qsort to sort adapters by priority. From ce061ad8fe94fbb25f3ea66a3358ba96d2c42e1d Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Fri, 14 Nov 2025 20:11:05 +0200 Subject: [PATCH 06/20] refactor: apply changes according to the review notes --- windows/pubnub_dns_system_servers.c | 12 ++++++------ windows/tests/pubnub_dns_windows_test.cpp | 9 +++++---- windows/tests/pubnub_tls_security_test.cpp | 3 ++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index a9e25bf4..10187688 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -419,8 +419,8 @@ int pubnub_dns_read_system_servers_ipv4( compare_adapters_by_priority); /* Extract DNS servers from adapters in priority order. */ - unsigned j = 0; - for (size_t i = 0; i < adapter_count && j < n; i++) { + unsigned dns_count = 0; + for (size_t i = 0; i < adapter_count && dns_count < n; i++) { IP_ADAPTER_ADDRESSES* aa = adapter_list[i].adapter; PUBNUB_LOG_TRACE("Processing adapter: Index=%lu, Metric=%lu, IsBestRoute=%d\n", @@ -428,7 +428,7 @@ int pubnub_dns_read_system_servers_ipv4( (unsigned long)adapter_list[i].metric, adapter_list[i].is_best_route); - for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && j < n; ds = ds->Next) { + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && dns_count < n; ds = ds->Next) { if (!ds->Address.lpSockaddr || ds->Address.lpSockaddr->sa_family != AF_INET) continue; @@ -456,10 +456,10 @@ int pubnub_dns_read_system_servers_ipv4( } /* Add to output if not duplicate */ - if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, j, net_addr, o_ipv4[j].ipv4)) { + if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, dns_count, net_addr, o_ipv4[dns_count].ipv4)) { PUBNUB_LOG_TRACE("Added DNS server from adapter index %lu\n", (unsigned long)aa->IfIndex); - ++j; + ++dns_count; } } } @@ -467,5 +467,5 @@ int pubnub_dns_read_system_servers_ipv4( FREE(adapter_list); FREE(addrs); - return (int)j; + return (int)dns_count; } diff --git a/windows/tests/pubnub_dns_windows_test.cpp b/windows/tests/pubnub_dns_windows_test.cpp index 98868759..04c03310 100644 --- a/windows/tests/pubnub_dns_windows_test.cpp +++ b/windows/tests/pubnub_dns_windows_test.cpp @@ -1,4 +1,3 @@ -/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ /** * @file pubnub_dns_windows_test.cpp * @brief Comprehensive Windows DNS resolution tests for PubNub C-core SDK @@ -11,9 +10,11 @@ * - Edge cases with VPN/virtual adapters */ -#include "core/pubnub_dns_servers.h" -#include "core/pubnub_log.h" -#include "core/pubnub_assert.h" +extern "C" { + #include "core/pubnub_dns_servers.h" + #include "core/pubnub_log.h" + #include "core/pubnub_assert.h" +} #include #include diff --git a/windows/tests/pubnub_tls_security_test.cpp b/windows/tests/pubnub_tls_security_test.cpp index 9394bd92..a04f8ca0 100644 --- a/windows/tests/pubnub_tls_security_test.cpp +++ b/windows/tests/pubnub_tls_security_test.cpp @@ -1,4 +1,3 @@ -/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ /** * @file pubnub_tls_security_test.cpp * @brief TLS/SSL security validation tests for PubNub C-core SDK on Windows @@ -12,11 +11,13 @@ * - Certificate expiration handling */ +extern "C" { #include "pubnub_sync.h" #include "core/pubnub_ssl.h" #include "core/pubnub_helper.h" #include "core/pubnub_timers.h" #include "core/pubnub_log.h" +} #include #include From 27048e220e394b7ae64ff4fd23d2a4a3d64cddc1 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Sun, 16 Nov 2025 11:44:54 +0200 Subject: [PATCH 07/20] feat(ssl): add SNI support for TLS connections Added SNI (Server Name Indication) support for TLS connections. feat(ssl): add hostname verification Added hostname verification for SSL/TLS certificates to prevent MITM attacks. --- cpp/windows.mk | 8 +-- cpp/windows_openssl.mk | 20 +++++--- make/windows_preprocessing.mk | 4 +- openssl/pbpal_connect_openssl.c | 10 ++++ windows/tests/pubnub_dns_windows_test.cpp | 15 +++--- windows/tests/pubnub_tls_security_test.cpp | 59 +++++----------------- 6 files changed, 48 insertions(+), 68 deletions(-) diff --git a/cpp/windows.mk b/cpp/windows.mk index 24eced99..0539a269 100644 --- a/cpp/windows.mk +++ b/cpp/windows.mk @@ -5,9 +5,9 @@ # Making CPP version of the PubNub SDK. WITH_CPP = 1 -include ../make/posix_preprocessing.mk -include ../make/posix_source_files.mk -include ../make/posix_compiler_linker_flags.mk +!include <../make/windows_preprocessing.mk> +!include <../make/windows_source_files.mk> +!include <../make/windows_compiler_linker_flags.mk> ############################################################################### @@ -16,7 +16,7 @@ include ../make/posix_compiler_linker_flags.mk TARGET_BUILD_PATH = -include ../make/windows_cpp_targets.mk +!include <../make/windows_cpp_targets.mk> all: \ diff --git a/cpp/windows_openssl.mk b/cpp/windows_openssl.mk index 932aee6c..f69885ee 100644 --- a/cpp/windows_openssl.mk +++ b/cpp/windows_openssl.mk @@ -5,13 +5,19 @@ # Making CPP version of the PubNub SDK with OpenSSL support. WITH_CPP = 1 OPENSSL = 1 -USE_CRYPTO_API ?= 1 -USE_GRANT_TOKEN ?= 1 -USE_REVOKE_TOKEN ?= 1 +!ifndef USE_CRYPTO_API +USE_CRYPTO_API = 1 +!endif +!ifndef USE_GRANT_TOKEN +USE_GRANT_TOKEN = 1 +!endif +!ifndef USE_REVOKE_TOKEN +USE_REVOKE_TOKEN = 1 +!endif -include ../make/posix_preprocessing.mk -include ../make/posix_source_files.mk -include ../make/posix_compiler_linker_flags.mk +!include <../make/posix_preprocessing.mk> +!include <../make/posix_source_files.mk> +!include <../make/posix_compiler_linker_flags.mk> ############################################################################### @@ -20,7 +26,7 @@ include ../make/posix_compiler_linker_flags.mk TARGET_BUILD_PATH = openssl$(PATH_SEP) -include ../make/windows_cpp_targets.mk +!include <../make/windows_cpp_targets.mk> all: \ diff --git a/make/windows_preprocessing.mk b/make/windows_preprocessing.mk index fd7b6678..44127ae6 100644 --- a/make/windows_preprocessing.mk +++ b/make/windows_preprocessing.mk @@ -78,10 +78,10 @@ WITH_CPP = $(DEFAULT_WITH_CPP) COMPILER = $(DEFAULT_COMPILER_TYPE) !if $(WITH_CPP) COMPILER = $(CXX) -!ifndef $(USE_EXTERN_API) +!ifndef USE_EXTERN_API USE_EXTERN_API = $(DEFAULT_USE_EXTERN_API) !endif -!ifndef $(USE_CPP11) +!ifndef USE_CPP11 USE_CPP11 = $(DEFAULT_USE_CPP11) !endif !endif diff --git a/openssl/pbpal_connect_openssl.c b/openssl/pbpal_connect_openssl.c index 4829ef0d..c265d5ce 100644 --- a/openssl/pbpal_connect_openssl.c +++ b/openssl/pbpal_connect_openssl.c @@ -205,6 +205,16 @@ enum pbpal_tls_result pbpal_start_tls(pubnub_t* pb) return pbtlsResourceFailure; } + /** Enable SNI (Server Name Indication) for virtual hosting support. */ + if (!SSL_set_tlsext_host_name(ssl, pb->origin)) { + PUBNUB_LOG_WARNING( + "pb=%p: SSL_set_tlsext_host_name() failed to set SNI hostname '%s'\n", + pb, + pb->origin); + ERR_print_errors_cb(print_to_pubnub_log, pb); + return pbtlsFailed; + } + /** Enable hostname verification. */ X509_VERIFY_PARAM *param = SSL_get0_param(ssl); if (param != NULL) { diff --git a/windows/tests/pubnub_dns_windows_test.cpp b/windows/tests/pubnub_dns_windows_test.cpp index 04c03310..567226c8 100644 --- a/windows/tests/pubnub_dns_windows_test.cpp +++ b/windows/tests/pubnub_dns_windows_test.cpp @@ -10,17 +10,16 @@ * - Edge cases with VPN/virtual adapters */ -extern "C" { - #include "core/pubnub_dns_servers.h" - #include "core/pubnub_log.h" - #include "core/pubnub_assert.h" -} - -#include -#include #include #include +#include +#include #include + +#include "core/pubnub_dns_servers.h" +#include "core/pubnub_log.h" +#include "core/pubnub_assert.h" + #include #include #include diff --git a/windows/tests/pubnub_tls_security_test.cpp b/windows/tests/pubnub_tls_security_test.cpp index a04f8ca0..764c7556 100644 --- a/windows/tests/pubnub_tls_security_test.cpp +++ b/windows/tests/pubnub_tls_security_test.cpp @@ -11,13 +11,11 @@ * - Certificate expiration handling */ -extern "C" { #include "pubnub_sync.h" #include "core/pubnub_ssl.h" #include "core/pubnub_helper.h" #include "core/pubnub_timers.h" #include "core/pubnub_log.h" -} #include #include @@ -49,6 +47,7 @@ pubnub_t* create_test_context() { if (pb != NULL) { pubnub_init(pb, "demo", "demo"); pubnub_set_ssl_options(pb, true, false); // SSL required, no fallback + pubnub_set_user_id(pb, "demo"); } return pb; } @@ -89,7 +88,7 @@ bool test_certificate_validation() { // Try connecting to a known self-signed cert site // Note: This requires the ability to set custom origin #if PUBNUB_ORIGIN_SETTABLE - pubnub_set_origin(pb, "self-signed.badssl.com"); + pubnub_origin_set(pb, "self-signed.badssl.com"); pubnub_set_transaction_timeout(pb, 10000); enum pubnub_res res = pubnub_time(pb); @@ -112,7 +111,6 @@ bool test_certificate_validation() { // Test 3: CRITICAL - Hostname verification bool test_hostname_verification() { TEST_LOG("Test 3: CRITICAL - Hostname verification"); - TEST_LOG("WARNING: Current implementation may NOT verify hostname!"); #if PUBNUB_ORIGIN_SETTABLE pubnub_t* pb = create_test_context(); @@ -120,7 +118,7 @@ bool test_hostname_verification() { // Try connecting to wrong-host.badssl.com // This has a valid certificate but for the wrong hostname - pubnub_set_origin(pb, "wrong.host.badssl.com"); + pubnub_origin_set(pb, "wrong.host.badssl.com"); pubnub_set_transaction_timeout(pb, 10000); enum pubnub_res res = pubnub_time(pb); @@ -188,7 +186,7 @@ bool test_protocol_version() { // Try connecting to TLS 1.2 only server // tls-v1-2.badssl.com requires TLS 1.2 or higher - pubnub_set_origin(pb, "tls-v1-2.badssl.com"); + pubnub_origin_set(pb, "tls-v1-2.badssl.com"); pubnub_set_transaction_timeout(pb, 10000); enum pubnub_res res = pubnub_time(pb); @@ -197,7 +195,7 @@ bool test_protocol_version() { } // Should succeed if we support TLS 1.2+ - if (res == PNR_OK) { + if (res == PNR_OK || res == PNR_FORMAT_ERROR) { TEST_LOG("Successfully negotiated TLS 1.2+"); } else { TEST_LOG("WARNING: Failed to connect to TLS 1.2 server: " << pubnub_res_2_string(res)); @@ -221,7 +219,7 @@ bool test_expired_certificate() { TEST_ASSERT(pb != NULL, "Failed to create context"); // Try connecting to expired.badssl.com - pubnub_set_origin(pb, "expired.badssl.com"); + pubnub_origin_set(pb, "expired.badssl.com"); pubnub_set_transaction_timeout(pb, 10000); enum pubnub_res res = pubnub_time(pb); @@ -242,41 +240,9 @@ bool test_expired_certificate() { return true; } -// Test 7: SSL session reuse -bool test_ssl_session_reuse() { - TEST_LOG("Test 7: SSL session reuse"); - - pubnub_t* pb = create_test_context(); - TEST_ASSERT(pb != NULL, "Failed to create context"); - - // Enable SSL session reuse - pubnub_set_reuse_ssl_session(pb, true); - pubnub_set_transaction_timeout(pb, 15000); - - // First connection - enum pubnub_res res = pubnub_time(pb); - if (res == PNR_STARTED) { - res = pubnub_await(pb); - } - TEST_ASSERT(res == PNR_OK, "First connection failed"); - - // Second connection (should reuse session) - res = pubnub_time(pb); - if (res == PNR_STARTED) { - res = pubnub_await(pb); - } - TEST_ASSERT(res == PNR_OK, "Second connection (session reuse) failed"); - - TEST_LOG("SSL session reuse appears to be working"); - - pubnub_free(pb); - TEST_PASS("test_ssl_session_reuse"); - return true; -} - -// Test 8: Concurrent SSL connections (thread safety) +// Test 7: Concurrent SSL connections (thread safety) bool test_concurrent_ssl_connections() { - TEST_LOG("Test 8: Concurrent SSL connections"); + TEST_LOG("Test 7: Concurrent SSL connections"); const int num_contexts = 5; pubnub_t* contexts[num_contexts]; @@ -315,9 +281,9 @@ bool test_concurrent_ssl_connections() { return true; } -// Test 9: Custom CA certificate +// Test 8: Custom CA certificate bool test_custom_ca_certificate() { - TEST_LOG("Test 9: Custom CA certificate handling"); + TEST_LOG("Test 8: Custom CA certificate handling"); pubnub_t* pb = create_test_context(); TEST_ASSERT(pb != NULL, "Failed to create context"); @@ -341,9 +307,9 @@ bool test_custom_ca_certificate() { return true; } -// Test 10: Verify no cleartext fallback +// Test 9: Verify no cleartext fallback bool test_no_cleartext_fallback() { - TEST_LOG("Test 10: Verify no cleartext fallback"); + TEST_LOG("Test 9: Verify no cleartext fallback"); pubnub_t* pb = create_test_context(); TEST_ASSERT(pb != NULL, "Failed to create context"); @@ -390,7 +356,6 @@ int main(int argc, char* argv[]) { all_passed &= test_protocol_version(); all_passed &= test_expired_certificate(); - all_passed &= test_ssl_session_reuse(); all_passed &= test_concurrent_ssl_connections(); all_passed &= test_custom_ca_certificate(); all_passed &= test_no_cleartext_fallback(); From 84df0f6e812f696b2e3f91674895a8b66437405b Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 17 Nov 2025 10:35:59 +0200 Subject: [PATCH 08/20] refactor(ssl): actualize bundled certificates Update bundled certificates which is used for secured connection and peers verification. --- mbedtls/pbpal_connect_mbedtls.c | 93 +++++++++++++++++++------------- openssl/pbpal_connect_openssl.c | 94 ++++++++++++++++++++------------- 2 files changed, 113 insertions(+), 74 deletions(-) diff --git a/mbedtls/pbpal_connect_mbedtls.c b/mbedtls/pbpal_connect_mbedtls.c index d98714f9..ad4a8ceb 100644 --- a/mbedtls/pbpal_connect_mbedtls.c +++ b/mbedtls/pbpal_connect_mbedtls.c @@ -20,9 +20,57 @@ #include "mbedtls/ssl.h" -/* Starfields Inc (Class 2) Root certificate. - It was used to sign the server certificate of https://pubsub.pubnub.com. - (at the time of writing this, 2015-06-04). +/* Amazon Root CA 1 + Used by PubNub domains: ps.pndsn.com, *.pubnubapi.com + + Subject: C=US, O=Amazon, CN=Amazon Root CA 1 + Issuer: C=US, O=Amazon, CN=Amazon Root CA 1 + + Valid from: May 26, 2015 + Valid to: January 17, 2038 + + Key: RSA 2048-bit + Source: https://www.amazontrust.com/repository/AmazonRootCA1.pem + */ +static char pubnub_cert_Amazon_Root_CA_1[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" + "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" + "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" + "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" + "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" + "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" + "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" + "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" + "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" + "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" + "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" + "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" + "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" + "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" + "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" + "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" + "rqXRfboQnoZsG4q5WTP468SQvvG5\n" + "-----END CERTIFICATE-----\n"; + + +/* Starfield Class 2 Certification Authority + Used by PubNub domains: pubsub.pubnub.com, pubnub.pubnub.com, *.pubnub.com + Validates certificates issued by Starfield Root Certificate Authority - G2 + through cross-certification. + + Subject: C=US, O=Starfield Technologies, Inc., + OU=Starfield Class 2 Certification Authority + Issuer: C=US, O=Starfield Technologies, Inc., + OU=Starfield Class 2 Certification Authority + + Valid from: June 29, 2004 + Valid to: June 29, 2034 + + Key: RSA 2048-bit + Source: Extracted from pubsub.pubnub.com certificate chain (2015-06-04) + Available at https://certs.starfieldtech.com/repository/ */ static char pubnub_cert_Starfield[] = "-----BEGIN CERTIFICATE-----\n" @@ -50,37 +98,6 @@ static char pubnub_cert_Starfield[] = "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" "-----END CERTIFICATE-----\n"; - -/* GlobalSign Root class 2 Certificate, used at the time of this - writing (2016-11-26): - - 2 s:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA - i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA - - */ -static char pubnub_cert_GlobalSign[] = - "-----BEGIN CERTIFICATE-----\n" - "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" - "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" - "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" - "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" - "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" - "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" - "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" - "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" - "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" - "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" - "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" - "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" - "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" - "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" - "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" - "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" - "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" - "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" - "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" - "-----END CERTIFICATE-----\n"; - static void alloc_setup(pubnub_t* pb); #define PUBNUB_PORT "443" @@ -111,8 +128,12 @@ enum pbpal_tls_result pbpal_start_tls(pubnub_t* pb) return pbtlsFailed; } - if (0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_Starfield, sizeof(pubnub_cert_Starfield)) - && 0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_GlobalSign, sizeof(pubnub_cert_GlobalSign))) { + /* Load certificates in priority order: + 1. Amazon Root CA 1 - Primary for PubNub domains (ps.pndsn.com, *.pubnubapi.com) + 2. Starfield Root CA G2 - For pubsub.pubnub.com + */ + if (0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_Amazon_Root_CA_1, sizeof(pubnub_cert_Amazon_Root_CA_1)) + && 0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_Starfield, sizeof(pubnub_cert_Starfield))) { PUBNUB_LOG_ERROR("Failed to parse CA certificate\n"); return pbtlsFailed; } diff --git a/openssl/pbpal_connect_openssl.c b/openssl/pbpal_connect_openssl.c index c265d5ce..53651feb 100644 --- a/openssl/pbpal_connect_openssl.c +++ b/openssl/pbpal_connect_openssl.c @@ -41,11 +41,57 @@ static int print_to_pubnub_log(const char* s, size_t len, void* p) return 0; } -/* Starfields Inc (Class 2) Root certificate. - It was used to sign the server certificate of https://pubsub.pubnub.com. - (at the time of writing this, 2015-06-04). +/* Amazon Root CA 1 + Used by PubNub domains: ps.pndsn.com, *.pubnubapi.com + Subject: C=US, O=Amazon, CN=Amazon Root CA 1 + Issuer: C=US, O=Amazon, CN=Amazon Root CA 1 + + Valid from: May 26, 2015 + Valid to: January 17, 2038 + + Key: RSA 2048-bit + Source: https://www.amazontrust.com/repository/AmazonRootCA1.pem + */ +static char pubnub_cert_Amazon_Root_CA_1[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" + "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" + "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" + "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" + "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" + "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" + "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" + "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" + "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" + "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" + "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" + "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" + "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" + "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" + "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" + "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" + "rqXRfboQnoZsG4q5WTP468SQvvG5\n" + "-----END CERTIFICATE-----\n"; + + +/* Starfield Class 2 Certification Authority + Used by PubNub domains: pubsub.pubnub.com, pubnub.pubnub.com, *.pubnub.com + Validates certificates issued by Starfield Root Certificate Authority - G2 + through cross-certification. + + Subject: C=US, O=Starfield Technologies, Inc., + OU=Starfield Class 2 Certification Authority + Issuer: C=US, O=Starfield Technologies, Inc., + OU=Starfield Class 2 Certification Authority + + Valid from: June 29, 2004 Valid to: June 29, 2034 + + Key: RSA 2048-bit + Source: Extracted from pubsub.pubnub.com certificate chain (2015-06-04) + Available at https://certs.starfieldtech.com/repository/ */ static char pubnub_cert_Starfield[] = "-----BEGIN CERTIFICATE-----\n" @@ -73,39 +119,6 @@ static char pubnub_cert_Starfield[] = "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" "-----END CERTIFICATE-----\n"; - -/* GlobalSign Root class 2 Certificate, used at the time of this - writing (2016-11-26): - - 2 s:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA - i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA - - Valid to: January 28, 2028 - */ -static char pubnub_cert_GlobalSign[] = - "-----BEGIN CERTIFICATE-----\n" - "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" - "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" - "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" - "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" - "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" - "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" - "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" - "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" - "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" - "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" - "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" - "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" - "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" - "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" - "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" - "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" - "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" - "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" - "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" - "-----END CERTIFICATE-----\n"; - - static int add_pem_cert(SSL_CTX* sslCtx, char const* pem_cert) { X509* cert; @@ -137,8 +150,13 @@ static int add_pem_cert(SSL_CTX* sslCtx, char const* pem_cert) static int add_pubnub_cert(SSL_CTX* sslCtx) { - int rslt = add_pem_cert(sslCtx, pubnub_cert_Starfield); - return rslt || add_pem_cert(sslCtx, pubnub_cert_GlobalSign); + /* Load certificates in priority order: + 1. Amazon Root CA 1 - Primary for PubNub domains (ps.pndsn.com, *.pubnubapi.com) + 2. Starfield Root CA G2 - For pubsub.pubnub.com + */ + int rslt = add_pem_cert(sslCtx, pubnub_cert_Amazon_Root_CA_1); + rslt = rslt || add_pem_cert(sslCtx, pubnub_cert_Starfield); + return rslt; } From 5e7140933912585919d66c847eeeea1220393f62 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 17 Nov 2025 13:45:28 +0200 Subject: [PATCH 09/20] refactor(ssl): Add ISRG Root X1 Certificate Add the Let's Encrypt ISRG Root X1 Certificate for Windows and parametrically for other platforms. --- mbedtls/pbpal_connect_mbedtls.c | 55 ++++++++++++++++++++++++++++++++- openssl/pbpal_connect_openssl.c | 52 +++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/mbedtls/pbpal_connect_mbedtls.c b/mbedtls/pbpal_connect_mbedtls.c index ad4a8ceb..8b84a5f7 100644 --- a/mbedtls/pbpal_connect_mbedtls.c +++ b/mbedtls/pbpal_connect_mbedtls.c @@ -98,6 +98,54 @@ static char pubnub_cert_Starfield[] = "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" "-----END CERTIFICATE-----\n"; +#if PUBNUB_USE_LETS_ENCRYPT_CERTIFICATE +/* ISRG Root X1 (Let's Encrypt) + Used for testing and validation purposes with badssl.com and other Let's Encrypt domains. + Note: This is NOT used by PubNub production infrastructure. + + Subject: C=US, O=Internet Security Research Group, CN=ISRG Root X1 + Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1 + + Valid from: June 4, 2015 + Valid to: June 4, 2035 + + Key: RSA 4096-bit + Source: https://letsencrypt.org/certs/isrgrootx1.pem + */ +static char pubnub_cert_ISRG_Root_X1[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" + "-----END CERTIFICATE-----\n"; +#endif + static void alloc_setup(pubnub_t* pb); #define PUBNUB_PORT "443" @@ -131,9 +179,14 @@ enum pbpal_tls_result pbpal_start_tls(pubnub_t* pb) /* Load certificates in priority order: 1. Amazon Root CA 1 - Primary for PubNub domains (ps.pndsn.com, *.pubnubapi.com) 2. Starfield Root CA G2 - For pubsub.pubnub.com + 3. ISRG Root X1 - For testing with Let's Encrypt domains (badssl.com, etc.) */ if (0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_Amazon_Root_CA_1, sizeof(pubnub_cert_Amazon_Root_CA_1)) - && 0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_Starfield, sizeof(pubnub_cert_Starfield))) { + && 0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_Starfield, sizeof(pubnub_cert_Starfield)) +#if PUBNUB_USE_LETS_ENCRYPT_CERTIFICATE + && 0 != mbedtls_x509_crt_parse(pal->ca_certificates, (const unsigned char*)pubnub_cert_ISRG_Root_X1, sizeof(pubnub_cert_ISRG_Root_X1)) +#endif + ) { PUBNUB_LOG_ERROR("Failed to parse CA certificate\n"); return pbtlsFailed; } diff --git a/openssl/pbpal_connect_openssl.c b/openssl/pbpal_connect_openssl.c index 53651feb..931d861d 100644 --- a/openssl/pbpal_connect_openssl.c +++ b/openssl/pbpal_connect_openssl.c @@ -119,6 +119,54 @@ static char pubnub_cert_Starfield[] = "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" "-----END CERTIFICATE-----\n"; +#if defined(PUBNUB_USE_LETS_ENCRYPT_CERTIFICATE) || defined(_WIN32) +/* ISRG Root X1 (Let's Encrypt) + Used for testing and validation purposes with badssl.com and other Let's Encrypt domains. + Note: This is NOT used by PubNub production infrastructure. + + Subject: C=US, O=Internet Security Research Group, CN=ISRG Root X1 + Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1 + + Valid from: June 4, 2015 + Valid to: June 4, 2035 + + Key: RSA 4096-bit + Source: https://letsencrypt.org/certs/isrgrootx1.pem + */ +static char pubnub_cert_ISRG_Root_X1[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" + "-----END CERTIFICATE-----\n"; +#endif + static int add_pem_cert(SSL_CTX* sslCtx, char const* pem_cert) { X509* cert; @@ -153,9 +201,13 @@ static int add_pubnub_cert(SSL_CTX* sslCtx) /* Load certificates in priority order: 1. Amazon Root CA 1 - Primary for PubNub domains (ps.pndsn.com, *.pubnubapi.com) 2. Starfield Root CA G2 - For pubsub.pubnub.com + 3. ISRG Root X1 - For testing with Let's Encrypt domains (badssl.com, etc.) */ int rslt = add_pem_cert(sslCtx, pubnub_cert_Amazon_Root_CA_1); rslt = rslt || add_pem_cert(sslCtx, pubnub_cert_Starfield); +#if defined(PUBNUB_USE_LETS_ENCRYPT_CERTIFICATE) || defined(_WIN32) + rslt = rslt || add_pem_cert(sslCtx, pubnub_cert_ISRG_Root_X1); +#endif return rslt; } From 1ca3bcac767f1ffd21e4029008470d9e109b00d7 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 17 Nov 2025 14:17:25 +0200 Subject: [PATCH 10/20] build(cpp-windows): update included make files for OpenSSL version --- cpp/windows_openssl.mk | 6 +++--- windows/tests/pubnub_tls_security_test.cpp | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cpp/windows_openssl.mk b/cpp/windows_openssl.mk index f69885ee..8659a3f7 100644 --- a/cpp/windows_openssl.mk +++ b/cpp/windows_openssl.mk @@ -15,9 +15,9 @@ USE_GRANT_TOKEN = 1 USE_REVOKE_TOKEN = 1 !endif -!include <../make/posix_preprocessing.mk> -!include <../make/posix_source_files.mk> -!include <../make/posix_compiler_linker_flags.mk> +!include <../make/windows_preprocessing.mk> +!include <../make/windows_source_files.mk> +!include <../make/windows_compiler_linker_flags.mk> ############################################################################### diff --git a/windows/tests/pubnub_tls_security_test.cpp b/windows/tests/pubnub_tls_security_test.cpp index 764c7556..c7ef4b68 100644 --- a/windows/tests/pubnub_tls_security_test.cpp +++ b/windows/tests/pubnub_tls_security_test.cpp @@ -300,6 +300,8 @@ bool test_custom_ca_certificate() { } // With invalid CA, connection might fail (or succeed if it falls back to hardcoded certs) + TEST_ASSERT(conn_res != PNR_OK, + "Connection should fail with invalid CA file (no fallback to bundled certs)"); TEST_LOG("Connection result with invalid CA: " << pubnub_res_2_string(conn_res)); pubnub_free(pb); From 0ddf134520f5aaf6f96a864df16755e297907a84 Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:29:14 +0000 Subject: [PATCH 11/20] PubNub SDK v6.1.0 release. --- .pubnub.yml | 29 +++++++++++++++++++++-------- CHANGELOG.md | 12 ++++++++++++ core/pubnub_version_internal.h | 2 +- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 58c99aab..131a2f57 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,21 @@ name: c-core schema: 1 -version: "6.0.0" +version: "6.1.0" scm: github.com/pubnub/c-core changelog: + - date: 2025-11-17 + version: v6.1.0 + changes: + - type: feature + text: "Add SNI support for TLS connections." + - type: feature + text: "Added SNI (Server Name Indication) support for TLS connections. Added hostname verification for SSL/TLS certificates to prevent MITM attacks." + - type: improvement + text: "Update bundled certificates (added Amazon Root CA 1) which is used for secured connection and peers verification." + - type: improvement + text: "Filter out DNS servers received from disconnected adapters, loopback, and APIPA addresses." + - type: improvement + text: "Added tests to test DNS server discovery in a controlled Windows environment (manual)." - date: 2025-11-07 version: v6.0.0 changes: @@ -1052,7 +1065,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" @@ -1118,7 +1131,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" @@ -1184,7 +1197,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" @@ -1246,7 +1259,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" @@ -1307,7 +1320,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" @@ -1363,7 +1376,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" @@ -1416,7 +1429,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.0.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 requires: - name: "miniz" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4781e630..13d4d1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## v6.1.0 +November 17 2025 + +#### Added +- Add SNI support for TLS connections. +- Added SNI (Server Name Indication) support for TLS connections. Added hostname verification for SSL/TLS certificates to prevent MITM attacks. + +#### Modified +- Update bundled certificates (added Amazon Root CA 1) which is used for secured connection and peers verification. +- Filter out DNS servers received from disconnected adapters, loopback, and APIPA addresses. +- Added tests to test DNS server discovery in a controlled Windows environment (manual). + ## v6.0.0 November 07 2025 diff --git a/core/pubnub_version_internal.h b/core/pubnub_version_internal.h index 4a7f6a17..2398d35f 100644 --- a/core/pubnub_version_internal.h +++ b/core/pubnub_version_internal.h @@ -3,7 +3,7 @@ #define INC_PUBNUB_VERSION_INTERNAL -#define PUBNUB_SDK_VERSION "6.0.0" +#define PUBNUB_SDK_VERSION "6.1.0" #endif /* !defined INC_PUBNUB_VERSION_INTERNAL */ From 24b83e13285398e26eaae1872a940307f1bd7222 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 17 Nov 2025 14:32:17 +0200 Subject: [PATCH 12/20] docs(changelogs): fix change log entries --- .pubnub.yml | 4 ++-- CHANGELOG.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 131a2f57..17640d8f 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -7,9 +7,9 @@ changelog: version: v6.1.0 changes: - type: feature - text: "Add SNI support for TLS connections." + text: "Added SNI (Server Name Indication) support for TLS connections." - type: feature - text: "Added SNI (Server Name Indication) support for TLS connections. Added hostname verification for SSL/TLS certificates to prevent MITM attacks." + text: "Added hostname verification for SSL/TLS certificates to prevent MITM attacks." - type: improvement text: "Update bundled certificates (added Amazon Root CA 1) which is used for secured connection and peers verification." - type: improvement diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d4d1d6..ea2cd4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ November 17 2025 #### Added -- Add SNI support for TLS connections. -- Added SNI (Server Name Indication) support for TLS connections. Added hostname verification for SSL/TLS certificates to prevent MITM attacks. +- Added SNI (Server Name Indication) support for TLS connections. +- Added hostname verification for SSL/TLS certificates to prevent MITM attacks. #### Modified - Update bundled certificates (added Amazon Root CA 1) which is used for secured connection and peers verification. From deffd7fdea9e273c927f65641201e3ef9096a49e Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 1 Dec 2025 01:25:00 +0200 Subject: [PATCH 13/20] feat(ipv6): add IPv6 route detection Add working IPv6 route detection to decide the preferable connection interface. refactor(dns-server): try user-provided IPv6 DNS servers first When built with IPv6 support, first try to use any of the user-provided IPv6 DNS servers before falling back to the user-provided IPv4 before falling back to the address provided by a well-known DNS provider. refactor(dns): request `A` and `AAAA` DNS records for domain When built with IPv6 support, SDK will send two queries to receive both records and decide which will be most suitable for currently available routing. --- core/pubnub_dns_servers.h | 34 +- core/pubnub_internal_common.h | 105 +- core/pubnub_netcore.c | 160 +-- .../samples/publish_callback_subloop_sample.c | 14 +- .../samples/subscribe_publish_from_callback.c | 17 +- core/test/pubnub_config.h | 20 +- cpp/pubnub_common.hpp | 590 +++++------ .../pbpal_resolv_and_connect_freertos_tcp.c | 2 +- lib/pubnub_dns_codec.c | 34 +- lib/pubnub_dns_codec.h | 34 +- lib/pubnub_dns_codec_unit_test.c | 412 ++++---- lib/sockets/pbpal_adns_sockets.c | 240 +++-- lib/sockets/pbpal_adns_sockets.h | 20 +- .../pbpal_resolv_and_connect_sockets.c | 966 +++++++++++------- .../pbpal_resolv_and_connect_harmony_tcp.c | 4 +- openssl/pbpal_connect_openssl.c | 48 +- openssl/pubnub_config.h | 36 +- posix/pubnub_config.h | 34 +- posix/pubnub_dns_system_servers.c | 93 +- windows/pubnub_config.h | 24 +- windows/pubnub_dns_system_servers.c | 924 +++++++++++------ 21 files changed, 2292 insertions(+), 1519 deletions(-) diff --git a/core/pubnub_dns_servers.h b/core/pubnub_dns_servers.h index 2a77f411..0ff0a3f0 100644 --- a/core/pubnub_dns_servers.h +++ b/core/pubnub_dns_servers.h @@ -21,7 +21,7 @@ struct pubnub_ipv6_address { }; /* primary, secondary(ipv4, ipv6) and default dns server */ -#define PUBNUB_MAX_DNS_SERVERS_MASK 0x10 +#define PUBNUB_MAX_DNS_SERVERS_MASK 0x10 #else /* primary, secondary(ipv4) and default dns server */ #define PUBNUB_MAX_DNS_SERVERS_MASK 0x04 @@ -122,13 +122,12 @@ PUBNUB_EXTERN int pubnub_get_dns_secondary_server_ipv4(struct pubnub_ipv4_addres @param[out] o_ipv4 The array where to put the system DNS servers. allocated by the caller for @p n elements. - @param[in] n The number of elements allocated for the @p o_ipv4 @retval -1: error, can't read DNS server configuration @retval otherwise: number of DNS servers read */ PUBNUB_EXTERN int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, - size_t n); + size_t n); #if PUBNUB_USE_IPV6 /** Sets the primary DNS server IPv6 address to use when resolving the Pubnub origin, in binary form(network order). @@ -162,7 +161,7 @@ PUBNUB_EXTERN int pubnub_dns_set_secondary_server_ipv6(struct pubnub_ipv6_addres /** Sets the primary DNS server IPv6 address from the corresponding 'numbers-and-colons' notation string to use when resolving the Pubnub origin. Applies to all subsequent DNS queries, if successful. - (Note: All DNS servers are initially set to zeros) + (Note: All DNS servers are initially set to zeros) @param ipv6_str The IPv6 address string of the server to use. Set all zeros("::0", or "0::") to not use this DNS server. @@ -178,7 +177,7 @@ PUBNUB_EXTERN int pubnub_dns_set_primary_server_ipv6_str(char const* ipv6_str); Applies to all subsequent DNS queries, if successful and if using secondary server is supported. Secondary server, if used at all, is used if a query to the primary server fails. - (Note: All DNS servers are initially set to zeros) + (Note: All DNS servers are initially set to zeros) @param ipv6_str The IPv6 address string of the server to use. Set all zeros("::0", or "0::") to not use this DNS server. @@ -207,10 +206,28 @@ PUBNUB_EXTERN int pubnub_get_dns_primary_server_ipv6(struct pubnub_ipv6_address* the secondary Ipv6 DNS server */ PUBNUB_EXTERN int pubnub_get_dns_secondary_server_ipv6(struct pubnub_ipv6_address* o_ipv6); -#endif /* PUBNUB_USE_IPV6 */ -#else +/** Reads the DNS servers in the system configuration. Will read + at most @p n servers, even if more are configured. Keep in + mind that modern systems have complex configurations and + often this will yield just one DNS server which listens on the + loopback IP address, while the "real" DNS configuration is + not available through standard means. + + On POSIX systems, this will read from `/etc/resolv.conf`, + looking for `nameserver` lines. On Windows, this will use OS + functions to get the info. + @param[out] o_ipv6 The array where to put the system DNS servers. + allocated by the caller for @p n elements. + @param[in] n The number of elements allocated for the @p o_ipv6 + @retval -1: error, can't read DNS server configuration + @retval otherwise: number of DNS servers read + */ +PUBNUB_EXTERN int pubnub_dns_read_system_servers_ipv6(struct pubnub_ipv6_address* o_ipv6, + size_t n); +#endif /* PUBNUB_USE_IPV6 */ +#else /* PUBNUB_SET_DNS_SERVERS */ #define pubnub_dns_set_primary_server_ipv4(ipv4) -1 #define pubnub_dns_set_secondary_server_ipv4(ipv4) -1 #define pubnub_dns_set_primary_server_ipv4_str(ipv4_str) -1 @@ -225,6 +242,7 @@ PUBNUB_EXTERN int pubnub_get_dns_secondary_server_ipv6(struct pubnub_ipv6_addres #define pubnub_dns_set_secondary_server_ipv6_str(ipv6_str) -1 #define pubnub_get_dns_primary_server_ipv6(o_ipv6) -1 #define pubnub_get_dns_secondary_server_ipv6(o_ipv6) -1 +#define pubnub_dns_read_system_servers_ipv6(o_ipv4, n) -1 #endif /* PUBNUB_USE_IPV6 */ -#endif /* PUBNUB_SET_DNS_SERVERS */ +#endif /* !PUBNUB_SET_DNS_SERVERS */ #endif /* !defined INC_PUBNUB_DNS_SERVERS */ diff --git a/core/pubnub_internal_common.h b/core/pubnub_internal_common.h index 7288bc12..ee9b2004 100644 --- a/core/pubnub_internal_common.h +++ b/core/pubnub_internal_common.h @@ -33,7 +33,7 @@ #define PUBNUB_CHANGE_DNS_SERVERS 0 #endif -#define PUBNUB_ADNS_RETRY_AFTER_CLOSE \ +#define PUBNUB_ADNS_RETRY_AFTER_CLOSE \ (PUBNUB_CHANGE_DNS_SERVERS || PUBNUB_USE_MULTIPLE_ADDRESSES) #if !defined(PUBNUB_ONLY_PUBSUB_API) @@ -118,7 +118,7 @@ /* Maximum object length that will be sent via PATCH, or POST methods */ #define PUBNUB_MAX_OBJECT_LENGTH 30000 -/* Default value port is initialized with in case of settable origin. Only +/* Default value port is initialized with in case of settable origin. Only values different than this are taken into account when making connections */ #define INITIAL_PORT_VALUE 0 @@ -205,19 +205,44 @@ typedef struct pubnub_tcp_keepalive_ { /** The time in seconds a socket needs to be @c idle before the first keep-alive probe is sent. */ - uint8_t time; + uint8_t time; /** The number of seconds that should pass between sends of keep-alive probes if the last one wasn't acknowledged. */ - uint8_t interval; + uint8_t interval; /** The number of times a probe will be sent and not acknowledged before the connection is deemed broken. */ - uint8_t probes; + uint8_t probes; } pubnub_tcp_keepalive; +#ifdef PUBNUB_CALLBACK_API +/** DNS resolution query tracking object. */ +struct dns_queries_tracking { + /* @c A record query has been sent. */ + bool sent_a; +#if PUBNUB_USE_IPV6 + /* @c AAAA record query has been sent. */ + bool sent_aaaa; +#endif /* PUBNUB_USE_IPV6 */ + /** Whether query should be retried in case of partial completion. */ + bool need_retry; + + /* @c A record response received. */ + bool received_a; + /* Temporarily storage until @c AAAA record response will be received. */ + struct sockaddr_in dns_a_addr; +#if PUBNUB_USE_IPV6 + /* @c AAAA record response received. */ + bool received_aaaa; + /* Temporarily storage until @c A record response will be received. */ + struct sockaddr_storage dns_aaaa_addr; +#endif /* PUBNUB_USE_IPV6 */ +}; +#endif /* PUBNUB_CALLBACK_API */ + struct pubnub_options { #if PUBNUB_BLOCKING_IO_SETTABLE /** Indicates whether to use blocking I/O. Not implemented if @@ -244,12 +269,12 @@ struct pubnub_options { #endif #if PUBNUB_USE_SSL /** Should the PubNub client establish the connection to - * PubNub using SSL? */ + * PubNub using SSL? */ bool useSSL : 1; /** When SSL is enabled, should the client fallback to a - * non-SSL connection if it experiences issues handshaking - * across local proxies, firewalls, etc? - */ + * non-SSL connection if it experiences issues handshaking + * across local proxies, firewalls, etc? + */ bool fallbackSSL : 1; /** Use system certificate store (if available) */ bool use_system_certificate_store : 1; @@ -291,11 +316,11 @@ struct pubnub_flags { #define ROTATIONS_COUNT_SIZE_IN_BITS 3 /** Number of full DNS servers list rotations in single transaction to a single DNS server. - Important when DNS server doesn't answer and transaction timeout. List of DNS + Important when DNS server doesn't answer and transaction timeout. List of DNS servers should rotate to find the one which is able to respond on DNS query. Macro constant limiting number of full DNS servers list rotations. */ - int rotations_count: ROTATIONS_COUNT_SIZE_IN_BITS; + int rotations_count : ROTATIONS_COUNT_SIZE_IN_BITS; #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif }; @@ -306,8 +331,8 @@ struct pbdns_servers_check { uint8_t dns_mask; /* dns server condition bit indicators(0 - OK, 1 - Error on server). Set to zeros indicates no issues encountered while sending, or receiving - valid response from any of available dns servers(up to 8 of them, as 'uint8_t' - conains 8 bits. In practise there is up to 5 dns servers). + valid response from any of available dns servers(up to 8 of them, as + 'uint8_t' conains 8 bits. In practise there is up to 5 dns servers). */ uint8_t dns_server_check; }; @@ -315,7 +340,7 @@ struct pbdns_servers_check { #if PUBNUB_USE_MULTIPLE_ADDRESSES struct pubnub_multi_addresses { - time_t time_of_the_last_dns_query; + time_t time_of_the_last_dns_query; /* Number of spare ipv4 addresses */ int n_ipv4; /* ipv4 address index(from the array) currently used */ @@ -398,7 +423,7 @@ struct pubnub_ { #if defined PUBNUB_ORIGIN_SETTABLE char const* origin; - + uint16_t port; #endif @@ -413,7 +438,7 @@ struct pubnub_ { Takes values from enum 'pubnub_method' defined in 'pubnub_api_types.h'. */ uint8_t method; - + #if PUBNUB_ADVANCED_KEEP_ALIVE struct pubnub_keep_alive_data { time_t timeout; @@ -466,28 +491,34 @@ struct pubnub_ { pubnub_callback_t cb; void* user_data; + struct dns_queries_tracking dns_queries; #if PUBNUB_CHANGE_DNS_SERVERS struct pbdns_servers_check dns_check; -#endif +#endif #if PUBNUB_USE_MULTIPLE_ADDRESSES struct pubnub_multi_addresses spare_addresses; #endif +#if PUBNUB_USE_IPV6 + struct sockaddr_storage dns_addr; +#else /* PUBNUB_USE_IPV6 */ + struct sockaddr_in dns_addr; +#endif /* !PUBNUB_USE_IPV6 */ #endif /* defined(PUBNUB_CALLBACK_API) */ /** Subscribed channels and channel groups saved. Exist when auto heartbeat support is enabled. */ M_channelInfo() - - /** Pubnub context fields for heartbeat info used by the module for keeping presence. - Exist when auto heartbeat support is enabled. - */ - M_heartbeatInfo() + + /** Pubnub context fields for heartbeat info used by the module for + keeping presence. Exist when auto heartbeat support is enabled. + */ + M_heartbeatInfo() #if PUBNUB_PROXY_API - /** The type (protocol) of the proxy to use */ - enum pubnub_proxy_type proxy_type; + /** The type (protocol) of the proxy to use */ + enum pubnub_proxy_type proxy_type; /** Hostname (address) of the proxy server to use */ char proxy_hostname[PUBNUB_MAX_PROXY_HOSTNAME_LENGTH + 1]; @@ -507,7 +538,7 @@ struct pubnub_ { struct pubnub_ipv6_address proxy_ipv6_address; #endif #endif /* defined(PUBNUB_CALLBACK_API) */ - + /** The (TCP) port to use on the proxy. */ uint16_t proxy_port; @@ -547,17 +578,17 @@ struct pubnub_ { /** Authentication realm - received from the server */ char realm[PUBNUB_MAX_HTTP_AUTH_REALM + 1]; - /** Proxy 'authentication required' response message counter for repeating realm - within a single transaction. - At this point this field is of importance for Digest proxy authentication sheme. - See RFC 7616 - 5.4. Limited-Use Nonce Values : - ...For example, a server MAY choose to allow each nonce value to be used only once by - maintaining a record of whether, or not each recently issued nonce has been returned - and sending a next-nonce parameter in the Authentication-Info header field of every - response... - - Doing it (within the same transaction)repeatedly, without restrictions, would be a sign - of irregular behaviour. + /** Proxy 'authentication required' response message counter for repeating + realm within a single transaction. At this point this field is of + importance for Digest proxy authentication sheme. See RFC 7616 - 5.4. + Limited-Use Nonce Values : + ...For example, a server MAY choose to allow each nonce value to be used + only once by maintaining a record of whether, or not each recently issued + nonce has been returned and sending a next-nonce parameter in the + Authentication-Info header field of every response... + + Doing it (within the same transaction)repeatedly, without restrictions, + would be a sign of irregular behaviour. */ uint8_t auth_msg_count; @@ -569,7 +600,7 @@ struct pubnub_ { #endif /* PUBNUB_PROXY_API */ /** Crypto module for encryption and decryption */ - struct pubnub_crypto_provider_t *crypto_module; + struct pubnub_crypto_provider_t* crypto_module; #ifdef PUBNUB_NTF_RUNTIME_SELECTION /** The PubNub API enforcement policy. */ diff --git a/core/pubnub_netcore.c b/core/pubnub_netcore.c index 6822c208..64669e3e 100644 --- a/core/pubnub_netcore.c +++ b/core/pubnub_netcore.c @@ -40,15 +40,15 @@ #include -#define WATCH_ENUM_RESOLV_N_CONNECT(X) \ - do { \ - enum pbpal_resolv_n_connect_result x_ = (X); \ - PUBNUB_LOG(PUBNUB_LOG_LEVEL_DEBUG, \ - __FILE__ "(%d) in %s: `" #X "` = %d (%s)\n", \ - __LINE__, \ - __FUNCTION__, \ - x_, \ - pbpal_resolv_n_connect_res_2_string(x_)); \ +#define WATCH_ENUM_RESOLV_N_CONNECT(X) \ + do { \ + enum pbpal_resolv_n_connect_result x_ = (X); \ + PUBNUB_LOG(PUBNUB_LOG_LEVEL_DEBUG, \ + __FILE__ "(%d) in %s: `" #X "` = %d (%s)\n", \ + __LINE__, \ + __FUNCTION__, \ + x_, \ + pbpal_resolv_n_connect_res_2_string(x_)); \ } while (0) /** Each HTTP chunk has a trailining CRLF ("\r\n" in C-speak). That's @@ -78,7 +78,7 @@ bool HTTP_request_has_body(uint8_t method) { - switch(method) { + switch (method) { case pubnubSendViaGET: case pubnubUseDELETE: return false; @@ -90,8 +90,8 @@ bool HTTP_request_has_body(uint8_t method) #endif return true; default: - PUBNUB_LOG_ERROR("Error: HTTP_message_has_body(method): unhandled method: %u\n", - method); + PUBNUB_LOG_ERROR( + "Error: HTTP_message_has_body(method): unhandled method: %u\n", method); return false; } } @@ -99,7 +99,7 @@ bool HTTP_request_has_body(uint8_t method) static char const* get_method_verb_string(uint8_t method) { - switch(method) { + switch (method) { case pubnubSendViaGET: return "GET "; case pubnubSendViaPOST: @@ -115,8 +115,8 @@ static char const* get_method_verb_string(uint8_t method) case pubnubUseDELETE: return "DELETE "; default: - PUBNUB_LOG_ERROR("Error: get_method_verb_string(method): unhandled method: %u\n", - method); + PUBNUB_LOG_ERROR( + "Error: get_method_verb_string(method): unhandled method: %u\n", method); return "UNKOWN "; } } @@ -142,11 +142,12 @@ static int send_fin_head(struct pubnub_* pb) static bool should_keep_alive(struct pubnub_* pb, enum pubnub_res rslt) { - PUBNUB_LOG_DEBUG("should_keep_alive(pb=%p, rslt=%d('%s')) pb->flags.should_close = %d\n", - pb, - rslt, - pubnub_res_2_string(rslt), - (int)pb->flags.should_close); + PUBNUB_LOG_DEBUG( + "should_keep_alive(pb=%p, rslt=%d('%s')) pb->flags.should_close = %d\n", + pb, + rslt, + pubnub_res_2_string(rslt), + (int)pb->flags.should_close); if (!pb->flags.should_close) { #if PUBNUB_ADVANCED_KEEP_ALIVE time_t tt = time(NULL); @@ -261,40 +262,41 @@ static enum pubnub_res dont_parse(struct pbcc_context* p) } -static PFpbcc_parse_response_T m_aParseResponse[] = { dont_parse, - pbcc_parse_subscribe_response, - pbcc_parse_publish_response, - pbcc_parse_publish_response, /* PBTT_SIGNAL */ +static PFpbcc_parse_response_T m_aParseResponse[] = { + dont_parse, + pbcc_parse_subscribe_response, + pbcc_parse_publish_response, + pbcc_parse_publish_response, /* PBTT_SIGNAL */ #if PUBNUB_ONLY_PUBSUB_API - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse, - dont_parse + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse, + dont_parse #else pbcc_parse_presence_response, /* PBTT_LEAVE */ pbcc_parse_time_response, pbcc_parse_history_response, - pbcc_parse_presence_response, /* PBTT_HERENOW */ - pbcc_parse_presence_response, /* PBTT_GLOBAL_HERENOW */ - pbcc_parse_presence_response, /* PBTT_WHERENOW */ - pbcc_parse_presence_response, /* PBTT_SET_STATE */ - pbcc_parse_presence_response, /* PBTT_STATE_GET */ + pbcc_parse_presence_response, /* PBTT_HERENOW */ + pbcc_parse_presence_response, /* PBTT_GLOBAL_HERENOW */ + pbcc_parse_presence_response, /* PBTT_WHERENOW */ + pbcc_parse_presence_response, /* PBTT_SET_STATE */ + pbcc_parse_presence_response, /* PBTT_STATE_GET */ pbcc_parse_channel_registry_response, /* PBTT_REMOVE_CHANNEL_GROUP */ pbcc_parse_channel_registry_response, /* PBTT_REMOVE_CHANNEL_FROM_GROUP */ pbcc_parse_channel_registry_response, /* PBTT_ADD_CHANNEL_TO_GROUP */ pbcc_parse_channel_registry_response, /* PBTT_LIST_CHANNEL_GROUP */ - pbcc_parse_presence_response /* PBTT_HEARTBEAT */ + pbcc_parse_presence_response /* PBTT_HEARTBEAT */ #if PUBNUB_USE_SUBSCRIBE_V2 , pbcc_parse_subscribe_v2_response /* PBTT_SUBSCRIBE_V2 */ #endif @@ -356,7 +358,7 @@ static enum pubnub_res parse_pubnub_result(struct pubnub_* pb) else { pbauto_heartbeat_start_timer(pb); } - + return pbres; } @@ -575,7 +577,7 @@ int pbnc_fsm(struct pubnub_* pb) pb->state = PBS_WAIT_CONNECT; break; case pbpal_connect_success: - i = pbntf_got_socket(pb); + i = pbntf_got_socket(pb); pb->state = PBS_CONNECTED; break; default: @@ -724,8 +726,10 @@ int pbnc_fsm(struct pubnub_* pb) case pbtlsStartedWaitRead: case pbtlsStartedWaitWrite: pb->state = PBS_WAIT_TLS_CONNECT; - if (pbtlsStartedWaitWrite == res) pbntf_watch_out_events(pb); - if (pbtlsStartedWaitRead == res) pbntf_watch_in_events(pb); + if (pbtlsStartedWaitWrite == res) + pbntf_watch_out_events(pb); + if (pbtlsStartedWaitRead == res) + pbntf_watch_in_events(pb); return 0; case pbtlsFailed: if (pb->options.fallbackSSL) { @@ -891,7 +895,7 @@ int pbnc_fsm(struct pubnub_* pb) } else { char const* o = PUBNUB_ORIGIN_SETTABLE ? pb->origin : PUBNUB_ORIGIN; - pb->state = PBS_TX_HOST; + pb->state = PBS_TX_HOST; if ((i < 0) || (-1 == pbpal_send_str(pb, o))) { outcome_detected(pb, PNR_IO_ERROR); break; @@ -985,8 +989,7 @@ int pbnc_fsm(struct pubnub_* pb) #endif ) { char hedr[128] = "\r\n"; - pbcc_via_post_headers( - &(pb->core), hedr + 2, sizeof hedr - 2); + pbcc_via_post_headers(&(pb->core), hedr + 2, sizeof hedr - 2); PUBNUB_LOG_TRACE( "Sending HTTP 'via POST, or PATCH' headers: '%s'\n", hedr); pb->state = PBS_TX_EXTRA_HEADERS; @@ -1079,8 +1082,8 @@ int pbnc_fsm(struct pubnub_* pb) /** Reset failed request `Retry-After` HTTP header value. */ pb->http_header_retry_after = 0; #endif // #if PUBNUB_USE_RETRY_CONFIGURATION - pb->http_chunked = false; - pb->state = PBS_RX_HEADERS; + pb->http_chunked = false; + pb->state = PBS_RX_HEADERS; goto next_state; case PNR_CONNECTION_TIMEOUT: case PNR_IO_ERROR: @@ -1162,10 +1165,12 @@ int pbnc_fsm(struct pubnub_* pb) } goto next_state; } - if (pb_strncasecmp(pb->core.http_buf, h_chunked, sizeof h_chunked - 1) == 0) { + if (pb_strncasecmp(pb->core.http_buf, h_chunked, sizeof h_chunked - 1) + == 0) { pb->http_chunked = true; } - else if (pb_strncasecmp(pb->core.http_buf, h_length, sizeof h_length - 1) == 0) { + else if (pb_strncasecmp(pb->core.http_buf, h_length, sizeof h_length - 1) + == 0) { size_t len = atoi(pb->core.http_buf + sizeof h_length - 1); if (0 != pbcc_realloc_reply_buffer(&pb->core, len)) { outcome_detected(pb, PNR_REPLY_TOO_BIG); @@ -1174,15 +1179,20 @@ int pbnc_fsm(struct pubnub_* pb) pb->core.http_content_len = len; } #if PUBNUB_USE_RETRY_CONFIGURATION - else if (pb_strncasecmp(pb->core.http_buf, h_retry_after, sizeof h_retry_after - 1) == 0) { - pb->http_header_retry_after = atoi(pb->core.http_buf + sizeof h_retry_after - 1); + else if (pb_strncasecmp( + pb->core.http_buf, h_retry_after, sizeof h_retry_after - 1) + == 0) { + pb->http_header_retry_after = + atoi(pb->core.http_buf + sizeof h_retry_after - 1); } #endif // #if PUBNUB_USE_RETRY_CONFIGURATION - else if (pb_strncasecmp(pb->core.http_buf, h_close, sizeof h_close - 1) == 0) { + else if (pb_strncasecmp(pb->core.http_buf, h_close, sizeof h_close - 1) + == 0) { pb->flags.should_close = true; } #if PUBNUB_RECEIVE_GZIP_RESPONSE - else if (pb_strncasecmp(pb->core.http_buf, h_encoding, sizeof h_encoding - 1) + else if (pb_strncasecmp( + pb->core.http_buf, h_encoding, sizeof h_encoding - 1) == 0) { pb->data_compressed = compressionGZIP; } @@ -1260,7 +1270,7 @@ int pbnc_fsm(struct pubnub_* pb) } else if (0 != pbcc_realloc_reply_buffer( - &pb->core, pb->core.http_buf_len + chunk_length)) { + &pb->core, pb->core.http_buf_len + chunk_length)) { outcome_detected(pb, PNR_REPLY_TOO_BIG); } else { @@ -1379,7 +1389,7 @@ int pbnc_fsm(struct pubnub_* pb) break; } pb->state = PBS_TX_GET; - i = pbpal_send_str(pb, get_method_verb_string(pb->method)); + i = pbpal_send_str(pb, get_method_verb_string(pb->method)); if (i < 0) { pb->state = close_kept_alive_connection(pb); } @@ -1434,27 +1444,27 @@ void pbnc_stop(struct pubnub_* pbp, enum pubnub_res outcome_to_report) #if defined(PUBNUB_NTF_RUNTIME_SELECTION) if (PNA_CALLBACK == pbp->api_policy) { #endif - if (PNR_TIMEOUT == outcome_to_report) { - if ((pbp->state != PBS_WAIT_CONNECT) && + if (PNR_TIMEOUT == outcome_to_report) { + if ((pbp->state != PBS_WAIT_CONNECT) && #if PUBNUB_CHANGE_DNS_SERVERS - (pbpal_dns_rotate_server(pbp) == 0) + (pbpal_dns_rotate_server(pbp) == 0) #else (pbp->flags.sent_queries < PUBNUB_MAX_DNS_QUERIES) #endif /* PUBNUB_CHANGE_DNS_SERVERS */ - ) { - pbp->state = PBS_WAIT_CANCEL_DNS; + ) { + pbp->state = PBS_WAIT_CANCEL_DNS; + } + else { + pbp->core.last_result = (PBS_WAIT_CONNECT == pbp->state) + ? PNR_WAIT_CONNECT_TIMEOUT + : PNR_ADDR_RESOLUTION_FAILED; + pbp->state = PBS_WAIT_CANCEL; + } } else { - pbp->core.last_result = (PBS_WAIT_CONNECT == pbp->state) - ? PNR_WAIT_CONNECT_TIMEOUT - : PNR_ADDR_RESOLUTION_FAILED; pbp->state = PBS_WAIT_CANCEL; } - } - else { - pbp->state = PBS_WAIT_CANCEL; - } - pbntf_requeue_for_processing(pbp); + pbntf_requeue_for_processing(pbp); #if defined(PUBNUB_NTF_RUNTIME_SELECTION) } /* if api_policy */ #endif diff --git a/core/samples/publish_callback_subloop_sample.c b/core/samples/publish_callback_subloop_sample.c index 5ff85d38..ba03fac1 100644 --- a/core/samples/publish_callback_subloop_sample.c +++ b/core/samples/publish_callback_subloop_sample.c @@ -128,13 +128,13 @@ static void callback_sample_free(pubnub_t* pb) int main() { - const unsigned minutes_in_loop = 1; - char const* chan = "hello_world"; - pubnub_t* pbp = pubnub_alloc(); - pubnub_t* pbp_2 = pubnub_alloc(); - enum pubnub_res result; + const unsigned minutes_in_loop = 1; + char const* chan = "hello_world"; + pubnub_t* pbp = pubnub_alloc(); + pubnub_t* pbp_2 = pubnub_alloc(); + enum pubnub_res result; struct pubnub_ipv4_address o_ipv4[3]; - pubnub_subloop_t* pbsld; + pubnub_subloop_t* pbsld; if (NULL == pbp) { printf("Failed to allocate Pubnub context!\n"); @@ -148,7 +148,7 @@ int main() pubnub_register_callback(pbp_2, publish_callback, (void*)chan); - paint_text_white(); + paint_text_white(); //! [Define subscribe loop] pbsld = pubnub_subloop_define( pbp, chan, pubnub_subscribe_defopts(), subloop_callback); diff --git a/core/samples/subscribe_publish_from_callback.c b/core/samples/subscribe_publish_from_callback.c index 718ab788..17a223b4 100644 --- a/core/samples/subscribe_publish_from_callback.c +++ b/core/samples/subscribe_publish_from_callback.c @@ -27,7 +27,7 @@ static char const* m_chan = "hello_world"; static void wait_seconds(double time_in_seconds) { - time_t start = time(NULL); + time_t start = time(NULL); double time_passed_in_seconds; do { time_passed_in_seconds = difftime(time(NULL), start); @@ -36,7 +36,7 @@ static void wait_seconds(double time_in_seconds) static void wait_useconds(unsigned long time_in_microseconds) { - clock_t start = clock(); + clock_t start = clock(); unsigned long time_passed_in_microseconds; do { time_passed_in_microseconds = clock() - start; @@ -257,7 +257,7 @@ int main() printf("Failed to read system DNS server, will use default %s\n", PUBNUB_DEFAULT_DNS_SERVER); } - + pubnub_mutex_init_static(m_lock); pubnub_set_transaction_timeout(pbp, 5000); @@ -278,7 +278,7 @@ int main() /* Awaiting subscribe/connect */ do { pubnub_mutex_lock(m_lock); - if(m_first_subscribe_done) { + if (m_first_subscribe_done) { pubnub_mutex_unlock(m_lock); break; } @@ -291,9 +291,8 @@ int main() puts("-----------------------"); puts("Publishing1..."); puts("-----------------------"); - res = pubnub_publish(pbp_2, - chan1, - "\"[1]Hello world from 'subscribe-publish from callback' sample!\""); + res = pubnub_publish( + pbp_2, chan1, "\"[1]Hello world from 'subscribe-publish from callback' sample!\""); if (res != PNR_STARTED) { printf("pubnub_publish1() returned unexpected: %d('%s')\n", res, @@ -303,8 +302,8 @@ int main() } /* Time to play. Turning internet connection 'off' and then back 'on'. - Starting everything disconnected and then connecting to internet at some point, - and so on... + Starting everything disconnected and then connecting to internet at some + point, and so on... */ wait_seconds(200); diff --git a/core/test/pubnub_config.h b/core/test/pubnub_config.h index c05e9286..00425407 100644 --- a/core/test/pubnub_config.h +++ b/core/test/pubnub_config.h @@ -1,6 +1,6 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #if !defined INC_PUBNUB_CONFIG -#define INC_PUBNUB_CONFIG +#define INC_PUBNUB_CONFIG /* -- Next few definitions can be tweaked by the user, but with care -- */ @@ -46,7 +46,7 @@ /** If defined, the PubNub implementation will not try to catch-up on * messages it could miss while subscribe failed with an IO error or - * such. Use this if missing some messages is not a problem. + * such. Use this if missing some messages is not a problem. * * @note messages may sometimes still be lost due to potential @ref * PUBNUB_REPLY_MAXLEN overrun issue */ @@ -55,7 +55,7 @@ /** This is the URL of the Pubnub server. Change only for testing purposes. */ -#define PUBNUB_ORIGIN "pubsub.pubnub.com" +#define PUBNUB_ORIGIN "pubsub.pubnub.com" /** The maximum length (in characters) of the host name of the proxy that will be saved in the Pubnub context. @@ -73,6 +73,9 @@ #define PUBNUB_USE_MDNS 1 #endif +#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" +#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" + #if defined(PUBNUB_CALLBACK_API) #if !defined(PUBNUB_USE_IPV6) /** If true (!=0), enable support for Ipv6 network addresses */ @@ -103,13 +106,16 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif -#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" - /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS +#if PUBNUB_USE_IPV6 +/** Maximum number of DNS servers list rotation in a single transaction */ +#define PUBNUB_MAX_DNS_ROTATION 5 +#else /* PUBNUB_USE_IPV6 */ /** Maximum number of DNS servers list rotation in a single transaction */ #define PUBNUB_MAX_DNS_ROTATION 3 +#endif /* !PUBNUB_USE_IPV6 */ #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif /* defined(PUBNUB_CALLBACK_API) */ @@ -122,14 +128,14 @@ #define PUBNUB_USE_LOG_CALLBACK 0 #endif -#define PUBNUB_DEFAULT_TRANSACTION_TIMER 310000 +#define PUBNUB_DEFAULT_TRANSACTION_TIMER 310000 #define PUBNUB_MIN_TRANSACTION_TIMER 200 /** Duration of the 'wait_connect_TCP_socket' timeout set during context initialization, in milliseconds. Can be changed later by the user. */ -#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 +#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 /** Mininmal duration of the 'wait_connect_TCP_socket' timer, in milliseconds. * You can't set less than this. diff --git a/cpp/pubnub_common.hpp b/cpp/pubnub_common.hpp index 1c3c37b7..768c811b 100644 --- a/cpp/pubnub_common.hpp +++ b/cpp/pubnub_common.hpp @@ -236,10 +236,10 @@ class subscribe_options { */ class subscribe_v2_options { pubnub_subscribe_v2_options d_; - std::string d_chgrp; - std::string d_filter_expr; - std::string d_timetoken; - + std::string d_chgrp; + std::string d_filter_expr; + std::string d_timetoken; + public: subscribe_v2_options() { d_ = pubnub_subscribe_v2_defopts(); } subscribe_v2_options& channel_group(std::string const& chgroup) @@ -259,16 +259,17 @@ class subscribe_v2_options { } subscribe_v2_options& filter_expr(std::string const& filter_exp) { - d_filter_expr = filter_exp; + d_filter_expr = filter_exp; d_.filter_expr = d_filter_expr.empty() ? 0 : d_filter_expr.c_str(); return *this; } subscribe_v2_options& timetoken(std::string const& timetoken) { - d_timetoken = timetoken; + d_timetoken = timetoken; if (d_timetoken.empty()) { d_.timetoken[0] = '\0'; - } else { + } + else { std::strncpy(d_.timetoken, d_timetoken.c_str(), sizeof(d_.timetoken) - 1); d_.timetoken[d_timetoken.size()] = '\0'; } @@ -426,11 +427,14 @@ class history_options { class set_state_options { pubnub_set_state_options d_; - std::string d_channel_group; - std::string d_user_id; + std::string d_channel_group; + std::string d_user_id; public: - set_state_options() : d_(pubnub_set_state_options()) {} + set_state_options() + : d_(pubnub_set_state_options()) + { + } set_state_options& channel_group(std::string const& channel_group) { @@ -447,7 +451,7 @@ class set_state_options { set_state_options& user_id(std::string const& user_id) { - d_user_id = user_id; + d_user_id = user_id; d_.user_id = d_user_id.empty() ? 0 : d_user_id.c_str(); return *this; @@ -468,18 +472,20 @@ class set_state_options { class include_options { char d_include_c_strings_array[MAX_INCLUDE_DIMENSION][MAX_ELEM_LENGTH + 1]; size_t d_include_count; - + public: include_options() : d_include_count(0) - {} + { + } char const** include_c_strings_array() { - return (d_include_count > 0) ? (char const**)d_include_c_strings_array : NULL; + return (d_include_count > 0) ? (char const**)d_include_c_strings_array + : NULL; } char const** include_to_c_strings_array(std::vector const& inc) { - size_t n = inc.size(); + size_t n = inc.size(); unsigned i; if (n > MAX_INCLUDE_DIMENSION) { throw std::range_error("include parameter has too many elements."); @@ -499,7 +505,7 @@ class include_options { } size_t include_count() { return d_include_count; } }; - + /** A wrapper class for objects api paging option parameters, enabling a nicer usage. Something like: pbp.get_users(list_options().start(last_bookmark)); @@ -509,41 +515,48 @@ class include_options { */ class list_options { std::string d_include; - size_t d_limit; + size_t d_limit; std::string d_start; std::string d_end; - tribool d_count; + tribool d_count; public: list_options() : d_limit(0) , d_count(tribool::not_set) - {} + { + } list_options& include(std::string const& incl) { d_include = incl; return *this; } - char const* include() { return (d_include.size() > 0) ? d_include.c_str() : NULL; } + char const* include() + { + return (d_include.size() > 0) ? d_include.c_str() : NULL; + } list_options& limit(size_t lim) { d_limit = lim; return *this; } - size_t limit() { return d_limit; } + size_t limit() { return d_limit; } list_options& start(std::string const& st) { d_start = st; return *this; } - char const* start() { return (d_start.size() > 0) ? d_start.c_str() : NULL; } + char const* start() + { + return (d_start.size() > 0) ? d_start.c_str() : NULL; + } list_options& end(std::string const& e) { d_end = e; return *this; } - char const* end() { return (d_end.size() > 0) ? d_end.c_str() : NULL; } + char const* end() { return (d_end.size() > 0) ? d_end.c_str() : NULL; } list_options& count(tribool co) { d_count = co; @@ -560,43 +573,43 @@ class list_options { return pbccNotSet; } }; -#endif /* PUBNUB_USE_OBJECTS_API */ +#endif /* PUBNUB_USE_OBJECTS_API */ #if PUBNUB_CRYPTO_API /** Interface for a cryptor. It is an algorithm class that * provides encryption and decryption of arrays of bytes. * - * It is used by the C++ Pubnub context to encrypt and decrypt messages + * It is used by the C++ Pubnub context to encrypt and decrypt messages * sent and received from Pubnub. * * Note that name comes from Rust, where it is used to convert from type to type. * The pointer returned by `into_cryptor_ptr()` should be allocated with `new`. * - * @see pubnub_cryptor -*/ + * @see pubnub_cryptor + */ class into_cryptor_ptr { public: - ~into_cryptor_ptr() { } + ~into_cryptor_ptr() {} virtual pubnub_cryptor_t* into_cryptor() = 0; }; -/** Interface for a crypto provider. It is used to encrypt and decrypt - * messages sent and received from Pubnub. It makes a conversion from +/** Interface for a crypto provider. It is used to encrypt and decrypt + * messages sent and received from Pubnub. It makes a conversion from * your cryptographic module to the C interface used by the C++ Pubnub context. * - * It is used by the C++ Pubnub context to encrypt and decrypt messages + * It is used by the C++ Pubnub context to encrypt and decrypt messages * sent and received from Pubnub. * * Note that name comes from Rust, where it is used to convert from type to type. * The pointer returned by `into_provider_ptr()` should be allocated with `new`. * * @see pubnub_crypto_provider_t -*/ + */ class into_crypto_provider_ptr { public: - ~into_crypto_provider_ptr() { } + ~into_crypto_provider_ptr() {} virtual pubnub_crypto_provider_t* into_provider() = 0; }; @@ -604,12 +617,12 @@ class into_crypto_provider_ptr { /** Default implementation of the cryptoprovider. * - * It implements the "into_crypto_provider_ptr" interface. + * It implements the "into_crypto_provider_ptr" interface. * - * It is used by the C++ Pubnub context to encrypt and decrypt messages + * It is used by the C++ Pubnub context to encrypt and decrypt messages * sent and received from Pubnub. * - * It is designed to behave like the default PubNub crypto module + * It is designed to behave like the default PubNub crypto module * returned by `pubnub_crypto_aes_cbc_module_init()`, * `pubnub_crypto_legacy_module_init()` and `pubnub_crypto_module_init()` functions. * @@ -617,8 +630,8 @@ class into_crypto_provider_ptr { * @see pubnub_crypto_aes_cbc_module_init * @see pubnub_crypto_legacy_module_init * @see pubnub_crypto_module_init -*/ -class crypto_module: public into_crypto_provider_ptr { + */ +class crypto_module : public into_crypto_provider_ptr { public: /* Wrapping constructor for the `pubnub_crypto_provider_t` structure. * @@ -627,7 +640,10 @@ class crypto_module: public into_crypto_provider_ptr { * * @param crypto_module The `pubnub_crypto_provider_t` structure to wrap. */ - crypto_module(pubnub_crypto_provider_t* crypto_module) : d_module(crypto_module) {} + crypto_module(pubnub_crypto_provider_t* crypto_module) + : d_module(crypto_module) + { + } /* Constructor that translates C++ data into the `pubnub_crypto_provider_init` function. * @@ -638,21 +654,24 @@ class crypto_module: public into_crypto_provider_ptr { * * @param cryptor The `pubnub_cryptor_t` structure to wrap. */ - crypto_module(into_cryptor_ptr &default_cryptor, std::vector &additional_cryptors) + crypto_module(into_cryptor_ptr& default_cryptor, + std::vector& additional_cryptors) { - size_t size = additional_cryptors.size(); - pubnub_cryptor_t* cryptors = new pubnub_cryptor_t[sizeof(pubnub_cryptor_t*) * size]; + size_t size = additional_cryptors.size(); + pubnub_cryptor_t* cryptors = + new pubnub_cryptor_t[sizeof(pubnub_cryptor_t*) * size]; for (size_t i = 0; i < size; ++i) { pubnub_cryptor_t* cryptor = additional_cryptors[i]->into_cryptor(); - cryptors[i] = *cryptor; + cryptors[i] = *cryptor; } - this->d_module = pubnub_crypto_module_init(default_cryptor.into_cryptor(), cryptors, size); + this->d_module = pubnub_crypto_module_init( + default_cryptor.into_cryptor(), cryptors, size); } /* Constructor that creates C++ `crypto_module` object that mimics - * the `pubnub_crypto_aes_cbc_module_init` function. + * the `pubnub_crypto_aes_cbc_module_init` function. * * @param cryptor The `pubnub_cryptor_t` structure to wrap.(it doesn't own the key - keep it alive) * @@ -662,11 +681,12 @@ class crypto_module: public into_crypto_provider_ptr { */ static crypto_module aes_cbc(std::string& cipher_key) { - return crypto_module(pubnub_crypto_aes_cbc_module_init((uint8_t*)(cipher_key.c_str()))); + return crypto_module( + pubnub_crypto_aes_cbc_module_init((uint8_t*)(cipher_key.c_str()))); } /* Constructor that creates C++ `crypto_module` object that mimics - * the `pubnub_crypto_legacy_module_init` function. + * the `pubnub_crypto_legacy_module_init` function. * * @param cryptor The `pubnub_cryptor_t` structure to wrap. (it doesn't own the key - keep it alive) * @@ -676,7 +696,8 @@ class crypto_module: public into_crypto_provider_ptr { */ static crypto_module legacy(std::string& cipher_key) { - return crypto_module(pubnub_crypto_legacy_module_init((uint8_t*)(cipher_key.c_str()))); + return crypto_module( + pubnub_crypto_legacy_module_init((uint8_t*)(cipher_key.c_str()))); } /* Encrypts the @p to_encrypt buffer and returns the encrypted @@ -686,15 +707,18 @@ class crypto_module: public into_crypto_provider_ptr { * * @return The encrypted buffer */ - std::vector encrypt(std::vector& to_encrypt) { + std::vector encrypt(std::vector& to_encrypt) + { pubnub_bymebl_t to_encrypt_c; - to_encrypt_c.ptr = to_encrypt.data(); + to_encrypt_c.ptr = to_encrypt.data(); to_encrypt_c.size = to_encrypt.size(); - pubnub_bymebl_t result = this->d_module->encrypt(this->d_module, to_encrypt_c); + pubnub_bymebl_t result = + this->d_module->encrypt(this->d_module, to_encrypt_c); if (result.ptr == nullptr) { - throw std::runtime_error("crypto_module::encrypt: Encryption failed!"); + throw std::runtime_error( + "crypto_module::encrypt: Encryption failed!"); } return std::vector(result.ptr, result.ptr + result.size); @@ -707,23 +731,24 @@ class crypto_module: public into_crypto_provider_ptr { * * @return The decrypted buffer */ - std::vector decrypt(std::vector& to_decrypt) { + std::vector decrypt(std::vector& to_decrypt) + { pubnub_bymebl_t to_decrypt_c; - to_decrypt_c.ptr = to_decrypt.data(); + to_decrypt_c.ptr = to_decrypt.data(); to_decrypt_c.size = to_decrypt.size(); - pubnub_bymebl_t result = this->d_module->decrypt(this->d_module, to_decrypt_c); + pubnub_bymebl_t result = + this->d_module->decrypt(this->d_module, to_decrypt_c); if (result.ptr == nullptr) { - throw std::runtime_error("crypto_module::decrypt: Decryption failed!"); + throw std::runtime_error( + "crypto_module::decrypt: Decryption failed!"); } return std::vector(result.ptr, result.ptr + result.size); } - pubnub_crypto_provider_t* into_provider() { - return this->d_module; - } + pubnub_crypto_provider_t* into_provider() { return this->d_module; } private: pubnub_crypto_provider_t* d_module; @@ -809,7 +834,7 @@ class context { return pubnub_port_set(d_pb, port); } - /** Sets the secret key to @p secret_key. If @p secret_key is an + /** Sets the secret key to @p secret_key. If @p secret_key is an empty string, `secret_key` will not be used. @see pubnub_set_secret_key */ @@ -817,14 +842,17 @@ class context { { lock_guard lck(d_mutex); if (!pubnub_can_start_transaction(d_pb)) { - throw std::logic_error("setting 'secret_key' key while transaction in progress"); + throw std::logic_error( + "setting 'secret_key' key while transaction in progress"); } d_secret_key = secret_key; - #if PUBNUB_CRYPTO_API - pubnub_set_secret_key(d_pb, secret_key.empty() ? NULL : d_secret_key.c_str()); - #else - throw std::logic_error("PUBNUB_CRYPTO_API is not enabled to use 'secret_key'"); - #endif +#if PUBNUB_CRYPTO_API + pubnub_set_secret_key(d_pb, + secret_key.empty() ? NULL : d_secret_key.c_str()); +#else + throw std::logic_error( + "PUBNUB_CRYPTO_API is not enabled to use 'secret_key'"); +#endif } /// Returns the current `secret_key` for this context @@ -842,7 +870,8 @@ class context { { lock_guard lck(d_mutex); if (!pubnub_can_start_transaction(d_pb)) { - throw std::logic_error("setting 'auth' key while transaction in progress"); + throw std::logic_error( + "setting 'auth' key while transaction in progress"); } d_auth = auth; pubnub_set_auth(d_pb, auth.empty() ? NULL : d_auth.c_str()); @@ -862,7 +891,8 @@ class context { { lock_guard lck(d_mutex); if (!pubnub_can_start_transaction(d_pb)) { - throw std::logic_error("setting 'auth_token' while transaction in progress"); + throw std::logic_error( + "setting 'auth_token' while transaction in progress"); } d_auth_token = token; pubnub_set_auth_token(d_pb, token.empty() ? NULL : d_auth_token.c_str()); @@ -873,7 +903,7 @@ class context { lock_guard lck(d_mutex); return d_auth_token; } - + /** sets the user_id to @p uuid. if @p uuid is an empty string, user_id will not be used. @@ -898,11 +928,11 @@ class context { } /// Set the user_id with a random-generated UUID - /// + /// /// @deprecated random generated uuid/user_id is deprecated. /// /// @see pubnub_generate_uuid_v4_random - PUBNUB_DEPRECATED int set_uuid_v4_random() + PUBNUB_DEPRECATED int set_uuid_v4_random() { struct Pubnub_UUID uuid; if (0 != pubnub_generate_uuid_v4_random(&uuid)) { @@ -917,10 +947,7 @@ class context { @deprecated this is provided as a workaround for existing users. Please use `user_id` instead. */ - PUBNUB_DEPRECATED std::string const uuid() const - { - return user_id(); - } + PUBNUB_DEPRECATED std::string const uuid() const { return user_id(); } /// Returns the current user_id std::string const user_id() const @@ -953,17 +980,14 @@ class context { #if PUBNUB_USE_SUBSCRIBE_V2 /// Returns the next v2 message from the context. If there are /// none, returns an empty message structure(checked through - /// v2_mesage::is_empty()). + /// v2_mesage::is_empty()). /// @see pubnub_get_v2 - v2_message get_v2() const - { - return v2_message(pubnub_get_v2(d_pb)); - } + v2_message get_v2() const { return v2_message(pubnub_get_v2(d_pb)); } /// Returns a vector of all v2 messages from the context. std::vector get_all_v2() const { std::vector all; - v2_message msg = get_v2(); + v2_message msg = get_v2(); while (!msg.is_empty()) { all.push_back(msg); @@ -1031,13 +1055,10 @@ class context { return all; } - /// Cancels the transaction, if any is ongoing, or connection is 'kept alive'. - /// If none is ongoing, it is ignored. + /// Cancels the transaction, if any is ongoing, or connection is 'kept + /// alive'. If none is ongoing, it is ignored. /// @see pubnub_cancel - enum pubnub_cancel_res cancel() - { - return pubnub_cancel(d_pb); - } + enum pubnub_cancel_res cancel() { return pubnub_cancel(d_pb); } /// Publishes a @p message on the @p channel. The @p channel /// can have many channels separated by a comma @@ -1053,7 +1074,8 @@ class context { std::string const& message, publish_options opt) { - return doit(pubnub_publish_ex(d_pb, channel.c_str(), message.c_str(), opt.data())); + return doit(pubnub_publish_ex( + d_pb, channel.c_str(), message.c_str(), opt.data())); } /// Sends a signal @p message on the @p channel. @@ -1062,7 +1084,7 @@ class context { { return doit(pubnub_signal(d_pb, channel.c_str(), message.c_str())); } - + #if PUBNUB_CRYPTO_API /// Publishes a @p message on the @p channel encrypted with @p /// cipher_key. @@ -1121,7 +1143,8 @@ class context { /// Pass a vector of channels in the @p channel and we'll put /// commas between them. A helper function. - futres subscribe_v2(std::vector const& channel, subscribe_v2_options opt) + futres subscribe_v2(std::vector const& channel, + subscribe_v2_options opt) { return subscribe_v2(join(channel), opt); } @@ -1147,10 +1170,7 @@ class context { /// Starts a "get time" transaction /// @see pubnub_time - futres time() - { - return doit(pubnub_time(d_pb)); - } + futres time() { return doit(pubnub_time(d_pb)); } /// Starts a transaction to get message history for @p channel /// with the limit of max @p count @@ -1161,10 +1181,8 @@ class context { unsigned count = 100, bool include_token = false) { - return doit(pubnub_history(d_pb, - channel.empty() ? 0 : channel.c_str(), - count, - include_token)); + return doit(pubnub_history( + d_pb, channel.empty() ? 0 : channel.c_str(), count, include_token)); } /// Starts a "history" with extended (full) options @@ -1206,29 +1224,30 @@ class context { /// the given @p timetoken futres message_counts(std::string const& channel, std::string const& timetoken) { - return doit(pubnub_message_counts(d_pb, - channel.empty() ? 0 : channel.c_str(), - timetoken.empty() ? 0 : timetoken.c_str())); + return doit( + pubnub_message_counts(d_pb, + channel.empty() ? 0 : channel.c_str(), + timetoken.empty() ? 0 : timetoken.c_str())); } - + /// Starts 'advanced history' pubnub_message_counts operation /// for unread messages on @p channel(channel list) starting from /// the given @p timetoken futres message_counts(std::vector const& channel, - std::string const& timetoken) + std::string const& timetoken) { return message_counts(join(channel), timetoken); } - + /// Starts 'advanced history' pubnub_message_counts operation /// for unread messages on @p channel(channel list) starting from /// the given @p channel_timetokens(per channel) - futres message_counts(std::string const& channel, + futres message_counts(std::string const& channel, std::vector const& channel_timetokens) { return message_counts(channel, join(channel_timetokens)); } - + /// Starts 'advanced history' pubnub_message_counts operation /// for unread messages on @p channel(channel list) starting from /// the given @p channel_timetokens(per channel) @@ -1237,18 +1256,17 @@ class context { { return message_counts(join(channel), channel_timetokens); } - + /// Starts 'advanced history' pubnub_message_counts operation /// for unread messages on @p channel_timetokens(channel, ch_timetoken pairs) - futres message_counts( - std::vector > const& channel_timetokens) + futres message_counts(std::vector> const& channel_timetokens) { std::string ch_list(""); std::string tt_list(""); - unsigned n = channel_timetokens.empty() ? 0 : channel_timetokens.size(); - unsigned i; + unsigned n = channel_timetokens.empty() ? 0 : channel_timetokens.size(); + unsigned i; for (i = 0; i < n; i++) { - std::string separator = ((i+1) < n) ? "," : ""; + std::string separator = ((i + 1) < n) ? "," : ""; ch_list += channel_timetokens[i].first + separator; tt_list += channel_timetokens[i].second + separator; } @@ -1259,15 +1277,16 @@ class context { /// 'advanced history' pubnub_message_counts operation std::map get_channel_message_counts() { - std::map map; + std::map map; std::vector chan_msg_counters; - int i; + int i; int count = pubnub_get_chan_msg_counts_size(d_pb); if (count <= 0) { return map; } chan_msg_counters = std::vector(count); - if (pubnub_get_chan_msg_counts(d_pb, (size_t*)&count, &chan_msg_counters[0]) != 0) { + if (pubnub_get_chan_msg_counts(d_pb, (size_t*)&count, &chan_msg_counters[0]) + != 0) { return map; } for (i = 0; i < count; i++) { @@ -1277,7 +1296,7 @@ class context { } return map; } - + /// Starts a transaction to inform Pubnub we're working /// on a @p channel and/or @p channel_group /// @see pubnub_heartbeat @@ -1335,10 +1354,7 @@ class context { /// Starts a transaction to get a list of currently present /// UUIDs on all channels /// @see pubnub_global_here_now - futres global_here_now() - { - return doit(pubnub_global_here_now(d_pb)); - } + futres global_here_now() { return doit(pubnub_global_here_now(d_pb)); } /// Starts a transaction to get a list of channels the @p uuid /// is currently present on. If @p uuid is not given (or is an @@ -1373,25 +1389,25 @@ class context { return set_state(join(channel), join(channel_group), uuid, state); } - /// Starts a transaction to set the @p state JSON object with selected - /// @options for the given @p channel and/or channel groups chosen + /// Starts a transaction to set the @p state JSON object with selected + /// @options for the given @p channel and/or channel groups chosen /// in options. /// @see pubnub_set_state_ex futres set_state(std::string const& channel, std::string const& state, - set_state_options options) + set_state_options options) { char const* ch = channel.empty() ? 0 : channel.c_str(); return doit(pubnub_set_state_ex(d_pb, ch, state.c_str(), options.data())); } - - /// Starts a transaction to set the @p state JSON object with selected + + /// Starts a transaction to set the @p state JSON object with selected /// @options for the given @p channels passed as a vector and/or channel /// groups chosen in options. /// @see pubnub_set_state_ex futres set_state(std::vector const& channel, - std::string const& state, - set_state_options options) + std::string const& state, + set_state_options options) { return set_state(join(channel), state, options); } @@ -1471,12 +1487,10 @@ class context { /// @see pubnub_grant_token futres grant_token(std::string const& grant_obj) { - return doit(pubnub_grant_token( - d_pb, - grant_obj.c_str())); + return doit(pubnub_grant_token(d_pb, grant_obj.c_str())); } - /// Returns grant token + /// Returns grant token /// @see pubnub_get_grant_token() std::string get_grant_token() { @@ -1488,20 +1502,18 @@ class context { /// @see pubnub_grant_token std::string parse_token(std::string const& token) { - char* parsed_token_chars = pubnub_parse_token(d_pb, token.c_str()); + char* parsed_token_chars = pubnub_parse_token(d_pb, token.c_str()); std::string owned_parsed_token(parsed_token_chars); - - ::free(parsed_token_chars); - return owned_parsed_token; + + ::free(parsed_token_chars); + return owned_parsed_token; } #endif #if PUBNUB_USE_REVOKE_TOKEN_API futres revoke_token(std::string const& token) { - return doit( - pubnub_revoke_token(d_pb, token.c_str()) - ); + return doit(pubnub_revoke_token(d_pb, token.c_str())); } std::string get_revoke_token_result() @@ -1518,34 +1530,29 @@ class context { /// @see pubnub_getall_uuidmetadata futres getall_uuidmetadata(list_options& options) { - return doit(pubnub_getall_uuidmetadata( - d_pb, - options.include(), - options.limit(), - options.start(), - options.end(), - options.count())); + return doit(pubnub_getall_uuidmetadata(d_pb, + options.include(), + options.limit(), + options.start(), + options.end(), + options.count())); } /// Starts a transaction that creates a user with the attributes specified in @p user_obj. /// @see pubnub_set_uuidmetadata - futres set_uuidmetadata(std::string const& uuid_metadataid, std::string const& include, std::string const& user_obj) + futres set_uuidmetadata(std::string const& uuid_metadataid, + std::string const& include, + std::string const& user_obj) { return doit(pubnub_set_uuidmetadata( - d_pb, - uuid_metadataid.c_str(), - include.c_str(), - user_obj.c_str())); + d_pb, uuid_metadataid.c_str(), include.c_str(), user_obj.c_str())); } /// Starts a transaction that returns the user object specified by @p uuid. /// @see pubnub_get_uuidmetadata futres get_uuidmetadata(std::string const& uuid, std::string const& include) { - return doit(pubnub_get_uuidmetadata( - d_pb, - include.c_str(), - uuid.c_str())); + return doit(pubnub_get_uuidmetadata(d_pb, include.c_str(), uuid.c_str())); } /// Starts a transaction that deletes the user specified by @p uuid. @@ -1559,34 +1566,33 @@ class context { /// @see pubnub_getall_channelmetadata futres getall_channelmetadata(list_options& options) { - return doit(pubnub_getall_channelmetadata( - d_pb, - options.include(), - options.limit(), - options.start(), - options.end(), - options.count())); + return doit(pubnub_getall_channelmetadata(d_pb, + options.include(), + options.limit(), + options.start(), + options.end(), + options.count())); } /// Starts a transaction that creates a space with the attributes specified in @p channel_metadata_obj. /// @see pubnub_set_channelmetadata - futres set_channelmetadata(std::string const& channel_metadataid, std::string const& include, std::string const& channel_metadata_obj) + futres set_channelmetadata(std::string const& channel_metadataid, + std::string const& include, + std::string const& channel_metadata_obj) { - return doit(pubnub_set_channelmetadata( - d_pb, - channel_metadataid.c_str(), - include.c_str(), - channel_metadata_obj.c_str())); + return doit(pubnub_set_channelmetadata(d_pb, + channel_metadataid.c_str(), + include.c_str(), + channel_metadata_obj.c_str())); } /// Starts a transaction that returns the space object specified by @p space_id. /// @see pubnub_get_channelmetadata - futres get_channelmetadata(std::string const& channel_metadataid, std::string const& include) + futres get_channelmetadata(std::string const& channel_metadataid, + std::string const& include) { return doit(pubnub_get_channelmetadata( - d_pb, - include.c_str(), - channel_metadataid.c_str())); + d_pb, include.c_str(), channel_metadataid.c_str())); } /// Starts a transaction that deletes the space specified with @p channel_metadataid. @@ -1596,121 +1602,100 @@ class context { return doit(pubnub_remove_channelmetadata(d_pb, channel_metadataid.c_str())); } - /// Starts a transaction that returns the space memberships of the user specified - /// by @p user_id. + /// Starts a transaction that returns the space memberships of the user + /// specified by @p user_id. /// @see pubnub_get_memberships futres get_memberships(std::string const& metadata_uuid, list_options& options) { - return doit(pubnub_get_memberships( - d_pb, - metadata_uuid.c_str(), - options.include(), - options.limit(), - options.start(), - options.end(), - options.count())); + return doit(pubnub_get_memberships(d_pb, + metadata_uuid.c_str(), + options.include(), + options.limit(), + options.start(), + options.end(), + options.count())); } - /// Starts a transaction that updates the space memberships of the user specified - /// by @p metadata_uuid. + /// Starts a transaction that updates the space memberships of the user + /// specified by @p metadata_uuid. /// @see pubnub_set_memberships futres set_memberships(std::string const& metadata_uuid, - std::string const& set_obj, - std::string const& include) + std::string const& set_obj, + std::string const& include) { return doit(pubnub_set_memberships( - d_pb, - metadata_uuid.c_str(), - include.c_str(), - set_obj.c_str())); + d_pb, metadata_uuid.c_str(), include.c_str(), set_obj.c_str())); } - /// Starts a transaction that removes the channel memberships of the user specified - /// by @p metadata_uuid. + /// Starts a transaction that removes the channel memberships of the user + /// specified by @p metadata_uuid. /// @see pubnub_remove_memberships futres remove_memberships(std::string const& metadata_uuid, - std::string const& remove_obj, - std::string const& include) + std::string const& remove_obj, + std::string const& include) { return doit(pubnub_remove_memberships( - d_pb, - metadata_uuid.c_str(), - include.c_str(), - remove_obj.c_str())); + d_pb, metadata_uuid.c_str(), include.c_str(), remove_obj.c_str())); } /// Starts a transaction that returns all users in the space specified by @p space_id. /// @see pubnub_get_members futres get_members(std::string const& channel_metadataid, list_options& options) { - return doit(pubnub_get_members( - d_pb, - channel_metadataid.c_str(), - options.include(), - options.limit(), - options.start(), - options.end(), - options.count())); + return doit(pubnub_get_members(d_pb, + channel_metadataid.c_str(), + options.include(), + options.limit(), + options.start(), + options.end(), + options.count())); } - /// Starts a transaction that adds the list of members of the space specified - /// by @p channel_metadataid. + /// Starts a transaction that adds the list of members of the space + /// specified by @p channel_metadataid. /// @see pubnub_add_members futres add_members(std::string const& channel_metadataid, std::string const& update_obj, std::string const& include) { return doit(pubnub_add_members( - d_pb, - channel_metadataid.c_str(), - include.c_str(), - update_obj.c_str())); + d_pb, channel_metadataid.c_str(), include.c_str(), update_obj.c_str())); } - /// Starts a transaction that updates the list of members of the space specified - /// by @p channel_metadataid. + /// Starts a transaction that updates the list of members of the space + /// specified by @p channel_metadataid. /// @see pubnub_set_members futres set_members(std::string const& channel_metadataid, - std::string const& set_obj, - std::string const& include) + std::string const& set_obj, + std::string const& include) { return doit(pubnub_set_members( - d_pb, - channel_metadataid.c_str(), - include.c_str(), - set_obj.c_str())); + d_pb, channel_metadataid.c_str(), include.c_str(), set_obj.c_str())); } - /// Starts a transaction that removes the list of members of the space specified - /// by @p channel_metadataid. + /// Starts a transaction that removes the list of members of the space + /// specified by @p channel_metadataid. /// @see pubnub_remove_members futres remove_members(std::string const& channel_metadataid, std::string const& remove_obj, std::string const& include) { return doit(pubnub_remove_members( - d_pb, - channel_metadataid.c_str(), - include.c_str(), - remove_obj.c_str())); + d_pb, channel_metadataid.c_str(), include.c_str(), remove_obj.c_str())); } #endif /* PUBNUB_USE_OBJECTS_API */ #if PUBNUB_USE_ACTIONS_API - /// Starts a transaction that adds new type of message called action as a support for - /// user reactions on a published messages. + /// Starts a transaction that adds new type of message called action as a + /// support for user reactions on a published messages. /// @see pubnub_add_message_action - futres add_message_action(std::string const& channel, - std::string const& message_timetoken, + futres add_message_action(std::string const& channel, + std::string const& message_timetoken, enum pubnub_action_type actype, - std::string const& value) + std::string const& value) { return doit(pubnub_add_message_action( - d_pb, - channel.c_str(), - message_timetoken.c_str(), - actype, - value.c_str())); + d_pb, channel.c_str(), message_timetoken.c_str(), actype, value.c_str())); } /// Returns message timetoken if previous transaction had been add_action() @@ -1737,26 +1722,26 @@ class context { std::string const& action_timetoken) { return doit(pubnub_remove_message_action( - d_pb, - channel.c_str(), - pubnub_str_2_chamebl_t((char*)message_timetoken.c_str()), - pubnub_str_2_chamebl_t((char*)action_timetoken.c_str()))); + d_pb, + channel.c_str(), + pubnub_str_2_chamebl_t((char*)message_timetoken.c_str()), + pubnub_str_2_chamebl_t((char*)action_timetoken.c_str()))); } - /// Initiates transaction that returns all actions added on a given @p channel - /// between @p start and @p end action timetoken. + /// Initiates transaction that returns all actions added on a given @p + /// channel between @p start and @p end action timetoken. /// @see pubnub_get_message_actions() futres get_message_actions(std::string const& channel, std::string const& start, std::string const& end, - size_t limit=0) + size_t limit = 0) { - return doit(pubnub_get_message_actions( - d_pb, - channel.c_str(), - (start.size() > 0) ? start.c_str() : NULL, - (end.size() > 0) ? end.c_str() : NULL, - limit)); + return doit( + pubnub_get_message_actions(d_pb, + channel.c_str(), + (start.size() > 0) ? start.c_str() : NULL, + (end.size() > 0) ? end.c_str() : NULL, + limit)); } /// @see pubnub_get_message_actions_more() @@ -1765,20 +1750,20 @@ class context { return doit(pubnub_get_message_actions_more(d_pb)); } - /// Initiates transaction that returns all actions added on a given @p channel - /// between @p start and @p end message timetoken. + /// Initiates transaction that returns all actions added on a given @p + /// channel between @p start and @p end message timetoken. /// @see pubnub_history_with_message_actions() futres history_with_message_actions(std::string const& channel, std::string const& start, std::string const& end, - size_t limit=0) + size_t limit = 0) { return doit(pubnub_history_with_message_actions( - d_pb, - channel.c_str(), - (start.size() > 0) ? start.c_str() : NULL, - (end.size() > 0) ? end.c_str() : NULL, - limit)); + d_pb, + channel.c_str(), + (start.size() > 0) ? start.c_str() : NULL, + (end.size() > 0) ? end.c_str() : NULL, + limit)); } /// @see pubnub_history_with_message_actions_more() @@ -1796,8 +1781,8 @@ class context { return pubnub_enable_auto_heartbeat(d_pb, period_sec); } - /// Sets(changes) heartbeat period for keeping presence on subscribed channels - /// and channel groups. + /// Sets(changes) heartbeat period for keeping presence on subscribed + /// channels and channel groups. /// @see pubnub_set_heartbeat_period() int set_heartbeat_period(size_t period_sec) { @@ -1806,13 +1791,10 @@ class context { /// Disables keeping presence on subscribed channels and channel groups /// @see pubnub_disable_auto_heartbeat() - void disable_auto_heartbeat() - { - pubnub_disable_auto_heartbeat(d_pb); - } + void disable_auto_heartbeat() { pubnub_disable_auto_heartbeat(d_pb); } - /// Returns whether(, or not) auto heartbeat on subscribed channels and channel - /// groups is enabled + /// Returns whether(, or not) auto heartbeat on subscribed channels and + /// channel groups is enabled /// @see pubnub_is_auto_heartbeat_enabled() bool is_auto_heartbeat_enabled() { @@ -1821,33 +1803,21 @@ class context { /// Enable "smart heartbeat" for presence management. /// @see pubnub_enable_smart_heartbeat() - void enable_smart_heartbeat() - { - pubnub_enable_smart_heartbeat(d_pb); - } + void enable_smart_heartbeat() { pubnub_enable_smart_heartbeat(d_pb); } /// Disable "smart heartbeat" for presence management. /// @see pubnub_disable_smart_heartbeat() - void disable_smart_heartbeat() - { - pubnub_disable_smart_heartbeat(d_pb); - } + void disable_smart_heartbeat() { pubnub_disable_smart_heartbeat(d_pb); } /// Releases all allocated heartbeat thumpers. /// Done on any object of the class, once, suffices. /// @see pubnub_heartbeat_free_thumpers() - void heartbeat_free_thumpers() - { - pubnub_heartbeat_free_thumpers(); - } + void heartbeat_free_thumpers() { pubnub_heartbeat_free_thumpers(); } #endif /* PUBNUB_USE_AUTO_HEARTBEAT */ - + /// Return the HTTP code (result) of the last transaction. /// @see pubnub_last_http_code - int last_http_code() const - { - return pubnub_last_http_code(d_pb); - } + int last_http_code() const { return pubnub_last_http_code(d_pb); } /// Return the string of the last publish transaction. /// @see pubnub_last_publish_result @@ -1875,10 +1845,7 @@ class context { /// Return the string of the last time token. /// @see pubnub_last_time_token - std::string last_time_token() const - { - return pubnub_last_time_token(d_pb); - } + std::string last_time_token() const { return pubnub_last_time_token(d_pb); } /// Sets whether to use (non-)blocking I/O according to option @p e. /// @see pubnub_set_blocking_io, pubnub_set_non_blocking_io @@ -1917,48 +1884,30 @@ class context { /// Reuse SSL sessions, if possible (from now on). /// @see pubnub_set_reuse_ssl_session - void reuse_ssl_session() - { - pubnub_set_reuse_ssl_session(d_pb, true); - } + void reuse_ssl_session() { pubnub_set_reuse_ssl_session(d_pb, true); } /// Don't reuse SSL sessions (from now on). /// @see pubnub_set_reuse_ssl_session - void dont_reuse_ssl_session() - { - pubnub_set_reuse_ssl_session(d_pb, false); - } + void dont_reuse_ssl_session() { pubnub_set_reuse_ssl_session(d_pb, false); } /// Use HTTP Keep-Alive on the context /// @see pubnub_use_http_keep_alive - void use_http_keep_alive() - { - pubnub_use_http_keep_alive(d_pb); - } + void use_http_keep_alive() { pubnub_use_http_keep_alive(d_pb); } /// Don't Use HTTP Keep-Alive on the context /// @see pubnub_dont_use_http_keep_alive - void dont_use_http_keep_alive() - { - pubnub_dont_use_http_keep_alive(d_pb); - } + void dont_use_http_keep_alive() { pubnub_dont_use_http_keep_alive(d_pb); } /// Use of TCP Keep-Alive ("probes") on the context. /// @see pubnub_use_tcp_keep_alive - void use_tcp_keep_alive( - uint8_t time, - uint8_t interval, - uint8_t probes) + void use_tcp_keep_alive(uint8_t time, uint8_t interval, uint8_t probes) { pubnub_use_tcp_keep_alive(d_pb, time, interval, probes); } /// Don't use of TCP Keep-Alive ("probes") on the context. /// @see pubnub_dont_use_tcp_keep_alive - void dont_use_tcp_keep_alive() - { - pubnub_dont_use_tcp_keep_alive(d_pb); - } + void dont_use_tcp_keep_alive() { pubnub_dont_use_tcp_keep_alive(d_pb); } #if PUBNUB_PROXY_API /// Manually set a proxy to use @@ -1974,10 +1923,7 @@ class context { /// Sets a proxy to none /// @see pubnub_set_proxy_none - void set_proxy_none() - { - pubnub_set_proxy_none(d_pb); - } + void set_proxy_none() { pubnub_set_proxy_none(d_pb); } /// Set the proxy to use from system configuration. /// @see pubnub_set_proxy_from_system @@ -2050,6 +1996,20 @@ class context { } #endif +#if PUBNUB_USE_IPV6 + /// Use IPv4 addresses to establish connection with remote origin. + void set_ipv4_connectivity(pubnub_t* p) + { + pubnub_set_ipv4_connectivity(d_pb); + } + + /// Use IPv6 addresses to establish connection with remote origin. + void set_ipv6_connectivity(pubnub_t* p) + { + pubnub_set_ipv6_connectivity(d_pb); + } +#endif /* PUBNUB_USE_IPV6 */ + /// Frees the context and any other thing that needs to be /// freed/released. /// @see pubnub_free @@ -2104,7 +2064,7 @@ class context { /// This function sets the crypto module to be used by the context /// for encryption and decryption of messages. /// @see pubnub_set_crypto_module() - void set_crypto_module(into_crypto_provider_ptr &crypto) + void set_crypto_module(into_crypto_provider_ptr& crypto) { pubnub_set_crypto_module(d_pb, crypto.into_provider()); } @@ -2114,7 +2074,7 @@ class context { /// This function gets the crypto module used by the context /// for encryption and decryption of messages. /// @see pubnub_get_crypto_module() - pubnub_crypto_provider_t *get_crypto_module() + pubnub_crypto_provider_t* get_crypto_module() { return pubnub_get_crypto_module(d_pb); } diff --git a/freertos/pbpal_resolv_and_connect_freertos_tcp.c b/freertos/pbpal_resolv_and_connect_freertos_tcp.c index 5ca0c12d..129145a9 100644 --- a/freertos/pbpal_resolv_and_connect_freertos_tcp.c +++ b/freertos/pbpal_resolv_and_connect_freertos_tcp.c @@ -100,7 +100,7 @@ enum pbpal_resolv_n_connect_result pbpal_check_connect(pubnub_t *pb) #if PUBNUB_CHANGE_DNS_SERVERS int pbpal_dns_rotate_server(pubnub_t *pb) { - return (pbp->flags.sent_queries < PUBNUB_MAX_DNS_QUERIES ? 0 : 1) + return (pb->flags.sent_queries < PUBNUB_MAX_DNS_QUERIES ? 0 : 1) } #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif /* defined(PUBNUB_CALLBACK_API) */ diff --git a/lib/pubnub_dns_codec.c b/lib/pubnub_dns_codec.c index a4bfebe1..c5638430 100644 --- a/lib/pubnub_dns_codec.c +++ b/lib/pubnub_dns_codec.c @@ -704,11 +704,11 @@ static int find_the_answer(uint8_t const* reader, return address_found ? 0 : -1; } -int pbdns_pick_resolved_addresses(uint8_t const* buf, - size_t msg_size, - struct pubnub_ipv4_address* resolved_addr_ipv4 - IPV6_ADDR_ARGUMENT_DECLARATION - PBDNS_OPTIONAL_PARAMS_DECLARATIONS) +int pbdns_pick_resolved_addresses(uint8_t const* buf, + size_t msg_size, + enum DNSqueryType* o_query_type, + struct pubnub_ipv4_address* resolved_addr_ipv4 IPV6_ADDR_ARGUMENT_DECLARATION + PBDNS_OPTIONAL_PARAMS_DECLARATIONS) { size_t q_count; size_t ans_count; @@ -725,6 +725,30 @@ int pbdns_pick_resolved_addresses(uint8_t const* buf, if (read_header(buf, msg_size, &q_count, &ans_count) != 0) { return -1; } + + if (q_count > 0) { + uint8_t const* type_reader = buf + HEADER_SIZE; + uint8_t const* _end = buf + msg_size; + + uint8_t name[256]; + size_t to_skip; + + // Decode the question name + if (dns_label_decode(name, sizeof name, type_reader, buf, msg_size, &to_skip) + == 0) { + type_reader += to_skip; + + // Check if we have enough space to read query type + if (type_reader + 2 <= _end) { + unsigned query_type = type_reader[0] * 256 + type_reader[1]; + PUBNUB_LOG_TRACE("DNS question type: %u\n", query_type); + + if (o_query_type != NULL) + *o_query_type = (enum DNSqueryType)query_type; + } + } + } + if (0 == ans_count) { return -1; } diff --git a/lib/pubnub_dns_codec.h b/lib/pubnub_dns_codec.h index 18bb1cfb..c2453da2 100644 --- a/lib/pubnub_dns_codec.h +++ b/lib/pubnub_dns_codec.h @@ -1,6 +1,6 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #if !defined INC_PUBNUB_DNS_HANDLER -#define INC_PUBNUB_DNS_HANDLER +#define INC_PUBNUB_DNS_HANDLER #include "pubnub_internal.h" #include "core/pubnub_dns_servers.h" @@ -32,7 +32,8 @@ enum DNSqueryType { }; #if PUBNUB_USE_IPV6 -#define IPV6_ADDR_ARGUMENT_DECLARATION , struct pubnub_ipv6_address* resolved_addr_ipv6 +#define IPV6_ADDR_ARGUMENT_DECLARATION \ + , struct pubnub_ipv6_address* resolved_addr_ipv6 #define IPV6_ADDR_ARGUMENT , resolved_addr_ipv6 #else #define IPV6_ADDR_ARGUMENT_DECLARATION @@ -40,13 +41,14 @@ enum DNSqueryType { #endif /* PUBNUB_USE_IPV6 */ #if PUBNUB_USE_MULTIPLE_ADDRESSES -#define PBDNS_OPTIONAL_PARAMS_DECLARATIONS , struct pubnub_multi_addresses* spare_addresses \ - , struct pubnub_options const* options +#define PBDNS_OPTIONAL_PARAMS_DECLARATIONS \ + , struct pubnub_multi_addresses *spare_addresses, \ + struct pubnub_options const *options #define PBDNS_OPTIONAL_PARAMS , spare_addresses, options -#else +#else /* PUBNUB_USE_MULTIPLE_ADDRESSES */ #define PBDNS_OPTIONAL_PARAMS_DECLARATIONS #define PBDNS_OPTIONAL_PARAMS -#endif +#endif /* !PUBNUB_USE_MULTIPLE_ADDRESSES */ /** Prepares DNS @p query_type query request for @p host(name) in @p buf whose maximum available @@ -54,13 +56,13 @@ enum DNSqueryType { If function succeedes, @p to_send 'carries' the length of prepared message. If function reports en error, @p to_send 'keeps' the length of successfully prepared segment before error occurred. - + @retval 0 success, -1 on error */ -int pbdns_prepare_dns_request(uint8_t* buf, - size_t buf_size, - char const* host, - int *to_send, +int pbdns_prepare_dns_request(uint8_t* buf, + size_t buf_size, + char const* host, + int* to_send, enum DNSqueryType query_type); /** Picks valid resolved(Ipv4, or Ipv6) domain name addresses from the response from DNS server. @@ -72,11 +74,11 @@ int pbdns_prepare_dns_request(uint8_t* buf, @retval 0 success, -1 on error */ -int pbdns_pick_resolved_addresses(uint8_t const* buf, - size_t msg_size, - struct pubnub_ipv4_address* resolved_addr_ipv4 - IPV6_ADDR_ARGUMENT_DECLARATION - PBDNS_OPTIONAL_PARAMS_DECLARATIONS); +int pbdns_pick_resolved_addresses(uint8_t const* buf, + size_t msg_size, + enum DNSqueryType* o_query_type, + struct pubnub_ipv4_address* resolved_addr_ipv4 IPV6_ADDR_ARGUMENT_DECLARATION + PBDNS_OPTIONAL_PARAMS_DECLARATIONS); #endif /* defined INC_PUBNUB_DNS_HANDLER */ diff --git a/lib/pubnub_dns_codec_unit_test.c b/lib/pubnub_dns_codec_unit_test.c index d9207dc6..880a7ef5 100644 --- a/lib/pubnub_dns_codec_unit_test.c +++ b/lib/pubnub_dns_codec_unit_test.c @@ -303,6 +303,7 @@ Ensure(pubnub_dns_codec, decodes_strange_response_2_questions_3_answers) uint8_t data_2[] = {5,6,7,8}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); /* Assembling test message(response from DNS server) with 2 questions and 2 answers. @@ -315,13 +316,14 @@ Ensure(pubnub_dns_codec, decodes_strange_response_2_questions_3_answers) append_answer_M(encoded_piece2, RecordTypeTXT, data, 10); append_answer_M(encoded_domain_name, RecordTypeA, data_2, 100); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv4_addresses[0].ipv4, data, @@ -351,6 +353,7 @@ Ensure(pubnub_dns_codec, decodes_response_1_question_3_answers_no_ssl_fallback) struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); @@ -367,13 +370,14 @@ Ensure(pubnub_dns_codec, decodes_response_1_question_3_answers_no_ssl_fallback) append_answer_M(encoded_domain_name, RecordTypeA, data_2, 65536); append_answer_M(encoded_domain_name, RecordTypeA, data_3, 2); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv4_addresses[0].ipv4, data, @@ -400,26 +404,27 @@ Ensure(pubnub_dns_codec, decodes_strange_response_wrong_answers) uint8_t data[] = {1,2,3,4}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; make_dns_header_M(RESPONSE, 1, 2); append_question_M(just_offset); append_answer_M(encoded_domain_name, RecordTypeSRV, data, 7000); append_answer_M(encoded_abc_domain_name, RecordTypeTXT, data, 65000); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); /* Message shortened to finish within last answers label */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - 20, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - 20, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); } @@ -429,6 +434,7 @@ Ensure(pubnub_dns_codec, decodes_label_too_long_to_fit_in_modules_buffer) uint8_t data[] = {10,20,30,40}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); @@ -438,20 +444,21 @@ Ensure(pubnub_dns_codec, decodes_label_too_long_to_fit_in_modules_buffer) append_question_M(encoded_long_piece21); append_answer_M(encoded_long_piece21, RecordTypeA, data, 5); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); /* Message shortened to end within the question label */ - attest(pbdns_pick_resolved_addresses(m_buf, - 280, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + 280, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv4_addresses[0].ipv4, @@ -473,6 +480,7 @@ Ensure(pubnub_dns_codec, decodes_name_too_long_for_modules_buffer_ending_with_ba /* Resolved Ipv4 address */ uint8_t data[] = {100,200,30,40}; struct pubnub_ipv4_address resolved_addr_ipv4; + enum DNSqueryType o_query_type; make_dns_header_M(RESPONSE, 1, 1); /* Label finishes with bad offset format */ @@ -480,11 +488,11 @@ Ensure(pubnub_dns_codec, decodes_name_too_long_for_modules_buffer_ending_with_ba append_question_M(encoded_long_piece21); append_answer_M(encoded_long_piece21, RecordTypeA, data, 3); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); } @@ -494,6 +502,7 @@ Ensure(pubnub_dns_codec, decodes_name_too_long_for_modules_buffer_finishing_with uint8_t data[] = {100,200,30,40}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); @@ -506,13 +515,14 @@ Ensure(pubnub_dns_codec, decodes_name_too_long_for_modules_buffer_finishing_with append_question_M(encoded_long_piece21); append_answer_M(encoded_long_piece21, RecordTypeA, data, 4); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); /* Returning previous length of the last label stretch */ encoded_long_piece21[length_M(encoded_long_piece21) - 37] = '\42'; } @@ -523,6 +533,7 @@ Ensure(pubnub_dns_codec, decodes_another_spooky_response) uint8_t data[] = {4,3,2,1}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); /* Assembling test message(response from DNS server). @@ -534,13 +545,14 @@ Ensure(pubnub_dns_codec, decodes_another_spooky_response) append_answer_M(encoded_domain_name, RecordTypeA, data, 60); append_answer_M(encoded_piece2, RecordTypePTR, data, 87); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); } Ensure(pubnub_dns_codec, decodes_response_encoded_label_splitted) @@ -549,6 +561,7 @@ Ensure(pubnub_dns_codec, decodes_response_encoded_label_splitted) uint8_t data[] = {192,168,40,37}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); /* Ignores the fact that response has no questions */ @@ -562,13 +575,14 @@ Ensure(pubnub_dns_codec, decodes_response_encoded_label_splitted) PUBNUB_LOG_TRACE("------->encoded label formed:\n"); resize_msg(); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv4_addresses[0].ipv4, data, @@ -591,99 +605,92 @@ Ensure(pubnub_dns_codec, handles_response_no_usable_answer) uint8_t data2[] = {192,168,1,2}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; make_dns_header_M(RESPONSE, 1, 2); /* Message shorter than its header?! */ - attest(pbdns_pick_resolved_addresses(m_buf, - DNS_MESSAGE_HEADER_SIZE - 1, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + DNS_MESSAGE_HEADER_SIZE - 1, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* Reducing the offset to contaminate second answers label */ encoded_piece21[length_M(encoded_piece21) - 2] = '\300'; append_question_M(encoded_piece21); /* Message doesn't contain its first question?! */ - attest(pbdns_pick_resolved_addresses(m_buf, - DNS_MESSAGE_HEADER_SIZE + TYPE_AND_CLASS_FIELDS_SIZE - 1, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + DNS_MESSAGE_HEADER_SIZE + TYPE_AND_CLASS_FIELDS_SIZE - 1, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* Message doesn't contain type, nor class question fields?! - */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + */ + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* Message doesn't contain complete question name(offset incomplete(1))?! - */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE - 1, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + */ + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE - 1, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* Message doesn't contain complete question name(offset missing(2))?! - */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE - 2, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + */ + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE - 2, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); - /* Message doesn't contain complete question name(offset missing(2) and last character in - label stretch(1))?! + /* Message doesn't contain complete question name(offset missing(2) and last + character in label stretch(1))?! */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE - 3, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - TYPE_AND_CLASS_FIELDS_SIZE - 3, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* Message doesn't contain its first answer?! */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size + - TYPE_AND_CLASS_FIELDS_SIZE + - TTL_FIELD_SIZE + - RECORD_DATA_LENGTH_FIELD_SIZE - 1, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size + TYPE_AND_CLASS_FIELDS_SIZE + TTL_FIELD_SIZE + + RECORD_DATA_LENGTH_FIELD_SIZE - 1, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); append_answer_M(encoded_long_piece1, RecordTypeAAAA, data, 11); /* Message doesn't contain complete answer name?! */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - - sizeof data - - TYPE_AND_CLASS_FIELDS_SIZE - - TTL_FIELD_SIZE - - RECORD_DATA_LENGTH_FIELD_SIZE - 1, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - sizeof data - TYPE_AND_CLASS_FIELDS_SIZE + - TTL_FIELD_SIZE - RECORD_DATA_LENGTH_FIELD_SIZE - 1, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* answer doesn't contain resource data fields */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - - sizeof data - - TYPE_AND_CLASS_FIELDS_SIZE - - TTL_FIELD_SIZE - - RECORD_DATA_LENGTH_FIELD_SIZE, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - sizeof data - TYPE_AND_CLASS_FIELDS_SIZE + - TTL_FIELD_SIZE - RECORD_DATA_LENGTH_FIELD_SIZE, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); /* Message doesn't contain complete answer?! */ - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size - 1, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size - 1, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); place_encoded_label_piece_M(offset_within_header); /* Won't find the answer due to contaminated answers label */ @@ -691,13 +698,14 @@ Ensure(pubnub_dns_codec, handles_response_no_usable_answer) place_encoded_label_piece_M(bad_offset_format); resize_msg(); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), + equals(0)); /* returning to safe offset value */ encoded_piece21[length_M(encoded_piece21) - 2] = '\301'; @@ -709,6 +717,7 @@ Ensure(pubnub_dns_codec, handles_label_offset_to_itself_preventing_infinite_loop uint8_t data[] = {192,168,1,1}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); @@ -721,59 +730,64 @@ Ensure(pubnub_dns_codec, handles_label_offset_to_itself_preventing_infinite_loop PUBNUB_LOG_TRACE("------->encoded label formed:\n"); resize_msg(); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); } Ensure(pubnub_dns_codec, handles_response_with_0_answers) { struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; make_dns_header_M(RESPONSE, 1, 0); append_question_M(encoded_piece31); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), + equals(0)); } Ensure(pubnub_dns_codec, handles_response_reporting_error) { struct pubnub_ipv4_address resolved_addr_ipv4; + enum DNSqueryType o_query_type; /* This kind of response header reports en issue: RCODE != 0 */ make_dns_header_M(QUERY, 1, 0); append_question_M(encoded_piece31); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); } Ensure(pubnub_dns_codec, handles_response_with_no_QR_flag_set) { struct pubnub_ipv4_address resolved_addr_ipv4; + enum DNSqueryType o_query_type; make_dns_header_M(RESPONSE, 1, 0); m_buf[OFFSET_FLAGS] ^= ResponseQueryFlagMask >> 8; append_question_M(offset_beyond_boudary); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); } @@ -783,6 +797,7 @@ Ensure(pubnub_dns_codec, handles_splitted_label_too_long_for_modules_buffer) uint8_t data[] = {192,168,2,5}; struct pubnub_ipv4_address key_addr = {{0}}; struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv4, data, sizeof key_addr.ipv4); @@ -802,13 +817,14 @@ Ensure(pubnub_dns_codec, handles_splitted_label_too_long_for_modules_buffer) append_answer_M(encoded_long_piece1, RecordTypeA, data, 0); resize_msg(); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv4, &key_addr, sizeof resolved_addr_ipv4), equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(bp.spare_addresses.n_ipv4, equals(0)); attest(bp.spare_addresses.ipv4_index, equals(0)); @@ -826,14 +842,15 @@ Ensure(pubnub_dns_codec, handles_response_label_encoded_badly) /* Resolved Ipv4 address */ uint8_t data[] = {192,168,0,0}; struct pubnub_ipv4_address resolved_addr_ipv4; + enum DNSqueryType o_query_type; make_dns_header_M(RESPONSE, 1, 1); append_question_M(label_start_encoded_badly_with_offset_to_itself); append_answer_M(encoded_piece31, RecordTypeA, data, 19); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); } @@ -842,16 +859,17 @@ Ensure(pubnub_dns_codec, handles_response_RecordType_and_DataLength_mismatch) /* Resolved IpvX address */ uint8_t data[] = {255,255,0,0,0}; uint8_t data_2[] = {255,255,0,0}; + enum DNSqueryType o_query_type; struct pubnub_ipv4_address resolved_addr_ipv4; make_dns_header_M(RESPONSE, 1, 2); append_question_M(encoded_abc_domain_name); append_answer_M(encoded_piece3, RecordTypeA, data, 47); append_answer_M(encoded_piece3, RecordTypeA, data_2, 123); - attest(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + attest(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv4_addresses[0].ipv4, @@ -944,6 +962,7 @@ Ensure(pubnub_dns_codec, handles_response_RecordType_and_DataLength_mismatch_on_ /* Resolved IpvX address */ uint8_t data[] = {255,255,0,0,0}; uint8_t data_2[] = {0xAB,0xCD,0x02,0x55,0,0,0,0,0x32}; + enum DNSqueryType o_query_type; struct pubnub_ipv6_address resolved_addr_ipv6; make_dns_header_M(RESPONSE, 1, 2); append_question_M(encoded_abc_domain_name); @@ -951,9 +970,9 @@ Ensure(pubnub_dns_codec, handles_response_RecordType_and_DataLength_mismatch_on_ append_answer_M(encoded_piece3, RecordTypeA, data, 54); attest(pbdns_pick_resolved_addresses(m_buf, m_msg_size, + &o_query_type, NULL, - &resolved_addr_ipv6 - PBDNS_OPTIONAL_PARAMS_BP), + &resolved_addr_ipv6 PBDNS_OPTIONAL_PARAMS_BP), equals(-1)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(bp.spare_addresses.n_ipv4, equals(0)); @@ -973,6 +992,7 @@ Ensure(pubnub_dns_codec, handles_response_RecordTypeAAAA_and_RecordTypeA) struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; struct pubnub_ipv6_address resolved_addr_ipv6 = {{0}}; struct pubnub_ipv6_address key_addr = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv6, data_2, sizeof key_addr.ipv6); @@ -983,12 +1003,15 @@ Ensure(pubnub_dns_codec, handles_response_RecordTypeAAAA_and_RecordTypeA) append_answer_M(encoded_piece3, RecordTypeA, data, 58); attest(pbdns_pick_resolved_addresses(m_buf, m_msg_size, + &o_query_type, &resolved_addr_ipv4, - &resolved_addr_ipv6 - PBDNS_OPTIONAL_PARAMS_BP), + &resolved_addr_ipv6 PBDNS_OPTIONAL_PARAMS_BP), equals(0)); - attest(memcmp(&resolved_addr_ipv6, &key_addr, sizeof resolved_addr_ipv6), equals(0)); - attest(memcmp(&resolved_addr_ipv4.ipv4, data_zeros, sizeof resolved_addr_ipv4.ipv4), equals(0)); + attest(memcmp(&resolved_addr_ipv6, &key_addr, sizeof resolved_addr_ipv6), + equals(0)); + attest( + memcmp(&resolved_addr_ipv4.ipv4, data_zeros, sizeof resolved_addr_ipv4.ipv4), + equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv6_addresses[0].ipv6, data_2, @@ -1023,6 +1046,7 @@ Ensure(pubnub_dns_codec, handles_response_with_RecordTypeAAAA_no_ssl_fallback) struct pubnub_ipv4_address resolved_addr_ipv4 = {{0}}; struct pubnub_ipv6_address resolved_addr_ipv6 = {{0}}; struct pubnub_ipv6_address key_addr = {{0}}; + enum DNSqueryType o_query_type; memcpy(key_addr.ipv6, data_1, sizeof key_addr.ipv6); @@ -1041,12 +1065,14 @@ Ensure(pubnub_dns_codec, handles_response_with_RecordTypeAAAA_no_ssl_fallback) append_answer_M(encoded_piece3, RecordTypeAAAA, data_2, 30); attest(pbdns_pick_resolved_addresses(m_buf, m_msg_size, + &o_query_type, &resolved_addr_ipv4, - &resolved_addr_ipv6 - PBDNS_OPTIONAL_PARAMS_BP), + &resolved_addr_ipv6 PBDNS_OPTIONAL_PARAMS_BP), + equals(0)); + attest(memcmp(&resolved_addr_ipv4.ipv4, data_zeros, sizeof resolved_addr_ipv4), + equals(0)); + attest(memcmp(&resolved_addr_ipv6, &key_addr, sizeof resolved_addr_ipv6), equals(0)); - attest(memcmp(&resolved_addr_ipv4.ipv4, data_zeros, sizeof resolved_addr_ipv4), equals(0)); - attest(memcmp(&resolved_addr_ipv6, &key_addr, sizeof resolved_addr_ipv6), equals(0)); #if PUBNUB_USE_MULTIPLE_ADDRESSES attest(memcmp(bp.spare_addresses.ipv6_addresses[0].ipv6, data_1, @@ -1079,23 +1105,27 @@ Ensure(pubnub_dns_codec, fires_asserts_on_illegal_parameters) { int to_send; struct pubnub_ipv4_address resolved_addr_ipv4; + enum DNSqueryType o_query_type; pubnub_assert_set_handler((pubnub_assert_handler_t)test_assert_handler); - expect_assert_in(pbdns_prepare_dns_request(NULL, 10, "pubsub.pubnub.com", &to_send, dnsA), - "pubnub_dns_codec.c"); + expect_assert_in( + pbdns_prepare_dns_request(NULL, 10, "pubsub.pubnub.com", &to_send, dnsA), + "pubnub_dns_codec.c"); expect_assert_in(pbdns_prepare_dns_request(m_buf, 3, NULL, &to_send, dnsA), "pubnub_dns_codec.c"); - expect_assert_in(pbdns_prepare_dns_request(m_buf, 5, "pubsub.pubnub.com", NULL, dnsA), - "pubnub_dns_codec.c"); - expect_assert_in(pbdns_pick_resolved_addresses(NULL, - m_msg_size, - &resolved_addr_ipv4 - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), - "pubnub_dns_codec.c"); - expect_assert_in(pbdns_pick_resolved_addresses(m_buf, - m_msg_size, - NULL - IPV6_NULL_ARGUMENT - PBDNS_OPTIONAL_PARAMS_BP), + expect_assert_in( + pbdns_prepare_dns_request(m_buf, 5, "pubsub.pubnub.com", NULL, dnsA), + "pubnub_dns_codec.c"); + expect_assert_in( + pbdns_pick_resolved_addresses( + NULL, + m_msg_size, + &o_query_type, + &resolved_addr_ipv4 IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), + "pubnub_dns_codec.c"); + expect_assert_in(pbdns_pick_resolved_addresses( + m_buf, + m_msg_size, + &o_query_type, + NULL IPV6_NULL_ARGUMENT PBDNS_OPTIONAL_PARAMS_BP), "pubnub_dns_codec.c"); } diff --git a/lib/sockets/pbpal_adns_sockets.c b/lib/sockets/pbpal_adns_sockets.c index 1994a26b..c42fa284 100644 --- a/lib/sockets/pbpal_adns_sockets.c +++ b/lib/sockets/pbpal_adns_sockets.c @@ -1,7 +1,7 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#include "pubnub_internal.h" - #include "lib/sockets/pbpal_adns_sockets.h" + +#include "pubnub_internal.h" #include "core/pubnub_assert.h" #include "core/pubnub_log.h" @@ -39,48 +39,84 @@ #endif -int send_dns_query(pb_socket_t skt, - struct sockaddr const* dest, - char const* host, - enum DNSqueryType query_type) +int send_dns_query(pb_socket_t skt, + struct sockaddr const* dest, + char const* host, + struct dns_queries_tracking* tracking) { uint8_t buf[4096]; int to_send; int sent_to; size_t sockaddr_size; + bool any_blocked = false; + + if (!tracking) { + PUBNUB_LOG_ERROR("send_dns_query: tracking required\n"); + return -1; + } switch (dest->sa_family) { case AF_INET: - sockaddr_size = sizeof(struct sockaddr_in); + sockaddr_size = sizeof(struct sockaddr_in); ((struct sockaddr_in*)dest)->sin_port = htons(DNS_PORT); break; #if PUBNUB_USE_IPV6 case AF_INET6: - sockaddr_size = sizeof(struct sockaddr_in6); + sockaddr_size = sizeof(struct sockaddr_in6); ((struct sockaddr_in6*)dest)->sin6_port = htons(DNS_PORT); break; #endif /* PUBNUB_USE_IPV6 */ default: PUBNUB_LOG_ERROR("send_dns_query(socket=%d): invalid address family " - "dest->sa_family =%uh\n", + "dest->sa_family=%u\n", skt, dest->sa_family); return -1; } - if (-1 == pbdns_prepare_dns_request(buf, sizeof buf, host, &to_send, query_type)) { - PUBNUB_LOG_ERROR("Couldn't prepare dns request! : #prepared bytes=%d\n", - to_send); - return -1; - } - TRACE_SOCKADDR("Sending DNS query to: ", dest, sockaddr_size); - sent_to = sendto(skt, (char*)buf, to_send, 0, dest, sockaddr_size); - if (sent_to <= 0) { - return socket_would_block() ? +1 : -1; + + if (!tracking->sent_a) { + if (pbdns_prepare_dns_request(buf, sizeof buf, host, &to_send, dnsA) == 0) { + TRACE_SOCKADDR("Sending DNS A query to: ", dest, sockaddr_size); + sent_to = sendto(skt, (char*)buf, to_send, 0, dest, sockaddr_size); + if (sent_to > 0 && to_send == sent_to) + tracking->sent_a = true; + else if (sent_to <= 0 && socket_would_block()) + any_blocked = true; + else + PUBNUB_LOG_WARNING("Failed to send A query: %d\n", sent_to); + } } - else if (to_send != sent_to) { - PUBNUB_LOG_ERROR("sendto() sent %d out of %d bytes!\n", sent_to, to_send); - return -1; + +#if PUBNUB_USE_IPV6 + if (!tracking->sent_aaaa) { + if (pbdns_prepare_dns_request(buf, sizeof buf, host, &to_send, dnsAAAA) + == 0) { + TRACE_SOCKADDR("Sending DNS AAAA query to: ", dest, sockaddr_size); + sent_to = sendto(skt, (char*)buf, to_send, 0, dest, sockaddr_size); + if (sent_to > 0 && to_send == sent_to) + tracking->sent_aaaa = true; + else if (sent_to <= 0 && socket_would_block()) + any_blocked = true; + else + PUBNUB_LOG_WARNING("Failed to send AAAA query: %d\n", sent_to); + } } +#endif /* PUBNUB_USE_IPV6 */ + tracking->need_retry = !tracking->sent_a +#if PUBNUB_USE_IPV6 + || !tracking->sent_aaaa +#endif + ; + + if (!tracking->sent_a +#if PUBNUB_USE_IPV6 + && !tracking->sent_aaaa +#endif + ) + return any_blocked ? +1 : -1; + if (tracking->need_retry && any_blocked) + return +1; + return 0; } @@ -91,66 +127,140 @@ int send_dns_query(pb_socket_t skt, #endif -int read_dns_response(pb_socket_t skt, +int read_dns_response(pb_socket_t skt, struct sockaddr* dest, - struct sockaddr* resolved_addr - PBDNS_OPTIONAL_PARAMS_DECLARATIONS) + struct dns_queries_tracking* tracking PBDNS_OPTIONAL_PARAMS_DECLARATIONS) { - uint8_t buf[8192]; - int msg_size; - unsigned sockaddr_size; - struct pubnub_ipv4_address addr_ipv4 = {{0}}; -#if PUBNUB_USE_IPV6 - struct pubnub_ipv6_address addr_ipv6 = {{0}}; -#endif + uint8_t buf[8192]; + int msg_size; + unsigned sockaddr_size; + int responses_received = 0; + int expected_responses = 0; + PUBNUB_ASSERT(SOCKET_INVALID != skt); + if (tracking->sent_a) + expected_responses++; + if (tracking->received_a) + responses_received++; +#if PUBNUB_USE_IPV6 + if (tracking->sent_aaaa) + expected_responses++; + if (tracking->received_aaaa) + responses_received++; +#endif /* PUBNUB_USE_IPV6 */ + + if (expected_responses == 0) { + PUBNUB_LOG_ERROR("read_dns_response: No queries were sent!\n"); + return -1; + } + + if (responses_received >= expected_responses) { + PUBNUB_LOG_WARNING( + "read_dns_response: Already received all responses\n"); + goto select_address; + } + + PUBNUB_LOG_TRACE("Expecting %d response(s), already received %d\n", + expected_responses, + responses_received); + switch (dest->sa_family) { case AF_INET: - sockaddr_size = sizeof(struct sockaddr_in); + sockaddr_size = sizeof(struct sockaddr_in); ((struct sockaddr_in*)dest)->sin_port = htons(DNS_PORT); break; #if PUBNUB_USE_IPV6 case AF_INET6: - sockaddr_size = sizeof(struct sockaddr_in6); + sockaddr_size = sizeof(struct sockaddr_in6); ((struct sockaddr_in6*)dest)->sin6_port = htons(DNS_PORT); break; #endif /* PUBNUB_USE_IPV6 */ default: PUBNUB_LOG_ERROR("read_dns_response(socket=%d): invalid address family " - "dest->sa_family =%uh\n", + "dest->sa_family =%u\n", skt, dest->sa_family); return -1; } - msg_size = recvfrom(skt, (char*)buf, sizeof buf, 0, dest, CAST & sockaddr_size); - if (msg_size <= 0) { - return socket_would_block() ? +1 : -1; - } + while (responses_received < expected_responses) { + msg_size = + recvfrom(skt, (char*)buf, sizeof buf, 0, dest, CAST & sockaddr_size); + if (msg_size <= 0) + return socket_would_block() ? +1 : -1; + + PUBNUB_LOG_TRACE("Received DNS response packet (%d bytes)\n", msg_size); #if PUBNUB_USE_MULTIPLE_ADDRESSES - time(&spare_addresses->time_of_the_last_dns_query); + if (responses_received == 0) + time(&spare_addresses->time_of_the_last_dns_query); +#endif /* PUBNUB_USE_MULTIPLE_ADDRESSES */ + enum DNSqueryType question_type; + struct pubnub_ipv4_address this_ipv4 = { { 0 } }; +#if PUBNUB_USE_IPV6 + struct pubnub_ipv6_address this_ipv6 = { { 0 } }; #endif - if (pbdns_pick_resolved_addresses(buf, - (size_t)msg_size, - &addr_ipv4 - P_ADDR_IPV6_ARGUMENT - PBDNS_OPTIONAL_PARAMS) != 0) { - return -1; - } - if (addr_ipv4.ipv4[0] != 0) { - memcpy(&((struct sockaddr_in*)resolved_addr)->sin_addr.s_addr, - addr_ipv4.ipv4, - sizeof addr_ipv4.ipv4); - resolved_addr->sa_family = AF_INET; - } + + if (pbdns_pick_resolved_addresses(buf, + (size_t)msg_size, + &question_type, + &this_ipv4 #if PUBNUB_USE_IPV6 - else { - memcpy(((struct sockaddr_in6*)resolved_addr)->sin6_addr.s6_addr, - addr_ipv6.ipv6, - sizeof addr_ipv6.ipv6); - resolved_addr->sa_family = AF_INET6; - } + , + &this_ipv6 +#endif + PBDNS_OPTIONAL_PARAMS) + == 0) { + if (dnsA == question_type && !tracking->received_a) { + if (this_ipv4.ipv4[0] != 0) { + memcpy(&tracking->dns_a_addr.sin_addr.s_addr, + this_ipv4.ipv4, + sizeof this_ipv4.ipv4); + tracking->dns_a_addr.sin_family = AF_INET; + } + tracking->received_a = true; + responses_received++; + } +#if PUBNUB_USE_IPV6 + else if (dnsAAAA == question_type && !tracking->received_aaaa) { + if (this_ipv6.ipv6[0] != 0 || this_ipv6.ipv6[1] != 0) { + struct sockaddr_in6* sin6 = + (struct sockaddr_in6*)&tracking->dns_aaaa_addr; + memcpy(sin6->sin6_addr.s6_addr, + this_ipv6.ipv6, + sizeof this_ipv6.ipv6); + sin6->sin6_family = AF_INET6; + } + tracking->received_aaaa = true; + responses_received++; + } +#endif + else { + PUBNUB_LOG_WARNING( + "Received duplicate or unexpected DNS response\n"); + } + } + else { + responses_received++; + if (dnsA == question_type) { + tracking->received_a = true; + PUBNUB_LOG_WARNING( + "There are no 'A' records for requested domain.\n"); + } +#if PUBNUB_USE_IPV6 + else if (dnsAAAA == question_type) { + tracking->received_aaaa = true; + PUBNUB_LOG_WARNING( + "There are no 'AAAA' records for requested domain.\n"); + } #endif /* PUBNUB_USE_IPV6 */ + else + PUBNUB_LOG_WARNING("Failed to parse DNS response.\n"); + } + } + +select_address: + PUBNUB_LOG_TRACE("All %d DNS responses received\n", expected_responses); + return 0; } @@ -169,7 +279,9 @@ int main() struct sockaddr_in dest; struct sockaddr_in6 dest6; struct sockaddr_storage resolved_addr; - + struct dns_queries_tracking tracking; + memset(&tracking, 0, sizeof(tracking)); + #if defined(_WIN32) WSADATA wsaData; int iResult; @@ -188,7 +300,7 @@ int main() PUBNUB_LOG_ERROR("Error: Couldnt't get Ipv4 socket.\n"); return -1; } - + #if !defined(_WIN32) int flags = fcntl(skt, F_GETFL, 0); fcntl(skt, F_SETFL, flags | O_NONBLOCK); @@ -197,7 +309,7 @@ int main() dest.sin_port = htons(53); inet_pton(AF_INET, "208.67.222.222", &(dest.sin_addr.s_addr)); - if (-1 == send_dns_query(skt, (struct sockaddr*)&dest, "facebook.com", dnsANY)) { + if (-1 == send_dns_query(skt, (struct sockaddr*)&dest, "facebook.com", &tracking)) { PUBNUB_LOG_ERROR("Error: Couldn't send datagram(Ipv4).\n"); return -1; } @@ -221,7 +333,7 @@ int main() timev.tv_sec, timev.tv_usec); #endif - read_dns_response(skt, (struct sockaddr*)&dest, (struct sockaddr*)&resolved_addr); + read_dns_response(skt, (struct sockaddr*)&dest, &tracking); #if !defined(_WIN32) } else { @@ -247,7 +359,7 @@ int main() dest6.sin6_port = htons(53); inet_pton(AF_INET6, "2001:470:20::2", dest6.sin6_addr.s6_addr); - if (-1 == send_dns_query(skt, (struct sockaddr*)&dest6, "facebook.com", dnsANY)) { + if (-1 == send_dns_query(skt, (struct sockaddr*)&dest6, "facebook.com", &tracking)) { PUBNUB_LOG_ERROR("Error: Couldn't send datagram(Ipv6).\n"); return -1; @@ -268,7 +380,7 @@ int main() timev.tv_sec, timev.tv_usec); #endif - read_dns_response(skt, (struct sockaddr*)&dest6, (struct sockaddr*)&resolved_addr); + read_dns_response(skt, (struct sockaddr*)&dest6, &tracking); #if !defined(_WIN32) } else { diff --git a/lib/sockets/pbpal_adns_sockets.h b/lib/sockets/pbpal_adns_sockets.h index 378fb120..358ba36c 100644 --- a/lib/sockets/pbpal_adns_sockets.h +++ b/lib/sockets/pbpal_adns_sockets.h @@ -1,25 +1,25 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #if !defined INC_PBPAL_ANDS_SOCKETS -#define INC_PBPAL_ANDS_SOCKETS +#define INC_PBPAL_ANDS_SOCKETS #include "lib/pubnub_dns_codec.h" struct sockaddr; +struct dns_queries_tracking; /** * Perform a DNS query by sending a packet to the DNS server @p dest. */ -int send_dns_query(pb_socket_t skt, - struct sockaddr const *dest, - char const *host, - enum DNSqueryType query_type); +int send_dns_query(pb_socket_t skt, + struct sockaddr const* dest, + char const* host, + struct dns_queries_tracking* tracking); /** Reads response from DNS server @p dest, putting it into @p resolved addr. */ -int read_dns_response(pb_socket_t skt, - struct sockaddr *dest, - struct sockaddr *resolved_addr - PBDNS_OPTIONAL_PARAMS_DECLARATIONS); - +int read_dns_response(pb_socket_t skt, + struct sockaddr* dest, + struct dns_queries_tracking* tracking + PBDNS_OPTIONAL_PARAMS_DECLARATIONS); #endif /* defined INC_PBPAL_ANDS_SOCKETS */ diff --git a/lib/sockets/pbpal_resolv_and_connect_sockets.c b/lib/sockets/pbpal_resolv_and_connect_sockets.c index cb170c13..e431f8e4 100644 --- a/lib/sockets/pbpal_resolv_and_connect_sockets.c +++ b/lib/sockets/pbpal_resolv_and_connect_sockets.c @@ -16,19 +16,24 @@ #if defined(_WIN32) #include "windows/pubnub_get_native_socket.h" #include +#include #else #include "posix/pubnub_get_native_socket.h" #include #endif #define HTTP_PORT 80 - #define TLS_PORT 443 #ifndef PUBNUB_CALLBACK_API #define send_dns_query(x, y, z, v) -1 -#define read_response(x, y, z, v) -1 -#else +#if PUBNUB_USE_MULTIPLE_ADDRESSES +#define read_dns_response(x, y, z, v, p) -1 +#else /* PUBNUB_USE_MULTIPLE_ADDRESSES */ +#define read_dns_response(x, y, z) -1 +#endif /* !PUBNUB_USE_MULTIPLE_ADDRESSES */ + +#else /* !PUBNUB_CALLBACK_API */ /** Considering the size of bit field for DNS queries sent */ PUBNUB_STATIC_ASSERT(PUBNUB_MAX_DNS_QUERIES < (1 << SENT_QUERIES_SIZE_IN_BITS), PUBNUB_MAX_DNS_QUERIES_is_too_big); @@ -39,17 +44,76 @@ PUBNUB_STATIC_ASSERT(PUBNUB_MAX_DNS_ROTATION < (1 << ROTATIONS_COUNT_SIZE_IN_BIT #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #if PUBNUB_USE_IPV6 typedef struct sockaddr_storage sockaddr_inX_t; -#define QUERY_TYPE pb->options.ipv6_connectivity ? dnsAAAA : dnsA -#else +#else /* PUBNUB_USE_IPV6 */ typedef struct sockaddr_in sockaddr_inX_t; -#define QUERY_TYPE dnsA -#endif +#endif /* !PUBNUB_USE_IPV6 */ #endif /* PUBNUB_CALLBACK_API */ +#if PUBNUB_USE_IPV6 +/** Check whether kernel has any IPv6 routing data or not. + * + * @return @c true if kernel knows about routing for @c AF_INET6, @c false + * otherwise. + */ +static bool is_ipv6_supported(void) +{ + struct sockaddr_in6 sin6 = { 0 }; + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(53); + inet_pton(AF_INET6, PUBNUB_DEFAULT_IPV6_DNS_SERVER, &sin6.sin6_addr); + + const pb_socket_t udp_socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (SOCKET_INVALID == udp_socket) + return false; + + int result = connect(udp_socket, (struct sockaddr*)&sin6, sizeof(sin6)); + socket_close(udp_socket); + + return 0 == result; +} + +#ifdef PUBNUB_CALLBACK_API +/** Check whether provided address is a IPv4 mapped IPv6 address or not. + * + * @param addr Address which should be checked to be IPv4 mapped IPv6 address. + * @return @c true if IPv4 mapped IPv6 address provided, @c false otherwise + */ +static bool is_ipv4_mapped_ipv6(const struct sockaddr* addr) +{ + if (addr->sa_family != AF_INET6) + return false; + + const uint8_t prefix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; + return (memcmp(((struct sockaddr_in6*)addr)->sin6_addr.s6_addr, prefix, 12) + == 0); +} -static void prepare_port_and_hostname(pubnub_t* pb, - uint16_t* p_port, - char const** p_origin) +/** Remap IPv6 encoded IPv4 address back to IPv4. + * + * @param[in,out] addr IPv4 mapped IPv6 address which should be mapped back to + * IPv4. + */ +static void remap_ipv6_to_ipv4(struct sockaddr* addr) +{ + if (addr->sa_family != AF_INET6) + return; + + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr; + struct sockaddr_in sin4 = { 0 }; + + sin4.sin_family = AF_INET; + sin4.sin_port = sin6->sin6_port; + memcpy(&sin4.sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4); + + memset(addr, 0, sizeof(sockaddr_inX_t)); + memcpy(addr, &sin4, sizeof(struct sockaddr_in)); +} +#endif /* PUBNUB_USE_IPV6 */ +#endif /* PUBNUB_CALLBACK_API */ + +static void prepare_port_and_hostname(const pubnub_t* pb, + uint16_t* p_port, + char const** p_origin) { PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); PUBNUB_ASSERT_OPT((pb->state == PBS_READY) || (pb->state == PBS_WAIT_DNS_SEND)); @@ -65,7 +129,9 @@ static void prepare_port_and_hostname(pubnub_t* pb, *p_port = pb->port; } *p_origin = pb->origin ? pb->origin : PUBNUB_ORIGIN; -#endif +#else /* PUBNUB_ORIGIN_SETTABLE */ + *p_origin = PUBNUB_ORIGIN; +#endif /* !PUBNUB_ORIGIN_SETTABLE */ #if PUBNUB_PROXY_API switch (pb->proxy_type) { case pbproxyHTTP_CONNECT: @@ -82,107 +148,182 @@ static void prepare_port_and_hostname(pubnub_t* pb, default: break; } -#endif - return; +#endif /* PUBNUB_PROXY_API */ } - #ifdef PUBNUB_CALLBACK_API - -static void get_default_dns_ip(struct pubnub_ipv4_address *addr) { +static void get_default_ipv4_dns_ip(struct pubnub_ipv4_address* addr) +{ if (pubnub_dns_read_system_servers_ipv4(addr, 1) != 1) { - inet_pton(AF_INET, PUBNUB_DEFAULT_DNS_SERVER, addr); + inet_pton(AF_INET, PUBNUB_DEFAULT_IPV4_DNS_SERVER, addr); } } +#if PUBNUB_USE_IPV6 +static void get_default_ipv6_dns_ip(struct pubnub_ipv6_address* addr) +{ + if (pubnub_dns_read_system_servers_ipv6(addr, 1) != 1) + inet_pton(AF_INET6, PUBNUB_DEFAULT_IPV6_DNS_SERVER, addr); +} +#endif /* PUBNUB_USE_IPV6 */ + #if PUBNUB_SET_DNS_SERVERS #if PUBNUB_CHANGE_DNS_SERVERS -static void get_dns_ip(struct pbdns_servers_check* dns_check, struct sockaddr* addr) +/** Retrieve DNS server suitable for preferred routing address family. + * + * @param family System preferred routing address family (prefer IPv6 over IPv4 + * if able to connect using IPv6). + * @param[in,out] dns_check Check structure used to rotate through + * user-provided DNS server addresses. + * @param[in,out] addr Decided DNS server for origin DNS query. + */ +static void get_dns_ip(sa_family_t family, + struct pbdns_servers_check* dns_check, + struct sockaddr* addr) { - void* p = &(((struct sockaddr_in*)addr)->sin_addr.s_addr); -#if PUBNUB_USE_IPV6 - void* pv6 = ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr; -#endif - addr->sa_family = AF_INET; dns_check->dns_mask = 1; - if ((pubnub_get_dns_primary_server_ipv4((struct pubnub_ipv4_address*)p) == -1) - || (dns_check->dns_server_check & dns_check->dns_mask)) { - dns_check->dns_mask <<= 1; - if ((pubnub_get_dns_secondary_server_ipv4((struct pubnub_ipv4_address*)p) +#if PUBNUB_USE_IPV6 + bool user_provided_ipv6_dns = false; + if (AF_INET6 == family) { + addr->sa_family = AF_INET6; + struct in6_addr* pv6 = &((struct sockaddr_in6*)addr)->sin6_addr; + + // Try to use user-provided IPv6 DNS addresses (if any). + if ((pubnub_get_dns_primary_server_ipv6((struct pubnub_ipv6_address*)pv6->s6_addr) == -1) || (dns_check->dns_server_check & dns_check->dns_mask)) { dns_check->dns_mask <<= 1; -#if PUBNUB_USE_IPV6 - addr->sa_family = AF_INET6; -#else - get_default_dns_ip((struct pubnub_ipv4_address*)p); -#endif /* PUBNUB_USE_IPV6 */ + + if ((pubnub_get_dns_secondary_server_ipv6( + (struct pubnub_ipv6_address*)pv6->s6_addr) + == -1) + || (dns_check->dns_server_check & dns_check->dns_mask)) { + dns_check->dns_mask <<= 1; + } } + user_provided_ipv6_dns = !IN6_IS_ADDR_UNSPECIFIED(pv6); } -#if PUBNUB_USE_IPV6 - if (AF_INET6 == addr->sa_family) { - if ((pubnub_get_dns_primary_server_ipv6((struct pubnub_ipv6_address*)pv6) - == -1) + // For IPv4 preferred family or uninitialized IPv6 address. + if (AF_INET == family || (AF_INET6 == family && !user_provided_ipv6_dns)) { +#endif /* PUBNUB_USE_IPV6 */ + addr->sa_family = AF_INET; + void* p = &(((struct sockaddr_in*)addr)->sin_addr.s_addr); + + // Try to use user-provided IPv4 DNS addresses (if any). + if ((pubnub_get_dns_primary_server_ipv4((struct pubnub_ipv4_address*)p) == -1) || (dns_check->dns_server_check & dns_check->dns_mask)) { dns_check->dns_mask <<= 1; - if ((pubnub_get_dns_secondary_server_ipv6((struct pubnub_ipv6_address*)pv6) + if ((pubnub_get_dns_secondary_server_ipv4((struct pubnub_ipv4_address*)p) == -1) || (dns_check->dns_server_check & dns_check->dns_mask)) { dns_check->dns_mask <<= 1; - addr->sa_family = AF_INET; - get_default_dns_ip((struct pubnub_ipv4_address*)p); + if (AF_INET == family) { + // If only IPv4 supported we don't have to fall back to the IPv6. + get_default_ipv4_dns_ip((struct pubnub_ipv4_address*)p); + } } } +#if PUBNUB_USE_IPV6 + } + // Fallback to the default (well-known DNS provider) IPv6 DNS address. + if (AF_INET6 == family + && (AF_INET == addr->sa_family + && 0 == ((struct sockaddr_in*)addr)->sin_addr.s_addr)) { + void* pv6 = ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr; + get_default_ipv6_dns_ip((struct pubnub_ipv6_address*)pv6); + + if (is_ipv4_mapped_ipv6(addr)) + remap_ipv6_to_ipv4(addr); } #endif /* PUBNUB_USE_IPV6 */ } -#else -static void get_dns_ip(struct sockaddr* addr) +#else /* PUBNUB_CHANGE_DNS_SERVERS */ +/** Retrieve DNS server suitable for preferred routing address family. + * + * @param family System preferred routing address family (prefer IPv6 over IPv4 + * if able to connect using IPv6). + * @param[in,out] addr Decided DNS server for origin DNS query. + */ +static void get_dns_ip(sa_family_t family, struct sockaddr* addr) { - void* p = &(((struct sockaddr_in*)addr)->sin_addr.s_addr); #if PUBNUB_USE_IPV6 - void* pv6 = ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr; -#endif - addr->sa_family = AF_INET; - if ((pubnub_get_dns_primary_server_ipv4((struct pubnub_ipv4_address*)p) == -1) - && (pubnub_get_dns_secondary_server_ipv4((struct pubnub_ipv4_address*)p) - == -1)) { -#if PUBNUB_USE_IPV6 - addr->sa_family = AF_INET6; -#else - get_default_dns_ip((struct pubnub_ipv4_address*)p); -#endif /* PUBNUB_USE_IPV6 */ + bool user_provided_ipv6_dns = false; + if (AF_INET6 == family) { + addr->sa_family = AF_INET6; + struct in6_addr* pv6 = &((struct sockaddr_in6*)addr)->sin6_addr; + + // Try to use user-provided IPv6 DNS addresses (if any). + if (pubnub_get_dns_primary_server_ipv6((struct pubnub_ipv6_address*)pv6->s6_addr) + == -1) { + pubnub_get_dns_secondary_server_ipv6( + (struct pubnub_ipv6_address*)pv6->s6_addr); + } + user_provided_ipv6_dns = !IN6_IS_ADDR_UNSPECIFIED(pv6); } -#if PUBNUB_USE_IPV6 - if (AF_INET6 == addr->sa_family) { - if ((pubnub_get_dns_primary_server_ipv6((struct pubnub_ipv6_address*)pv6) - == -1) - && (pubnub_get_dns_secondary_server_ipv6((struct pubnub_ipv6_address*)pv6) - == -1)) { - addr->sa_family = AF_INET; - get_default_dns_ip((struct pubnub_ipv4_address*)p); + // For IPv4 preferred family or uninitialized IPv6 address. + if (AF_INET == family || (AF_INET6 == family && !user_provided_ipv6_dns)) { +#endif /* PUBNUB_USE_IPV6 */ + addr->sa_family = AF_INET; + void* p = &(((struct sockaddr_in*)addr)->sin_addr.s_addr); + + // Try to use user-provided IPv4 DNS addresses (if any). + if ((pubnub_get_dns_primary_server_ipv4((struct pubnub_ipv4_address*)p) == -1) + && (pubnub_get_dns_secondary_server_ipv4((struct pubnub_ipv4_address*)p) + == -1) + && AF_INET == family) { + // If only IPv4 supported we don't have to fall back to the IPv6. + get_default_ipv4_dns_ip((struct pubnub_ipv4_address*)p); } +#if PUBNUB_USE_IPV6 } -#endif + // Fallback to the default (well-known DNS provider) IPv6 DNS address. + if (AF_INET6 == family + && (AF_INET == addr->sa_family + && 0 == ((struct sockaddr_in*)addr)->sin_addr.s_addr)) { + struct in6_addr* pv6 = &((struct sockaddr_in6*)addr)->sin6_addr; + get_default_ipv6_dns_ip((struct pubnub_ipv6_address*)pv6->s6_addr); + + if (is_ipv4_mapped_ipv6(addr)) + remap_ipv6_to_ipv4(addr); + } +#endif /* PUBNUB_USE_IPV6 */ } -#endif /* PUBNUB_CHANGE_DNS_SERVERS */ -#else -static void get_dns_ip(struct sockaddr* addr) +#endif /* !PUBNUB_CHANGE_DNS_SERVERS */ +#else /* PUBNUB_SET_DNS_SERVERS */ +/** Retrieve default DNS server suitable for preferred routing address family. + * + * @param family System preferred routing address family (prefer IPv6 over IPv4 + * if able to connect using IPv6). + * @param[in,out] addr Decided DNS server for origin DNS query. + */ +static void get_dns_ip(sa_family_t family, struct sockaddr* addr) { - addr->sa_family = AF_INET; - struct pubnub_ipv4_address* p = (struct pubnub_ipv4_address*)&(((struct sockaddr_in*)addr)->sin_addr.s_addr); - get_default_dns_ip(p); + addr->sa_family = family; + if (AF_INET == family) { + struct pubnub_ipv4_address* p = (struct pubnub_ipv4_address*)&( + ((struct sockaddr_in*)addr)->sin_addr.s_addr); + get_default_ipv4_dns_ip(p); + } +#if PUBNUB_USE_IPV6 + else { + struct in6_addr* pv6 = &((struct sockaddr_in6*)addr)->sin6_addr; + get_default_ipv6_dns_ip((struct pubnub_ipv6_address*)pv6->s6_addr); + + if (is_ipv4_mapped_ipv6(addr)) + remap_ipv6_to_ipv4(addr); + } +#endif /* PUBNUB_USE_IPV6 */ } -#endif /* PUBNUB_SET_DNS_SERVERS */ +#endif /* !PUBNUB_SET_DNS_SERVERS */ -static enum pbpal_resolv_n_connect_result -connect_TCP_socket(pb_socket_t* skt, - struct pubnub_options* options, - struct sockaddr* dest, - const uint16_t port) +static enum pbpal_resolv_n_connect_result connect_TCP_socket(pubnub_t* pb, + struct sockaddr* dest, + const uint16_t port) { - size_t sockaddr_size; + pb_socket_t* skt = &pb->pal.socket; + struct pubnub_options* options = &pb->options; + size_t sockaddr_size; PUBNUB_ASSERT_OPT(dest != NULL); @@ -208,7 +349,7 @@ connect_TCP_socket(pb_socket_t* skt, default: PUBNUB_LOG_ERROR( "connect_TCP_socket(socket=%ld): invalid internet protocol " - "dest->sa_family =%uh\n", + "dest->sa_family =%u\n", (long)*skt, dest->sa_family); return pbpal_connect_failed; @@ -217,6 +358,18 @@ connect_TCP_socket(pb_socket_t* skt, if (SOCKET_INVALID == *skt) { return pbpal_connect_resource_failure; } +#if defined(_WIN32) + const BOOL enabled = + pbccTrue == options->tcp_keepalive.keepalive.enabled ? TRUE : FALSE; + (void)setsockopt( + *skt, SOL_SOCKET, SO_KEEPALIVE, (const char*)&enabled, sizeof(enabled)); + // The most reliable time to set idle/interval/count + // (pbpal_set_tcp_keepalive) is after connection completion. +#else /* defined(_WIN32) */ + const int enabled = pbccTrue == options->tcp_keepalive.enabled ? 1 : 0; + (void)setsockopt(*skt, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled)); + pbpal_set_tcp_keepalive(pb); +#endif /* !defined(_WIN32) */ options->use_blocking_io = false; pbpal_set_socket_blocking_io(*skt, options->use_blocking_io); socket_disable_SIGPIPE(*skt); @@ -241,26 +394,25 @@ static void if_no_retry_close_socket(pb_socket_t* skt, struct pubnub_flags* flag #if PUBNUB_CHANGE_DNS_SERVERS -int pbpal_dns_rotate_server(pubnub_t *pb) +int pbpal_dns_rotate_server(pubnub_t* pb) { struct pbdns_servers_check* dns_check = &pb->dns_check; - struct pubnub_flags* flags = &pb->flags; + struct pubnub_flags* flags = &pb->flags; dns_check->dns_server_check |= dns_check->dns_mask; if ((dns_check->dns_mask >= PUBNUB_MAX_DNS_SERVERS_MASK) - && (flags->rotations_count < PUBNUB_MAX_DNS_ROTATION)) - { + && (flags->rotations_count < PUBNUB_MAX_DNS_ROTATION)) { dns_check->dns_server_check = 0; /** Update how many times all DNS servers has been tried to process query */ flags->rotations_count++; } - + if (flags->rotations_count >= PUBNUB_MAX_DNS_ROTATION) { flags->retry_after_close = false; - flags->rotations_count = 1; + flags->rotations_count = 1; return 1; } - + /** Going with new DNS server, after retry, brings new set of queries */ flags->sent_queries = 0; @@ -274,7 +426,7 @@ static void check_dns_server_error(struct pbdns_servers_check* dns_check, dns_check->dns_server_check |= dns_check->dns_mask; if (dns_check->dns_mask < PUBNUB_MAX_DNS_SERVERS_MASK) { /** Going with new DNS server, after retry, brings new set of queries */ - flags->sent_queries = 0; + flags->sent_queries = 0; flags->retry_after_close = true; } } @@ -291,54 +443,109 @@ void pbpal_multiple_addresses_reset_counters(struct pubnub_multi_addresses* spar #endif } - static enum pbpal_resolv_n_connect_result -try_TCP_connect_spare_address(pb_socket_t* skt, - struct pubnub_multi_addresses* spare_addresses, - struct pubnub_options* options, - struct pubnub_flags* flags, - const uint16_t port) +try_TCP_connect_spare_address(pubnub_t* pb, sa_family_t family, const uint16_t port) { + struct pubnub_multi_addresses* spare_addresses = &pb->spare_addresses; + struct pubnub_options* options = &pb->options; + struct pubnub_flags* flags = &pb->flags; + pb_socket_t* skt = &pb->pal.socket; enum pbpal_resolv_n_connect_result rslt = pbpal_resolv_resource_failure; time_t tt = time(NULL); - if (spare_addresses->ipv4_index < spare_addresses->n_ipv4 #if PUBNUB_USE_IPV6 - && !options->ipv6_connectivity + if (AF_INET6 == family) { + if (spare_addresses->ipv6_index < spare_addresses->n_ipv6) { + PUBNUB_LOG_TRACE("spare_addresses->ipv6_index = %d, " + "spare_addresses->n_ipv6 = %d.\n", + spare_addresses->ipv6_index, + spare_addresses->n_ipv6); + /* Need at least a second to live */ + if (spare_addresses->ttl_ipv6[spare_addresses->ipv6_index] - 2 + > tt - spare_addresses->time_of_the_last_dns_query) { + struct sockaddr_in6 dest = { 0 }; + dest.sin6_family = AF_INET6; + PUBNUB_LOG_TRACE( + "spare_addresses->ttl_ipv6[spare_addresses->ipv6_index]=%" + "ld > (time() - " + "spare_addresses->time_of_the_last_dns_query)=%ld\n", + (long)(spare_addresses->ttl_ipv6[spare_addresses->ipv6_index]), + (long)(tt - spare_addresses->time_of_the_last_dns_query)); + memcpy( + dest.sin6_addr.s6_addr, + spare_addresses->ipv6_addresses[spare_addresses->ipv6_index].ipv6, + sizeof dest.sin6_addr.s6_addr); + rslt = connect_TCP_socket(pb, (struct sockaddr*)&dest, port); + if (pbpal_connect_failed == rslt) { + pbpal_report_error_from_environment(NULL, __FILE__, __LINE__); + } +#if defined(_WIN32) + // Complete TCP Keep-Alive configuration if connection established. + if (pbpal_connect_success == rslt) + pbpal_set_tcp_keepalive(pb); +#endif /* defined(_WIN32) */ + } + else { + char str[INET6_ADDRSTRLEN]; + uint8_t* ipv6 = + spare_addresses->ipv6_addresses[spare_addresses->ipv6_index].ipv6; + PUBNUB_LOG_TRACE("Spare IPv6: %s address expired.\n", + inet_ntop(AF_INET6, ipv6, str, INET6_ADDRSTRLEN)); + rslt = pbpal_connect_failed; + } + + if (pbpal_connect_failed == rslt) { + flags->retry_after_close = + (++spare_addresses->ipv6_index < spare_addresses->n_ipv6); + if_no_retry_close_socket(skt, flags); +#if PUBNUB_USE_SSL + flags->trySSL = options->useSSL; +#endif + } + } + } #endif /* PUBNUB_USE_IPV6 */ - ) { - PUBNUB_LOG_TRACE( - "spare_addresses->ipv4_index = %d, spare_addresses->n_ipv4 = %d.\n", - spare_addresses->ipv4_index, - spare_addresses->n_ipv4); + + if ((AF_INET == family || pbpal_connect_failed == rslt) + && spare_addresses->ipv4_index < spare_addresses->n_ipv4) { + PUBNUB_LOG_TRACE("spare_addresses->ipv4_index = %d, " + "spare_addresses->n_ipv4 = %d.\n", + spare_addresses->ipv4_index, + spare_addresses->n_ipv4); /* Need at least a second to live */ if (spare_addresses->ttl_ipv4[spare_addresses->ipv4_index] - 2 > tt - spare_addresses->time_of_the_last_dns_query) { struct sockaddr_in dest = { 0 }; PUBNUB_LOG_TRACE( - "spare_addresses->ttl_ipv4[spare_addresses->ipv4_index]=%ld > " - "(time() - spare_addresses->time_of_the_last_dns_query)=%ld\n", + "spare_addresses->ttl_ipv4[spare_addresses->ipv4_index]=%" + "ld > " + "(time() - " + "spare_addresses->time_of_the_last_dns_query)=%ld\n", (long)(spare_addresses->ttl_ipv4[spare_addresses->ipv4_index]), (long)(tt - spare_addresses->time_of_the_last_dns_query)); memcpy(&(dest.sin_addr.s_addr), spare_addresses->ipv4_addresses[spare_addresses->ipv4_index].ipv4, sizeof dest.sin_addr.s_addr); dest.sin_family = AF_INET; - rslt = connect_TCP_socket(skt, options, (struct sockaddr*)&dest, port); + rslt = connect_TCP_socket(pb, (struct sockaddr*)&dest, port); if (pbpal_connect_failed == rslt) { pbpal_report_error_from_environment(NULL, __FILE__, __LINE__); } +#if defined(_WIN32) + // Complete TCP Keep-Alive configuration if connection established. + if (pbpal_connect_success == rslt) + pbpal_set_tcp_keepalive(pb); +#endif /* defined(_WIN32) */ } else { + char str[INET_ADDRSTRLEN]; uint8_t* ipv4 = spare_addresses->ipv4_addresses[spare_addresses->ipv4_index].ipv4; - PUBNUB_LOG_TRACE("Spare IPv4: %d.%d.%d.%d address expired.\n", - ipv4[0], - ipv4[1], - ipv4[2], - ipv4[3]); + PUBNUB_LOG_TRACE("Spare IPv4: %s address expired.\n", + inet_ntop(AF_INET, ipv4, str, INET_ADDRSTRLEN)); rslt = pbpal_connect_failed; } + if (pbpal_connect_failed == rslt) { flags->retry_after_close = (++spare_addresses->ipv4_index < spare_addresses->n_ipv4); @@ -348,58 +555,9 @@ try_TCP_connect_spare_address(pb_socket_t* skt, #endif } } -#if PUBNUB_USE_IPV6 - else if (spare_addresses->ipv6_index < spare_addresses->n_ipv6) { - PUBNUB_LOG_TRACE( - "spare_addresses->ipv6_index = %d, spare_addresses->n_ipv6 = %d.\n", - spare_addresses->ipv6_index, - spare_addresses->n_ipv6); - /* Need at least a second to live */ - if (spare_addresses->ttl_ipv6[spare_addresses->ipv6_index] - 2 - > tt - spare_addresses->time_of_the_last_dns_query) { - struct sockaddr_in6 dest = { 0 }; - PUBNUB_LOG_TRACE( - "spare_addresses->ttl_ipv6[spare_addresses->ipv6_index]=%ld > " - "(time() - spare_addresses->time_of_the_last_dns_query)=%ld\n", - (long)(spare_addresses->ttl_ipv6[spare_addresses->ipv6_index]), - (long)(tt - spare_addresses->time_of_the_last_dns_query)); - memcpy(dest.sin6_addr.s6_addr, - spare_addresses->ipv6_addresses[spare_addresses->ipv6_index].ipv6, - sizeof dest.sin6_addr.s6_addr); - dest.sin6_family = AF_INET6; - rslt = connect_TCP_socket(skt, options, (struct sockaddr*)&dest, port); - if (pbpal_connect_failed == rslt) { - pbpal_report_error_from_environment(NULL, __FILE__, __LINE__); - } - } - else { - uint8_t* ipv6 = - spare_addresses->ipv6_addresses[spare_addresses->ipv6_index].ipv6; - PUBNUB_LOG_TRACE( - "Spare IPv6: %X:%X:%X:%X:%X:%X:%X:%X address expired.\n", - ipv6[0] * 256 + ipv6[1], - ipv6[2] * 256 + ipv6[3], - ipv6[4] * 256 + ipv6[5], - ipv6[6] * 256 + ipv6[7], - ipv6[8] * 256 + ipv6[9], - ipv6[10] * 256 + ipv6[11], - ipv6[12] * 256 + ipv6[13], - ipv6[14] * 256 + ipv6[15]); - rslt = pbpal_connect_failed; - } - if (pbpal_connect_failed == rslt) { - flags->retry_after_close = - (++spare_addresses->ipv6_index < spare_addresses->n_ipv6); - if_no_retry_close_socket(skt, flags); -#if PUBNUB_USE_SSL - flags->trySSL = options->useSSL; -#endif - } - } -#endif /* PUBNUB_USE_IPV6 */ - else { + else pbpal_multiple_addresses_reset_counters(spare_addresses); - } + if ((pbpal_connect_failed == rslt) && !flags->retry_after_close) { rslt = pbpal_resolv_resource_failure; pbpal_multiple_addresses_reset_counters(spare_addresses); @@ -410,192 +568,226 @@ try_TCP_connect_spare_address(pb_socket_t* skt, #endif /* PUBNUB_USE_MULTIPLE_ADDRESSES */ #endif /* PUBNUB_CALLBACK_API */ - enum pbpal_resolv_n_connect_result pbpal_resolv_and_connect(pubnub_t* pb) { int error; uint16_t port = HTTP_PORT; char const* origin; +#if PUBNUB_USE_IPV6 + bool use_ipv6_addresses = is_ipv6_supported(); + sa_family_t family = use_ipv6_addresses ? AF_INET6 : AF_INET; +#else /* PUBNUB_USE_IPV6 */ + sa_family_t family = AF_INET; +#endif /* !PUBNUB_USE_IPV6 */ #ifdef PUBNUB_NTF_RUNTIME_SELECTION - if (PNA_CALLBACK == pb->api_policy) { // if policy + if (PNA_CALLBACK == pb->api_policy) { // if policy #endif #ifdef PUBNUB_CALLBACK_API - sockaddr_inX_t dest = { 0 }; + sockaddr_inX_t dest = { 0 }; #if PUBNUB_PROXY_API #if PUBNUB_USE_IPV6 - const bool has_ipv6_proxy = 0 != pb->proxy_ipv6_address.ipv6[0] - || 0 != pb->proxy_ipv6_address.ipv6[1]; + const bool has_ipv6_proxy = 0 != pb->proxy_ipv6_address.ipv6[0] + || 0 != pb->proxy_ipv6_address.ipv6[1]; +#else /* !PUBNUB_USE_IPV6 */ + const bool has_ipv6_proxy = false; #endif /* PUBNUB_USE_IPV6 */ #endif /* PUBNUB_PROXY_API */ - prepare_port_and_hostname(pb, &port, &origin); + prepare_port_and_hostname(pb, &port, &origin); #if PUBNUB_PROXY_API - if (0 != pb->proxy_ipv4_address.ipv4[0] #if PUBNUB_USE_IPV6 - && (!pb->options.ipv6_connectivity || !has_ipv6_proxy) -#endif /* PUBNUB_USE_IPV6 */ - ) { - struct sockaddr_in dest = { 0 }; - PUBNUB_LOG_TRACE("(0 != pb->proxy_ipv4_address.ipv4[0]) - "); - memcpy(&(dest.sin_addr.s_addr), - pb->proxy_ipv4_address.ipv4, - sizeof dest.sin_addr.s_addr); - dest.sin_family = AF_INET; + if (AF_INET6 == family && has_ipv6_proxy) { + struct sockaddr_in6 dest = { 0 }; + dest.sin6_family = AF_INET6; + PUBNUB_LOG_TRACE("(0 != pb->proxy_ipv6_address.ipv6[0]) ||" + " (0 != pb->proxy_ipv6_address.ipv6[1]) - "); + memcpy(dest.sin6_addr.s6_addr, + pb->proxy_ipv6_address.ipv6, + sizeof dest.sin6_addr.s6_addr); #if defined(_WIN32) - enum pbpal_resolv_n_connect_result rslt = connect_TCP_socket( - &pb->pal.socket, &pb->options, (struct sockaddr*)&dest, port); - if (pbpal_connect_success == rslt) pbpal_set_tcp_keepalive(pb); -#else - return connect_TCP_socket( - &pb->pal.socket, &pb->options, (struct sockaddr*)&dest, port); -#endif - } -#if PUBNUB_USE_IPV6 - if (has_ipv6_proxy) { - struct sockaddr_in6 dest = { 0 }; - PUBNUB_LOG_TRACE("(0 != pb->proxy_ipv6_address.ipv6[0]) ||" - " (0 != pb->proxy_ipv6_address.ipv6[1]) - "); - memcpy(dest.sin6_addr.s6_addr, - pb->proxy_ipv6_address.ipv6, - sizeof dest.sin6_addr.s6_addr); - dest.sin6_family = AF_INET6; + enum pbpal_resolv_n_connect_result rslt = + connect_TCP_socket(pb, (struct sockaddr*)&dest, port); + // Complete TCP Keep-Alive configuration if connection established. + if (pbpal_connect_success == rslt) + pbpal_set_tcp_keepalive(pb); +#else /* defined(_WIN32) */ + return connect_TCP_socket(pb, (struct sockaddr*)&dest, port); +#endif /* !defined(_WIN32) */ + } +#endif /* PUBNUB_USE_IPV6 */ + if (0 != pb->proxy_ipv4_address.ipv4[0] + && (AF_INET6 != family || !has_ipv6_proxy)) { + struct sockaddr_in dest = { 0 }; + dest.sin_family = AF_INET; + PUBNUB_LOG_TRACE("(0 != pb->proxy_ipv4_address.ipv4[0]) - "); + memcpy(&(dest.sin_addr.s_addr), + pb->proxy_ipv4_address.ipv4, + sizeof dest.sin_addr.s_addr); #if defined(_WIN32) - enum pbpal_resolv_n_connect_result rslt = connect_TCP_socket( - &pb->pal.socket, &pb->options, (struct sockaddr*)&dest, port); - if (pbpal_connect_success == rslt) pbpal_set_tcp_keepalive(pb); + enum pbpal_resolv_n_connect_result rslt = + connect_TCP_socket(pb, (struct sockaddr*)&dest, port); + // Complete TCP Keep-Alive configuration if connection established. + if (pbpal_connect_success == rslt) + pbpal_set_tcp_keepalive(pb); #else - return connect_TCP_socket( - &pb->pal.socket, &pb->options, (struct sockaddr*)&dest, port); + return connect_TCP_socket(pb, (struct sockaddr*)&dest, port); #endif - } -#endif /* PUBNUB_USE_IPV6 */ + } #endif /* PUBNUB_PROXY_API */ #if PUBNUB_USE_MULTIPLE_ADDRESSES - { - enum pbpal_resolv_n_connect_result rslt; - rslt = try_TCP_connect_spare_address( - &pb->pal.socket, &pb->spare_addresses, &pb->options, &pb->flags, port); + { + enum pbpal_resolv_n_connect_result rslt; + rslt = try_TCP_connect_spare_address(pb, family, port); #if defined(_WIN32) - if (pbpal_connect_success == rslt) pbpal_set_tcp_keepalive(pb); -#endif - if (rslt != pbpal_resolv_resource_failure) { - return rslt; + // Complete TCP Keep-Alive configuration if connection established. + if (pbpal_connect_success == rslt) + pbpal_set_tcp_keepalive(pb); +#endif /* defined(_WIN32) */ + if (rslt != pbpal_resolv_resource_failure) + return rslt; } - } -#endif +#endif /* !defined(_WIN32) */ + if (SOCKET_INVALID == pb->pal.socket) { + /* Reset DNS server address at the start of new resolution */ + memset(&pb->dns_queries, 0, sizeof(pb->dns_queries)); + memset(&pb->dns_addr, 0, sizeof(pb->dns_addr)); + #if PUBNUB_CHANGE_DNS_SERVERS - get_dns_ip(&pb->dns_check, (struct sockaddr*)&dest); -#else - get_dns_ip((struct sockaddr*)&dest); -#endif - if (SOCKET_INVALID == pb->pal.socket) { - pb->pal.socket = - socket(((struct sockaddr*)&dest)->sa_family, SOCK_DGRAM, IPPROTO_UDP); - } - if (SOCKET_INVALID == pb->pal.socket) { - return pbpal_resolv_resource_failure; - } - pb->options.use_blocking_io = false; - pbpal_set_blocking_io(pb); - error = - send_dns_query(pb->pal.socket, (struct sockaddr*)&dest, origin, QUERY_TYPE); - if (error < 0) { + get_dns_ip(family, &pb->dns_check, (struct sockaddr*)&dest); +#else /* PUBNUB_CHANGE_DNS_SERVERS */ + get_dns_ip(family, (struct sockaddr*)&dest); +#endif /* !PUBNUB_CHANGE_DNS_SERVERS */ + /* Store DNS server address for later use in pbpal_check_resolv_and_connect */ + memcpy(&pb->dns_addr, &dest, sizeof(dest)); + + pb->pal.socket = socket( + ((struct sockaddr*)&dest)->sa_family, SOCK_DGRAM, IPPROTO_UDP); + } + if (SOCKET_INVALID == pb->pal.socket) { + return pbpal_resolv_resource_failure; + } + pb->options.use_blocking_io = false; + pbpal_set_blocking_io(pb); + error = send_dns_query(pb->pal.socket, + (struct sockaddr*)&pb->dns_addr, + origin, + &pb->dns_queries); + if (error < 0) { #if PUBNUB_CHANGE_DNS_SERVERS - check_dns_server_error(&pb->dns_check, &pb->flags); - if_no_retry_close_socket(&pb->pal.socket, &pb->flags); + check_dns_server_error(&pb->dns_check, &pb->flags); + if_no_retry_close_socket(&pb->pal.socket, &pb->flags); #endif - return pbpal_resolv_failed_send; - } - if (error > 0) { - return pbpal_resolv_send_wouldblock; - } - pb->flags.sent_queries++; - - return pbpal_resolv_sent; + return pbpal_resolv_failed_send; + } + if (error > 0) { + return pbpal_resolv_send_wouldblock; + } + pb->flags.sent_queries++; + + return pbpal_resolv_sent; #endif /* PUBNUB_CALLBACK_API */ #ifdef PUBNUB_NTF_RUNTIME_SELECTION - } else { // if policy + } + else { // if policy #endif #if !defined PUBNUB_CALLBACK_API || defined PUBNUB_NTF_RUNTIME_SELECTION - char port_string[20]; - struct addrinfo* result; - struct addrinfo* it; - struct addrinfo hint; - - hint.ai_socktype = SOCK_STREAM; - hint.ai_family = AF_UNSPEC; - hint.ai_protocol = hint.ai_flags = hint.ai_addrlen = 0; - hint.ai_addr = NULL; - hint.ai_canonname = NULL; - hint.ai_next = NULL; - - prepare_port_and_hostname(pb, &port, &origin); - snprintf(port_string, sizeof port_string, "%hu", port); - error = getaddrinfo(origin, port_string, &hint, &result); - if (error != 0) { - return pbpal_resolv_failed_processing; - } + char port_string[20]; + struct addrinfo* result; + struct addrinfo* it; + struct addrinfo hint; + + hint.ai_socktype = SOCK_STREAM; + hint.ai_family = AF_UNSPEC; + hint.ai_protocol = hint.ai_flags = hint.ai_addrlen = 0; + hint.ai_addr = NULL; + hint.ai_canonname = NULL; + hint.ai_next = NULL; + + prepare_port_and_hostname(pb, &port, &origin); + snprintf(port_string, sizeof port_string, "%hu", port); + error = getaddrinfo(origin, port_string, &hint, &result); + if (error != 0) { + return pbpal_resolv_failed_processing; + } #if PUBNUB_USE_IPV6 - for (int pass = 0; pass < 2; ++pass) { - bool prioritize_ipv6 = pass == 0 && pb->options.ipv6_connectivity; + for (int pass = 0; pass < 2; ++pass) { + bool prioritize_ipv6 = pass == 0 && AF_INET6 == family; #endif /* PUBNUB_USE_IPV6 */ - for (it = result; it != NULL; it = it->ai_next) { + for (it = result; it != NULL; it = it->ai_next) { #if PUBNUB_USE_IPV6 - if (prioritize_ipv6 && it->ai_family != AF_INET6) { continue; } - if (!prioritize_ipv6 && it->ai_family != AF_INET) { continue; } + if (prioritize_ipv6 && it->ai_family != AF_INET6) + continue; + if (!prioritize_ipv6 && it->ai_family != AF_INET) + continue; #endif /* PUBNUB_USE_IPV6 */ - pb->pal.socket = - socket(it->ai_family, it->ai_socktype, it->ai_protocol); - if (pb->pal.socket == SOCKET_INVALID) { - continue; - } - - pbpal_set_blocking_io(pb); -#ifndef _WIN32 - const int enabled = pbccTrue == pb->options.tcp_keepalive.enabled ? 1 : 0; - (void)setsockopt(pb->pal.socket, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled)); - pbpal_set_tcp_keepalive(pb); -#endif - if (connect(pb->pal.socket, it->ai_addr, it->ai_addrlen) == SOCKET_ERROR) { - if (socket_would_block()) { - error = 1; - break; - } - else { - PUBNUB_LOG_WARNING("socket connect() failed, will try " - "another IP address, if available\n"); - socket_close(pb->pal.socket); - pb->pal.socket = SOCKET_INVALID; + pb->pal.socket = + socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (pb->pal.socket == SOCKET_INVALID) { continue; } +#if defined(_WIN32) + const BOOL enabled = + pbccTrue == pb->options.tcp_keepalive.enabled ? TRUE : FALSE; + (void)setsockopt(pb->pal.socket, + SOL_SOCKET, + SO_KEEPALIVE, + (const char*)&enabled, + sizeof(enabled)); + // The most reliable time to set idle/interval/count + // (pbpal_set_tcp_keepalive) is after connection completion. +#else /* defined(_WIN32) */ + const int enabled = + pbccTrue == pb->options.tcp_keepalive.enabled ? 1 : 0; + (void)setsockopt( + pb->pal.socket, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled)); + pbpal_set_tcp_keepalive(pb); +#endif /* !defined(_WIN32) */ + + pbpal_set_blocking_io(pb); + if (connect(pb->pal.socket, it->ai_addr, it->ai_addrlen) + == SOCKET_ERROR) { + if (socket_would_block()) { + error = 1; + break; + } + else { + PUBNUB_LOG_WARNING( + "socket connect() failed, will try " + "another IP address, if available\n"); + socket_close(pb->pal.socket); + pb->pal.socket = SOCKET_INVALID; + continue; + } + } + break; } - break; - } #if PUBNUB_USE_IPV6 - if (1 == error) break; - } + if (1 == error) + break; + } #endif /* PUBNUB_USE_IPV6 */ - freeaddrinfo(result); + freeaddrinfo(result); - if (NULL == it) { - return pbpal_connect_failed; - } + if (NULL == it) { + return pbpal_connect_failed; + } - socket_set_rcv_timeout(pb->pal.socket, pb->transaction_timeout_ms); - socket_disable_SIGPIPE(pb->pal.socket); + socket_set_rcv_timeout(pb->pal.socket, pb->transaction_timeout_ms); + socket_disable_SIGPIPE(pb->pal.socket); #if defined(_WIN32) - if (!error) pbpal_set_tcp_keepalive(pb); + if (!error) + pbpal_set_tcp_keepalive(pb); #endif - return error ? pbpal_connect_wouldblock : pbpal_connect_success; + return error ? pbpal_connect_wouldblock : pbpal_connect_success; #endif /* !defined PUBNUB_CALLBACK_API || defined PUBNUB_NTF_RUNTIME_SELECTION */ #ifdef PUBNUB_NTF_RUNTIME_SELECTION - } // if policy + } // if policy #endif } @@ -608,85 +800,102 @@ enum pbpal_resolv_n_connect_result pbpal_resolv_and_connect(pubnub_t* pb) enum pbpal_resolv_n_connect_result pbpal_check_resolv_and_connect(pubnub_t* pb) { #ifdef PUBNUB_NTF_RUNTIME_SELECTION - if (PNA_CALLBACK == pb->api_policy) { // if policy + if (PNA_CALLBACK == pb->api_policy) { // if policy #endif #ifdef PUBNUB_CALLBACK_API + sockaddr_inX_t dest = { 0 }; + uint16_t port = HTTP_PORT; + enum pbpal_resolv_n_connect_result rslt; - sockaddr_inX_t dns_server = { 0 }; - sockaddr_inX_t dest = { 0 }; - uint16_t port = HTTP_PORT; - enum pbpal_resolv_n_connect_result rslt; - - PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); - PUBNUB_ASSERT_OPT(pb->state == PBS_WAIT_DNS_RCV); + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + PUBNUB_ASSERT_OPT(pb->state == PBS_WAIT_DNS_RCV); #if PUBNUB_USE_SSL - if (pb->flags.trySSL) { - PUBNUB_ASSERT(pb->options.useSSL); - port = TLS_PORT; - } + if (pb->flags.trySSL) { + PUBNUB_ASSERT(pb->options.useSSL); + port = TLS_PORT; + } #endif #if PUBNUB_PROXY_API - if (pbproxyNONE != pb->proxy_type) { - port = pb->proxy_port; - } -#endif -#if PUBNUB_CHANGE_DNS_SERVERS - get_dns_ip(&pb->dns_check, (struct sockaddr*)&dns_server); -#else - get_dns_ip((struct sockaddr*)&dns_server); + if (pbproxyNONE != pb->proxy_type) { + port = pb->proxy_port; + } #endif - switch (read_dns_response(pb->pal.socket, - (struct sockaddr*)&dns_server, - (struct sockaddr*)&dest PBDNS_OPTIONAL_PARAMS_PB)) { - case -1: + /* Reuse the DNS server address that was stored when sending the query */ + switch (read_dns_response(pb->pal.socket, + (struct sockaddr*)&pb->dns_addr, + &pb->dns_queries PBDNS_OPTIONAL_PARAMS_PB)) { + case -1: #if PUBNUB_CHANGE_DNS_SERVERS - check_dns_server_error(&pb->dns_check, &pb->flags); + check_dns_server_error(&pb->dns_check, &pb->flags); #endif - return pbpal_resolv_failed_rcv; - case +1: - return pbpal_resolv_rcv_wouldblock; - case 0: + return pbpal_resolv_failed_rcv; + case +1: + return pbpal_resolv_rcv_wouldblock; + case 0: #if PUBNUB_CHANGE_DNS_SERVERS - pb->flags.rotations_count = 0; + pb->flags.rotations_count = 0; #endif /* PUBNUB_CHANGE_DNS_SERVERS */ - break; - } - socket_close(pb->pal.socket); - - rslt = connect_TCP_socket( - &pb->pal.socket, &pb->options, (struct sockaddr*)&dest, port); -#if PUBNUB_USE_MULTIPLE_ADDRESSES - if (pbpal_connect_failed == rslt) { - if (AF_INET == ((struct sockaddr*)&dest)->sa_family) { - pb->flags.retry_after_close = - (++pb->spare_addresses.ipv4_index < pb->spare_addresses.n_ipv4); + break; } + socket_close(pb->pal.socket); + #if PUBNUB_USE_IPV6 - else if (AF_INET6 == ((struct sockaddr*)&dest)->sa_family) { - pb->flags.retry_after_close = - (++pb->spare_addresses.ipv6_index < pb->spare_addresses.n_ipv6); + if (is_ipv6_supported() + && !IN6_IS_ADDR_UNSPECIFIED( + &((struct sockaddr_in6*)&pb->dns_queries.dns_aaaa_addr)->sin6_addr)) { + ((struct sockaddr_in6*)&dest)->sin6_family = AF_INET6; + memcpy(((struct sockaddr_in6*)&dest)->sin6_addr.s6_addr, + ((struct sockaddr_in6*)&pb->dns_queries.dns_aaaa_addr) + ->sin6_addr.s6_addr, + 16); + } + else { + ((struct sockaddr_in*)&dest)->sin_family = AF_INET; + memcpy(&((struct sockaddr_in*)&dest)->sin_addr.s_addr, + &pb->dns_queries.dns_a_addr.sin_addr.s_addr, + 4); } +#else /* PUBNUB_USE_IPV6 */ + dest.sin_family = AF_INET; + memcpy(&dest.sin_addr.s_addr, &pb->dns_queries.dns_a_addr.sin_addr.s_addr, 4); +#endif /* !PUBNUB_USE_IPV6 */ + + rslt = connect_TCP_socket(pb, (struct sockaddr*)&dest, port); +#if PUBNUB_USE_MULTIPLE_ADDRESSES + if (pbpal_connect_failed == rslt) { + if (AF_INET == ((struct sockaddr*)&dest)->sa_family) { + pb->flags.retry_after_close = (++pb->spare_addresses.ipv4_index + < pb->spare_addresses.n_ipv4); + } +#if PUBNUB_USE_IPV6 + else if (AF_INET6 == ((struct sockaddr*)&dest)->sa_family) { + pb->flags.retry_after_close = (++pb->spare_addresses.ipv6_index + < pb->spare_addresses.n_ipv6); + } #endif #if PUBNUB_USE_SSL - pb->flags.trySSL = pb->options.useSSL; + pb->flags.trySSL = pb->options.useSSL; #endif - } + } #endif /* PUBNUB_USE_MULTIPLE_ADDRESSES */ #if defined(_WIN32) - if (pbpal_connect_success == rslt) pbpal_set_tcp_keepalive(pb); -#endif - return rslt; + // Complete TCP Keep-Alive configuration if connection established. + if (pbpal_connect_success == rslt) + pbpal_set_tcp_keepalive(pb); +#endif /* defined(_WIN32) */ + return rslt; #endif /* PUBNUB_CALLBACK_API */ #ifdef PUBNUB_NTF_RUNTIME_SELECTION - } else { // if policy + } + else { // if policy #endif #if !defined PUBNUB_CALLBACK_API || defined PUBNUB_NTF_RUNTIME_SELECTION - PUBNUB_UNUSED(pb); + PUBNUB_UNUSED(pb); - /* Under regular BSD-ish sockets, this function should not be - called unless using async DNS, so this is an error */ - return pbpal_connect_failed; + /* Under regular BSD-ish sockets, this function should not be + called unless using async DNS, so this is an error */ + return pbpal_connect_failed; #endif /* !defined PUBNUB_CALLBACK_API || defined PUBNUB_NTF_RUNTIME_SELECTION */ #ifdef PUBNUB_NTF_RUNTIME_SELECTION @@ -707,14 +916,17 @@ enum pbpal_resolv_n_connect_result pbpal_check_connect(pubnub_t* pb) PUBNUB_ASSERT_OPT(pb->state == PBS_WAIT_CONNECT); #if defined(_WIN32) - rslt = getsockopt( - pb->pal.socket, SOL_SOCKET, SO_ERROR, (char*)&error_code, (int*)&error_code_size); + rslt = getsockopt(pb->pal.socket, + SOL_SOCKET, + SO_ERROR, + (char*)&error_code, + (int*)&error_code_size); if (rslt == SOCKET_ERROR) { - PUBNUB_LOG_ERROR( - "Error: pbpal_check_connect(pb=%p)---> getsockopt() == SOCKET_ERROR\n" - " WSAGetLastError()=%d\n", - pb, - WSAGetLastError()); + PUBNUB_LOG_ERROR("Error: pbpal_check_connect(pb=%p)---> getsockopt() " + "== SOCKET_ERROR\n" + " WSAGetLastError()=%d\n", + pb, + WSAGetLastError()); return pbpal_connect_failed; } #else @@ -727,11 +939,11 @@ enum pbpal_resolv_n_connect_result pbpal_check_connect(pubnub_t* pb) } #endif /* defined(_WIN32) */ if (error_code != 0) { - PUBNUB_LOG_ERROR( - "Error: pbpal_check_connect(pb=%p)--> getsockopt(&error_code)--> " - "error_code=%d\n", - pb, - error_code); + PUBNUB_LOG_ERROR("Error: pbpal_check_connect(pb=%p)--> " + "getsockopt(&error_code)--> " + "error_code=%d\n", + pb, + error_code); #if PUBNUB_USE_MULTIPLE_ADDRESSES PUBNUB_LOG_ERROR( " time_since_the_last_dns_query = %ld seconds\n", @@ -761,40 +973,39 @@ enum pbpal_resolv_n_connect_result pbpal_check_connect(pubnub_t* pb) else if (rslt > 0) { PUBNUB_LOG_TRACE("pbpal_connected(): select() event\n"); #if defined(_WIN32) + // Complete TCP Keep-Alive configuration if connection established. pbpal_set_tcp_keepalive(pb); -#endif +#endif /* defined(_WIN32) */ return pbpal_connect_success; } PUBNUB_LOG_TRACE("pbpal_connected(): no select() events\n"); return pbpal_connect_wouldblock; } -void pbpal_set_tcp_keepalive(const pubnub_t *pb) +void pbpal_set_tcp_keepalive(const pubnub_t* pb) { - if (pb->pal.socket == SOCKET_INVALID) return; + if (pb->pal.socket == SOCKET_INVALID) + return; const pubnub_tcp_keepalive keepalive = pb->options.tcp_keepalive; - const pb_socket_t skt = pb->pal.socket; - -#if defined(_WIN32) - const BOOL enabled = pbccTrue == keepalive.enabled ? TRUE : FALSE; - (void)setsockopt(skt, SOL_SOCKET, SO_KEEPALIVE, (const char*)&enabled, sizeof(enabled)); -#endif + const pb_socket_t skt = pb->pal.socket; - if (pbccTrue != keepalive.enabled || - (0 == keepalive.time && 0 == keepalive.interval)) return; + if (pbccTrue != keepalive.enabled + || (0 == keepalive.time && 0 == keepalive.interval)) + return; #if defined(_WIN32) struct tcp_keepalive alive; - DWORD bytes = 0; - alive.onoff = 1; - alive.keepaliveinterval = (keepalive.interval > 0) ? (ULONG)keepalive.interval * 1000UL : 0; - alive.keepalivetime = (keepalive.time > 0) ? (ULONG)keepalive.time * 1000UL : 0; - (void)WSAIoctl(skt, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), - NULL, 0, &bytes, NULL, NULL); + DWORD bytes = 0; + alive.onoff = 1; + alive.keepaliveinterval = + (keepalive.interval > 0) ? (ULONG)keepalive.interval * 1000UL : 0; + alive.keepalivetime = (keepalive.time > 0) ? (ULONG)keepalive.time * 1000UL : 0; + (void)WSAIoctl( + skt, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), NULL, 0, &bytes, NULL, NULL); #else const int interval = keepalive.interval; - const int probes = keepalive.probes; - const int time = keepalive.time; + const int probes = keepalive.probes; + const int time = keepalive.time; if (time > 0) { #if defined(__APPLE__) @@ -805,7 +1016,8 @@ void pbpal_set_tcp_keepalive(const pubnub_t *pb) } if (interval > 0) - (void)setsockopt(skt, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); + (void)setsockopt( + skt, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); if (probes > 0) (void)setsockopt(skt, IPPROTO_TCP, TCP_KEEPCNT, &probes, sizeof(probes)); diff --git a/microchip_harmony/pbpal_resolv_and_connect_harmony_tcp.c b/microchip_harmony/pbpal_resolv_and_connect_harmony_tcp.c index 0ed5148b..5ef6433a 100644 --- a/microchip_harmony/pbpal_resolv_and_connect_harmony_tcp.c +++ b/microchip_harmony/pbpal_resolv_and_connect_harmony_tcp.c @@ -95,7 +95,7 @@ enum pbpal_resolv_n_connect_result pbpal_check_connect(pubnub_t *pb) #if PUBNUB_USE_SSL bool connected = !NET_PRES_SocketIsNegotiatingEncryption(pb->pal.socket) && NET_PRES_SocketIsSecure(pb->pal.socket); - return bool ? pbpal_connect_success : pbpal_connect_wouldblock; + return connected ? pbpal_connect_success : pbpal_connect_wouldblock; #else return TCPIP_TCP_IsConnected(pb->pal.socket) ? pbpal_connect_success : pbpal_connect_wouldblock; #endif @@ -105,7 +105,7 @@ enum pbpal_resolv_n_connect_result pbpal_check_connect(pubnub_t *pb) #if PUBNUB_CHANGE_DNS_SERVERS int pbpal_dns_rotate_server(pubnub_t *pb) { - return (pbp->flags.sent_queries < PUBNUB_MAX_DNS_QUERIES ? 0 : 1) + return (pb->flags.sent_queries < PUBNUB_MAX_DNS_QUERIES ? 0 : 1) } #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif /* defined(PUBNUB_CALLBACK_API) */ diff --git a/openssl/pbpal_connect_openssl.c b/openssl/pbpal_connect_openssl.c index 931d861d..82b6c778 100644 --- a/openssl/pbpal_connect_openssl.c +++ b/openssl/pbpal_connect_openssl.c @@ -121,8 +121,8 @@ static char pubnub_cert_Starfield[] = #if defined(PUBNUB_USE_LETS_ENCRYPT_CERTIFICATE) || defined(_WIN32) /* ISRG Root X1 (Let's Encrypt) - Used for testing and validation purposes with badssl.com and other Let's Encrypt domains. - Note: This is NOT used by PubNub production infrastructure. + Used for testing and validation purposes with badssl.com and other Let's + Encrypt domains. Note: This is NOT used by PubNub production infrastructure. Subject: C=US, O=Internet Security Research Group, CN=ISRG Root X1 Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1 @@ -204,7 +204,7 @@ static int add_pubnub_cert(SSL_CTX* sslCtx) 3. ISRG Root X1 - For testing with Let's Encrypt domains (badssl.com, etc.) */ int rslt = add_pem_cert(sslCtx, pubnub_cert_Amazon_Root_CA_1); - rslt = rslt || add_pem_cert(sslCtx, pubnub_cert_Starfield); + rslt = rslt || add_pem_cert(sslCtx, pubnub_cert_Starfield); #if defined(PUBNUB_USE_LETS_ENCRYPT_CERTIFICATE) || defined(_WIN32) rslt = rslt || add_pem_cert(sslCtx, pubnub_cert_ISRG_Root_X1); #endif @@ -277,27 +277,27 @@ enum pbpal_tls_result pbpal_start_tls(pubnub_t* pb) /** Enable SNI (Server Name Indication) for virtual hosting support. */ if (!SSL_set_tlsext_host_name(ssl, pb->origin)) { - PUBNUB_LOG_WARNING( - "pb=%p: SSL_set_tlsext_host_name() failed to set SNI hostname '%s'\n", - pb, - pb->origin); + PUBNUB_LOG_WARNING("pb=%p: SSL_set_tlsext_host_name() failed to set " + "SNI hostname '%s'\n", + pb, + pb->origin); ERR_print_errors_cb(print_to_pubnub_log, pb); return pbtlsFailed; } /** Enable hostname verification. */ - X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + X509_VERIFY_PARAM* param = SSL_get0_param(ssl); if (param != NULL) { char const* hostname = pb->origin; if (!X509_VERIFY_PARAM_set1_host(param, hostname, 0)) { - PUBNUB_LOG_WARNING( - "pb=%p: X509_VERIFY_PARAM_set1_host() failed to set hostname '%s' for verification\n", - pb, - hostname); + PUBNUB_LOG_WARNING("pb=%p: X509_VERIFY_PARAM_set1_host() failed to " + "set hostname '%s' for verification\n", + pb, + hostname); return pbtlsFailed; } - PUBNUB_LOG_DEBUG("pb=%p: Hostname verification configured for '%s'\n", - pb, hostname); + PUBNUB_LOG_TRACE( + "pb=%p: Hostname verification configured for '%s'\n", pb, hostname); } PUBNUB_LOG_TRACE("pb=%p: Got SSL\n", pb); SSL_set_fd(ssl, pb->pal.socket); @@ -318,8 +318,8 @@ enum pbpal_tls_result pbpal_start_tls(pubnub_t* pb) */ enum pbpal_tls_result pbpal_check_tls(pubnub_t* pb) { - SSL* ssl; - int rslt; + SSL* ssl; + int rslt; X509* cert; PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); @@ -331,19 +331,23 @@ enum pbpal_tls_result pbpal_check_tls(pubnub_t* pb) bool needRead = false, needWrite = false; rslt = SSL_connect(ssl); - rslt = pbpal_handle_socket_condition(rslt, pb, __FILE__, __LINE__, &needRead, &needWrite); + rslt = pbpal_handle_socket_condition( + rslt, pb, __FILE__, __LINE__, &needRead, &needWrite); if (PNR_OK != rslt) { - if (rslt != PNR_IN_PROGRESS) return pbtlsFailed; - return needRead ? pbtlsStartedWaitRead : needWrite ? pbtlsStartedWaitWrite : pbtlsStarted; + if (rslt != PNR_IN_PROGRESS) + return pbtlsFailed; + return needRead ? pbtlsStartedWaitRead + : needWrite ? pbtlsStartedWaitWrite + : pbtlsStarted; } PUBNUB_LOG_TRACE("pb=%p: SSL connected\n", pb); socket_set_rcv_timeout(pb->pal.socket, pb->transaction_timeout_ms); - #if OPENSSL_VERSION_NUMBER < 0x30000000L +#if OPENSSL_VERSION_NUMBER < 0x30000000L cert = SSL_get_peer_certificate(ssl); - #else +#else cert = SSL_get1_peer_certificate(ssl); - #endif +#endif if (cert != NULL) { rslt = SSL_get_verify_result(ssl); X509_free(cert); diff --git a/openssl/pubnub_config.h b/openssl/pubnub_config.h index 924911d8..12232b1c 100644 --- a/openssl/pubnub_config.h +++ b/openssl/pubnub_config.h @@ -1,6 +1,6 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #if !defined INC_PUBNUB_CONFIG -#define INC_PUBNUB_CONFIG +#define INC_PUBNUB_CONFIG /* -- Next few definitions can be tweaked by the user, but with care -- */ @@ -62,7 +62,7 @@ /** This is the URL of the Pubnub server. Change only for testing purposes. */ -#define PUBNUB_ORIGIN "pubsub.pubnub.com" +#define PUBNUB_ORIGIN "pubsub.pubnub.com" /** Set to 0 to disable changing the origin from the default #PUBNUB_ORIGIN. Set to anything != 0 to enable changing the @@ -71,15 +71,15 @@ #define PUBNUB_ORIGIN_SETTABLE 1 /** Duration of the transaction timeout set during context initialization, - in milliseconds. Timeout dration in the context can be changed by the + in milliseconds. Timeout dration in the context can be changed by the user after initialization. */ -#define PUBNUB_DEFAULT_TRANSACTION_TIMER 310000 +#define PUBNUB_DEFAULT_TRANSACTION_TIMER 310000 /** Duration of the 'wait_connect_TCP_socket' timeout set during context initialization, in milliseconds. Can be changed later by the user. */ -#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 +#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 /** Mininmal duration of the 'wait_connect_TCP_socket' timer, in milliseconds. * You can't set less than this. @@ -96,13 +96,16 @@ #define PUBNUB_PROXY_API 1 #endif +#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" +#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" + #if defined(PUBNUB_CALLBACK_API) -/** The size of the stack (in kilobytes) for the "polling" thread, when using - the callback interface. We don't need much, so, if you want to conserve - memory, you can try small values. It's hard to say what is the minumum, - as it depends on the OS functions we call, but, you probably +/** The size of the stack (in kilobytes) for the "polling" thread, when using + the callback interface. We don't need much, so, if you want to conserve + memory, you can try small values. It's hard to say what is the minumum, + as it depends on the OS functions we call, but, you probably shouldn't try less than 64 KB. - + Set to `0` to use the default stack size. */ #define PUBNUB_CALLBACK_THREAD_STACK_SIZE_KB 0 @@ -134,13 +137,16 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif -#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" - /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS +#if PUBNUB_USE_IPV6 +/** Maximum number of DNS servers list rotation in a single transaction */ +#define PUBNUB_MAX_DNS_ROTATION 5 +#else /* PUBNUB_USE_IPV6 */ /** Maximum number of DNS servers list rotation in a single transaction */ #define PUBNUB_MAX_DNS_ROTATION 3 +#endif /* !PUBNUB_USE_IPV6 */ #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif /* defined(PUBNUB_CALLBACK_API) */ @@ -205,9 +211,9 @@ #if !defined(PUBNUB_USE_OBJECTS_API) /** If true (!=0) will enable using the objects API, which is a - collection of rest API features that enables "CRUD"(Create, Read, Update and Delete) - on two new pubnub objects: User and Space, as well as manipulating connections - between them. */ + collection of rest API features that enables "CRUD"(Create, Read, Update and + Delete) on two new pubnub objects: User and Space, as well as manipulating + connections between them. */ #define PUBNUB_USE_OBJECTS_API 1 #endif diff --git a/posix/pubnub_config.h b/posix/pubnub_config.h index 74bb94bb..ff0c5ae0 100644 --- a/posix/pubnub_config.h +++ b/posix/pubnub_config.h @@ -1,6 +1,6 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #if !defined INC_PUBNUB_CONFIG -#define INC_PUBNUB_CONFIG +#define INC_PUBNUB_CONFIG /* -- Next few definitions can be tweaked by the user, but with care -- */ @@ -58,7 +58,7 @@ /** This is the URL of the Pubnub server. Change only for testing purposes. */ -#define PUBNUB_ORIGIN "ps.pndsn.com" +#define PUBNUB_ORIGIN "ps.pndsn.com" /** Set to 0 to disable changing the origin from the default #PUBNUB_ORIGIN. Set to anything != 0 to enable changing the @@ -69,12 +69,12 @@ /** Duration of the transaction timeout set during context initialization, in milliseconds. Can be changed later by the user. */ -#define PUBNUB_DEFAULT_TRANSACTION_TIMER 310000 +#define PUBNUB_DEFAULT_TRANSACTION_TIMER 310000 /** Duration of the 'wait_connect_TCP_socket' timeout set during context initialization, in milliseconds. Can be changed later by the user. */ -#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 +#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 /** Mininmal duration of the 'wait_connect_TCP_socket' timer, in milliseconds. * You can't set less than this. @@ -89,16 +89,19 @@ #endif #if defined(PUBNUB_CALLBACK_API) -/** The size of the stack (in kilobytes) for the "polling" thread, when using - the callback interface. We don't need much, so, if you want to conserve - memory, you can try small values. It's hard to say what is the minumum, - as it depends on the OS functions we call, but, you probably +/** The size of the stack (in kilobytes) for the "polling" thread, when using + the callback interface. We don't need much, so, if you want to conserve + memory, you can try small values. It's hard to say what is the minumum, + as it depends on the OS functions we call, but, you probably shouldn't try less than 64 KB. - + Set to `0` to use the default stack size. */ #define PUBNUB_CALLBACK_THREAD_STACK_SIZE_KB 0 +#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" +#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" + #if !defined(PUBNUB_USE_IPV6) /** If true (!=0), enable support for Ipv6 network addresses */ #define PUBNUB_USE_IPV6 1 @@ -126,13 +129,16 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif -#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" - /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS +#if PUBNUB_USE_IPV6 +/** Maximum number of DNS servers list rotation in a single transaction */ +#define PUBNUB_MAX_DNS_ROTATION 5 +#else /* PUBNUB_USE_IPV6 */ /** Maximum number of DNS servers list rotation in a single transaction */ #define PUBNUB_MAX_DNS_ROTATION 3 +#endif /* !PUBNUB_USE_IPV6 */ #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif /* defined(PUBNUB_CALLBACK_API) */ @@ -196,9 +202,9 @@ #if !defined(PUBNUB_USE_OBJECTS_API) /** If true (!=0) will enable using the objects API, which is a - collection of Rest API features that enables "CRUD"(Create, Read, Update and Delete) - on two new pubnub objects: User and Space, as well as manipulating connections - between them. */ + collection of Rest API features that enables "CRUD"(Create, Read, Update and + Delete) on two new pubnub objects: User and Space, as well as manipulating + connections between them. */ #define PUBNUB_USE_OBJECTS_API 1 #endif diff --git a/posix/pubnub_dns_system_servers.c b/posix/pubnub_dns_system_servers.c index b9689429..b7b81067 100644 --- a/posix/pubnub_dns_system_servers.c +++ b/posix/pubnub_dns_system_servers.c @@ -3,17 +3,21 @@ #include "core/pubnub_log.h" #include "core/pubnub_dns_servers.h" #include "lib/pubnub_parse_ipv4_addr.h" +#if PUBNUB_USE_IPV6 +#include "lib/pubnub_parse_ipv6_addr.h" +#endif #include #include #include +#include int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size_t n) { - FILE* fp; - char buffer[255]; - unsigned i = 0; - bool found = false; + FILE* fp; + char buffer[255]; + unsigned i = 0; + bool found = false; PUBNUB_ASSERT_OPT(n > 0); PUBNUB_ASSERT_OPT(o_ipv4 != NULL); @@ -54,3 +58,84 @@ int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size return i; } + +#if PUBNUB_USE_IPV6 +int pubnub_dns_read_system_servers_ipv6(struct pubnub_ipv6_address* o_ipv6, size_t n) +{ + FILE* fp; + char buffer[255]; + unsigned dns_count = 0; + bool has_ipv4_addresses = false; + + PUBNUB_ASSERT_OPT(n > 0); + PUBNUB_ASSERT_OPT(o_ipv6 != NULL); + + fp = fopen("/etc/resolv.conf", "r"); + if (NULL == fp) { + PUBNUB_LOG_ERROR( + "Can't open file:'/etc/resolv.conf' for reading!-errno:%d\n", errno); + return -1; + } + while ((dns_count < n) && !feof(fp)) { + /* Reads new line */ + fgets(buffer, sizeof buffer, fp); + if (strncmp(buffer, "nameserver", 10) == 0) { + bool is_ipv4_address = false; + uint8_t* dns_bytes = NULL; + char* addr_start = buffer + 10; + while (*addr_start == ' ' || *addr_start == '\t') { + addr_start++; + } + + /* Try parsing as IPv6 first */ + if (pubnub_parse_ipv6_addr(addr_start, &o_ipv6[dns_count]) == 0) { + dns_bytes = o_ipv6[dns_count].ipv6; + ++dns_count; + } + else { + struct pubnub_ipv4_address ipv4_addr; + if (pubnub_parse_ipv4_addr(addr_start, &ipv4_addr) == 0) { + has_ipv4_addresses = true; + is_ipv4_address = true; + memset(o_ipv6[dns_count].ipv6, 0, 10); + o_ipv6[dns_count].ipv6[10] = 0xff; + o_ipv6[dns_count].ipv6[11] = 0xff; + memcpy(&o_ipv6[dns_count].ipv6[12], ipv4_addr.ipv4, 4); + dns_bytes = o_ipv6[dns_count].ipv6; + ++dns_count; + } + else { + PUBNUB_LOG_WARNING("pubnub_dns_read_system_servers_ipv6(): " + "Invalid nameserver address '%s' in " + "/etc/resolv.conf, skipping.\n", + addr_start); + } + } + + if (NULL != dns_bytes) { + char str[INET6_ADDRSTRLEN]; + PUBNUB_LOG_TRACE( + "Added %s DNS (%s).\n", + is_ipv4_address ? "IPv4-mapped IPv6" : "IPv6", + inet_ntop(AF_INET6, dns_bytes, str, INET6_ADDRSTRLEN)); + } + } + } + if (fclose(fp) != 0) { + PUBNUB_LOG_ERROR("Error closing file: '/etc/resolv.conf'! errno:%d\n", + errno); + return -1; + } + if (0 == dns_count) { + PUBNUB_LOG_WARNING( + "Couldn't find any DNS servers in file:'/etc/resolv.conf'!"); + return 0; + } + + PUBNUB_LOG_DEBUG("Discovered %u %s DNS server(s)\n", + dns_count, + has_ipv4_addresses ? "IPv4-mapped IPv6/IPv6" : "IPv6"); + + return dns_count; +} +#endif /* PUBNUB_USE_IPV6 */ diff --git a/windows/pubnub_config.h b/windows/pubnub_config.h index ea4f6a67..928897dd 100644 --- a/windows/pubnub_config.h +++ b/windows/pubnub_config.h @@ -58,7 +58,7 @@ /** This is the URL of the Pubnub server. Change only for testing purposes. */ -#define PUBNUB_ORIGIN "pubsub.pubnub.com" +#define PUBNUB_ORIGIN "ps.pndsn.com" /** Set to 0 to disable changing the origin from the default #PUBNUB_ORIGIN. Set to anything != 0 to enable changing the @@ -75,7 +75,7 @@ /** Duration of the 'wait_connect_TCP_socket' timeout set during context initialization, in milliseconds. Can be changed later by the user. */ -#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 +#define PUBNUB_DEFAULT_WAIT_CONNECT_TIMER 10000 /** Mininmal duration of the 'wait_connect_TCP_socket' timer, in milliseconds. * You can't set less than this. @@ -89,6 +89,9 @@ #define PUBNUB_PROXY_API 1 #endif +#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" +#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" + #if defined(PUBNUB_CALLBACK_API) /** The size of the stack (in kilobytes) for the "polling" thread, when using the callback interface. We don't need much, so, if you want to conserve @@ -125,13 +128,16 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif -#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" - /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS +#if PUBNUB_USE_IPV6 +/** Maximum number of DNS servers list rotation in a single transaction */ +#define PUBNUB_MAX_DNS_ROTATION 5 +#else /* PUBNUB_USE_IPV6 */ /** Maximum number of DNS servers list rotation in a single transaction */ #define PUBNUB_MAX_DNS_ROTATION 3 +#endif /* !PUBNUB_USE_IPV6 */ #endif /* PUBNUB_CHANGE_DNS_SERVERS */ #endif /* defined(PUBNUB_CALLBACK_API) */ @@ -148,7 +154,7 @@ /** If true (!=0) will use Windows SSPI (for NTLM and such). Otherwise, will use own implementation, if available. */ -#ifndef PUBNUB_USE_WIN_SSPI +#ifndef PUBNUB_USE_WIN_SSPI #define PUBNUB_USE_WIN_SSPI 1 #endif @@ -158,7 +164,7 @@ #define PUBNUB_MAX_PROXY_HOSTNAME_LENGTH 63 /** If true (!=0), enable support for message encryption/decryption */ -#ifndef PUBNUB_CRYPTO_API +#ifndef PUBNUB_CRYPTO_API #define PUBNUB_CRYPTO_API 0 #endif @@ -200,9 +206,9 @@ #define PUBNUB_USE_FETCH_HISTORY 1 /** If true (!=0) will enable using the objects API, which is a - collection of rest API features that enables "CRUD"(Create, Read, Update and Delete) - on two new pubnub objects: User and Space, as well as manipulating connections - between them. */ + collection of rest API features that enables "CRUD"(Create, Read, Update and + Delete) on two new pubnub objects: User and Space, as well as manipulating + connections between them. */ #define PUBNUB_USE_OBJECTS_API 1 /** If true (!=0) will enable using the Actions API, which is a collection diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index 10187688..601d48ea 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -1,14 +1,26 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#include "pubnub_internal.h" +/** + * @file pubnub_dns_system_servers.c + * @brief Windows DNS server discovery using GetAdaptersAddresses. + * + * This implementation is designed for Windows 8+ and provides: + * - Thread-safe DNS server discovery + * - Proper handling of stale VPN connections via DNS validation + * - IPv4 and IPv6 support with GetBestRoute2 + * - Adapter prioritization based on routing metrics + * + * Minimum Windows version: Windows 8 (NT 6.2) + */ +#include "pubnub_internal.h" #include "core/pubnub_dns_servers.h" #include "core/pubnub_assert.h" #include "core/pubnub_log.h" #include +#include #include #include -#include #include #include @@ -18,454 +30,704 @@ #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) -/* Maximum retry attempts for GetAdaptersAddresses buffer allocation */ +/** Maximum retry attempts for GetAdaptersAddresses */ #define MAX_ADAPTER_ADDRESS_RETRIES 3 +/** Maximum number of DNS servers per adapter */ +#define MAX_DNS_SERVERS_PER_ADAPTER 8 -/* Public DNS servers to use for GetBestInterface routing test */ -#define PUBLIC_DNS_TEST_IP_1 "8.8.8.8" /* Google Public DNS */ -#define PUBLIC_DNS_TEST_IP_2 "1.1.1.1" /* Cloudflare DNS */ - -/* Timeout for DNS server connectivity check (milliseconds) */ -#define DNS_CONNECTIVITY_TIMEOUT_MS 2000 - -/** Helper structure to track adapters with their metrics for sorting */ -struct adapter_with_metric { - IP_ADAPTER_ADDRESSES* adapter; - ULONG metric; - bool is_best_route; +/** DNS server validation timeout in milliseconds. + * DNS servers will be validated only if timeout is larget than @b 0. + */ +#ifndef PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT +#define PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT 0 +#endif /* PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT */ + +/** Adapter info for sorting with verified DNS servers. */ +struct adapter_info { + IP_ADAPTER_ADDRESSES* adapter; + ULONG metric; + bool is_best_route; + size_t dns_count; + struct sockaddr_storage dns_servers[MAX_DNS_SERVERS_PER_ADAPTER]; }; -/** Check whether provided address is valid (not loopback, not APIPA, not zero). +/** Check if @c IPv4 address is valid (not loopback, APIPA, or zero). * - * @param host_addr Address in host byte order. - * @return @c true if proper non-internal address has been provided, - * @c false otherwise. + * @param addr IPv4 address to check (4 bytes, network order). + * @return @c true if valid, @c false otherwise. */ -static bool pubnub_ipv4_address_valid(DWORD host_addr) +static bool is_valid_ipv4(const uint8_t addr[4]) { - return !(host_addr == 0 || ((host_addr >> 24) & 0xFF) == 127 || - (((host_addr >> 24) & 0xFF) == 169 && ((host_addr >> 16) & 0xFF) == 254)); + if (addr[0] == 0 || addr[0] == 127 || (addr[0] == 169 && addr[1] == 254)) + return false; + + return true; } -/** Comparison function for qsort to sort adapters by priority. - * Priority order: - * 1. Adapters on the best route (lowest priority number) - * 2. Lower interface metric +#if PUBNUB_USE_IPV6 +/** Check if @c IPv6 address is valid (not loopback, or zero). * - * @param a Left-hand @c adapter_with_metric which will be used in comparison. - * @param b Right-hand @c adapter_with_metric which will be used in comparison. - * @return @b -1 when the left-hand adapter has higher priority than the - * right-hand; @b 0 when both adapters have the same priority; @b 1 when the - * right-hand adapter has higher priority than the left-hand. + * @param addr IPv6 address to check (16 bytes, network order). + * @return @c true if valid, @c false otherwise. */ -static int compare_adapters_by_priority(const void* a, const void* b) +static bool is_valid_ipv6(const uint8_t addr[16]) { - const struct adapter_with_metric* aa = (const struct adapter_with_metric*)a; - const struct adapter_with_metric* bb = (const struct adapter_with_metric*)b; + bool loopback = addr[15] == 1; + bool all_zero = true; + + for (int i = 0; i < 16 && all_zero; i++) { + if (addr[i] != 0) + all_zero = false; + } - /* Best route adapters come first */ - if (aa->is_best_route && !bb->is_best_route) return -1; - if (!aa->is_best_route && bb->is_best_route) return 1; + for (int i = 0; i < 15 && loopback; i++) { + if (addr[i] != 0) + loopback = false; + } - /* Then sort by metric (lower is better) */ - if (aa->metric < bb->metric) return -1; - if (aa->metric > bb->metric) return 1; + if (all_zero || loopback || (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80)) + return false; - return 0; + return true; } +#endif /* PUBNUB_USE_IPV6 */ -/** Test if a DNS server is reachable by attempting a TCP connection to port 53. - * This is used to detect stale VPN connections where the adapter appears UP - * but the DNS server is actually unreachable. +/** Check address is valid (not loopback, APIPA, or zero) for specified family. * - * @param dns_addr_net DNS server address in network byte order. - * @return @c true if DNS server is reachable, @c false otherwise. + * @param family @c AF_INET or @c AF_INET6 family. + * @param addr Address to check (4 bytes for @c IPv4, 16 bytes for @c IPv6, + * network order). + * @return @c true if valid, @c false otherwise. */ -static bool pubnub_dns_server_reachable(DWORD dns_addr_net) +static bool is_valid_address(ULONG family, const uint8_t* addr) { - SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock == INVALID_SOCKET) return false; + if (family == AF_INET) + return is_valid_ipv4(addr); +#if PUBNUB_USE_IPV6 + if (family == AF_INET6) + return is_valid_ipv6(addr); +#endif /* PUBNUB_USE_IPV6 */ + return false; +} - /* Set socket to non-blocking mode for timeout control */ - u_long mode = 1; - if (ioctlsocket(sock, FIONBIO, &mode) != 0) { - closesocket(sock); +/** Check if adapter is suitable for DNS queries (unified for IPv4/IPv6). + * + * @param family Address family (@c AF_INET or @c AF_INET6). + * @param aa Adapter to check. + * @return @c true if suitable. + */ +static bool is_adapter_suitable(ULONG family, IP_ADAPTER_ADDRESSES* aa) +{ + if (!aa || aa->OperStatus != IfOperStatusUp + || aa->IfType == IF_TYPE_SOFTWARE_LOOPBACK) return false; - } - - struct sockaddr_in dns_addr; - memset(&dns_addr, 0, sizeof(dns_addr)); - dns_addr.sin_family = AF_INET; - dns_addr.sin_port = htons(53); - dns_addr.sin_addr.s_addr = dns_addr_net; - - int result = connect(sock, (struct sockaddr*)&dns_addr, sizeof(dns_addr)); - - if (result == SOCKET_ERROR) { - if (WSAGetLastError() != WSAEWOULDBLOCK) { - closesocket(sock); - return false; - } - - /* Wait for socket's in-progress connection complete with timeout. */ - struct timeval timeout; - timeout.tv_sec = DNS_CONNECTIVITY_TIMEOUT_MS / 1000; - timeout.tv_usec = (DNS_CONNECTIVITY_TIMEOUT_MS % 1000) * 1000; - - fd_set writefds, exceptfds; - FD_ZERO(&writefds); - FD_ZERO(&exceptfds); - FD_SET(sock, &writefds); - FD_SET(sock, &exceptfds); - result = select(0, NULL, &writefds, &exceptfds, &timeout); + if (AF_INET == family && !(aa->Flags & IP_ADAPTER_IPV4_ENABLED)) + return false; - if (result == SOCKET_ERROR || result == 0 || - FD_ISSET(sock, &exceptfds) || !FD_ISSET(sock, &writefds)) { - closesocket(sock); - return false; +#if PUBNUB_USE_IPV6 + if (AF_INET6 == family && !(aa->Flags & IP_ADAPTER_IPV6_ENABLED)) + return false; +#endif /* PUBNUB_USE_IPV6 */ + + /* For IPv4: check if it has valid gateway */ + if (AF_INET == family) { + for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; + gw = gw->Next) { + if (gw->Address.lpSockaddr + && gw->Address.lpSockaddr->sa_family == family) { + struct sockaddr_in* sin = + (struct sockaddr_in*)gw->Address.lpSockaddr; + const uint8_t* bytes = (uint8_t*)&sin->sin_addr.s_addr; + if (is_valid_address(AF_INET, bytes)) + return true; + } } - /* Ensure that connect didn't end up with an error. */ - int so_error = 0; - int len = sizeof(so_error); - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&so_error, &len) != 0 || - so_error != 0) { - closesocket(sock); + /* Accept physical Ethernet or Wi-Fi adapters with valid unicast address. + * This handles WSL2, Hyper-V, Docker scenarios where gateway info may + * be missing but the adapter is still functional for local DNS. + * + * Skip adapter in other case without unicast address verification. + */ + if (aa->IfType != IF_TYPE_ETHERNET_CSMACD + && aa->IfType != IF_TYPE_IEEE80211 && aa->IfType != 71) return false; - } } - closesocket(sock); - return true; -} - -/** Copy IPv4 address from network byte order DWORD to array, checking for - * duplicates. - * - * @param array Pointer to the array of @c pubnub_ipv4_address structures with - * previously discovered DNS server addresses. - * @param count Current number of entries in @b array. - * @param n_addr DNS server address in network byte order. - * @param out Target entry in @b array where @b n_addr should be copied (if not - * present yet in @b array). - * @return @c true if the provided address in network byte order was acceptable - * and copied to the list of discovered DNS server addresses, @c false otherwise. - */ -static bool pubnub_copy_ipv4_bytes_from_be_dword( - const struct pubnub_ipv4_address* array, - const size_t count, - DWORD n_addr, - unsigned char out[4]) -{ - DWORD host_addr = ntohl(n_addr); - unsigned char temp_ip[4]; - - temp_ip[0] = (unsigned char)((host_addr >> 24) & 0xFF); - temp_ip[1] = (unsigned char)((host_addr >> 16) & 0xFF); - temp_ip[2] = (unsigned char)((host_addr >> 8) & 0xFF); - temp_ip[3] = (unsigned char)(host_addr & 0xFF); + /* Must have at least one valid unicast address */ + /* For IPv4: required if no gateway, for IPv6: always required */ + for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; + ua = ua->Next) { + if (!ua->Address.lpSockaddr || ua->Address.lpSockaddr->sa_family != family) + continue; - /* Filter out invalid addresses */ - if (!pubnub_ipv4_address_valid(host_addr)) return false; + uint8_t* bytes = NULL; + if (AF_INET == family) { + struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; + bytes = (uint8_t*)&sin->sin_addr.s_addr; + } +#if PUBNUB_USE_IPV6 + else if (AF_INET6 == family) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ua->Address.lpSockaddr; + bytes = (uint8_t*)&sin6->sin6_addr; + } +#endif /* PUBNUB_USE_IPV6 */ - /* Check for duplicates */ - for (size_t i = 0; i < count; i++) { - if (memcmp(array[i].ipv4, temp_ip, 4) == 0) return false; + if (is_valid_address(family, bytes)) + return true; } - out[0] = temp_ip[0]; - out[1] = temp_ip[1]; - out[2] = temp_ip[2]; - out[3] = temp_ip[3]; - - return true; + return false; } -/** Get adapter addresses with retry logic to handle race conditions. +/** Retrieve adapter addresses for specified address family. * - * @param aa Pointer to the list of @c IP_ADAPTER_ADDRESSES structs. - * @return @c true if @p aa has been populated with active adapter, - * @c false otherwise. + * @param family Address family (@c AF_INET or @c AF_INET6). + * @return Pointer to adapter addresses on success, @c NULL on error. + * Caller must @c FREE the returned pointer. */ -static bool pubnub_adapter_addresses_get(IP_ADAPTER_ADDRESSES** aa) +static IP_ADAPTER_ADDRESSES* get_adapter_addresses(ULONG family) { - if (NULL == aa) return false; - - /* Free existing allocation if present */ - if (*aa) { - FREE(*aa); - *aa = NULL; - } - - ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS; - ULONG buflen = 15000; /* Start with reasonable size to avoid extra call */ + IP_ADAPTER_ADDRESSES* addrs = NULL; + ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST + | GAA_FLAG_INCLUDE_GATEWAYS; + ULONG buflen = 15000; + int retries = 0; DWORD ret; - int retries = 0; - /* Retry loop to handle race conditions where adapters change between calls */ do { - IP_ADAPTER_ADDRESSES* buffer = (IP_ADAPTER_ADDRESSES*)MALLOC(buflen); - if (NULL == buffer) { - PUBNUB_LOG_ERROR("OOM allocating %lu bytes for GetAdaptersAddresses\n", - (unsigned long)buflen); - return false; + addrs = (IP_ADAPTER_ADDRESSES*)MALLOC(buflen); + if (!addrs) { + PUBNUB_LOG_ERROR( + "Failed to allocate %lu bytes for adapter info (family=%lu)\n", + (unsigned long)buflen, + family); + return NULL; } - ret = GetAdaptersAddresses(AF_INET, flags, NULL, buffer, &buflen); - - if (ret == NO_ERROR) { - *aa = buffer; - return true; - } + ret = GetAdaptersAddresses(family, flags, NULL, addrs, &buflen); - FREE(buffer); + if (ret == NO_ERROR) + break; - if (ret == ERROR_BUFFER_OVERFLOW) { - if (++retries >= MAX_ADAPTER_ADDRESS_RETRIES) { - PUBNUB_LOG_ERROR("GetAdaptersAddresses buffer overflow after " - "%d retries\n", retries); - return false; - } - /* `GetAdaptersAddresses` call failed but updated `buflen` value. */ - continue; + FREE(addrs); + addrs = NULL; + + if (ret != ERROR_BUFFER_OVERFLOW + || ++retries >= MAX_ADAPTER_ADDRESS_RETRIES) { + PUBNUB_LOG_ERROR("GetAdaptersAddresses(family=%lu) failed: %lu\n", + (unsigned long)family, + (unsigned long)ret); + return NULL; } + } while (ret == ERROR_BUFFER_OVERFLOW); - PUBNUB_LOG_ERROR("GetAdaptersAddresses failed: %lu\n", (unsigned long)ret); - return false; - - } while (ret == ERROR_BUFFER_OVERFLOW && retries < MAX_ADAPTER_ADDRESS_RETRIES); - - return false; + return addrs; } -/** Check if an adapter is suitable for DNS queries. - * An adapter is suitable if it is: - * - Operationally UP, AND - * - Not loopback or tunnel, AND - * - IPv4 enabled, AND - * - Has a valid gateway OR physical adapters with valid unicast address. + +#if PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT +/** Validate DNS server by sending a UDP probe packet. * - * @param aa Pointer to the adapter structure which should be checked. - * @return @c true if provided adapter fulfill requirements ("alive"), - * @c false otherwise. + * @param addr_family Address family (@c AF_INET or @c AF_INET6). + * @param dns_addr DNS server address (4 bytes for @c IPv4, 16 bytes for + * @c IPv6, network order). + * @param if_index Interface index to bind query to (0 for any). + * @return @c true if DNS server responds successfully. */ -static bool pubnub_adapter_is_suitable(IP_ADAPTER_ADDRESSES* aa) +static bool validate_dns_server_udp(int addr_family, + const uint8_t dns_addr[], + DWORD if_index) { - if (NULL == aa) return false; + /* Minimal DNS query packet */ + const uint8_t query[12] = { 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + SOCKET sock = socket(addr_family, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) { + PUBNUB_LOG_ERROR("Failed to create validation socket: %d\n", + WSAGetLastError()); + return false; + } - if (aa->OperStatus != IfOperStatusUp || - aa->IfType == IF_TYPE_SOFTWARE_LOOPBACK || - aa->IfType == IF_TYPE_TUNNEL || - !(aa->Flags & IP_ADAPTER_IPV4_ENABLED)) { + /* Set receive timeout */ + DWORD timeout_ms = PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_ms, sizeof(timeout_ms)) + != 0) { + PUBNUB_LOG_WARNING("Failed to set socket timeout: %d\n", WSAGetLastError()); + } + + /* Prepare DNS server address */ + struct sockaddr_storage dns_server; + ZeroMemory(&dns_server, sizeof(dns_server)); + int addr_len; + if (addr_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&dns_server; + sin->sin_family = AF_INET; + sin->sin_port = htons(53); + memcpy(&sin->sin_addr.s_addr, dns_addr, 4); + addr_len = sizeof(struct sockaddr_in); + PUBNUB_LOG_TRACE("Validating DNS server: %u.%u.%u.%u\n", + dns_addr[0], + dns_addr[1], + dns_addr[2], + dns_addr[3]); + } +#if PUBNUB_USE_IPV6 + else if (addr_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&dns_server; + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(53); + memcpy(sin6->sin6_addr.s6_addr, dns_addr, 16); + addr_len = sizeof(struct sockaddr_in6); + PUBNUB_LOG_TRACE( + "Validating DNS server: %02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x:%02x%02x\n", + dns_addr[0], + dns_addr[1], + dns_addr[2], + dns_addr[3], + dns_addr[4], + dns_addr[5], + dns_addr[6], + dns_addr[7], + dns_addr[8], + dns_addr[9], + dns_addr[10], + dns_addr[11], + dns_addr[12], + dns_addr[13], + dns_addr[14], + dns_addr[15]); + } +#endif + else { + PUBNUB_LOG_ERROR("Invalid address family: %d\n", addr_family); + socket_close(sock); return false; } - /* Check for valid gateway. */ - for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { - if (gw->Address.lpSockaddr && gw->Address.lpSockaddr->sa_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)gw->Address.lpSockaddr; - if (pubnub_ipv4_address_valid(ntohl(sin->sin_addr.S_un.S_addr))) - return true; - } + /* Send minimal DNS query to probe server */ + int sent = sendto( + sock, (char*)query, sizeof(query), 0, (struct sockaddr*)&dns_server, addr_len); + + if (sent <= 0) { + PUBNUB_LOG_DEBUG("DNS validation sendto() failed: %d\n", WSAGetLastError()); + socket_close(sock); + return false; } - /* Fallback: Accept physical Ethernet or WiFi adapters with valid unicast address - * This handles WSL2, Hyper-V, Docker scenarios where gateway info may be missing - * but the adapter is still functional for local DNS. - */ - if (aa->IfType == IF_TYPE_ETHERNET_CSMACD || - aa->IfType == IF_TYPE_IEEE80211 || - aa->IfType == 71) { - - for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { - if (ua->Address.lpSockaddr && ua->Address.lpSockaddr->sa_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; - if (pubnub_ipv4_address_valid(ntohl(sin->sin_addr.S_un.S_addr))) - return true; - } - } + /* Try to receive any response (even error response means server is alive) */ + uint8_t response[512]; + int recvd = recvfrom(sock, (char*)response, sizeof(response), 0, NULL, NULL); + + if (recvd > 0) { + PUBNUB_LOG_TRACE("DNS server responded (%d bytes) - validation OK\n", recvd); + socket_close(sock); + return true; + } + + int err = WSAGetLastError(); + if (err == WSAETIMEDOUT) { + PUBNUB_LOG_DEBUG("DNS validation timeout - server unreachable\n"); + } + else { + PUBNUB_LOG_DEBUG("DNS validation recvfrom() failed: %d\n", err); } + socket_close(sock); return false; } +#endif /* PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT */ -/** Get the best interface for reaching the public Internet (for DNS queries). - * Detect the index of the adapter that is used by Windows for request routing. +/** Get best interface index for public Internet routing using GetBestRoute2. * - * @return Non-zero index of the adapter that can route to the public Internet, - * zero otherwise. + * @param family Address family (@c AF_INET or @c AF_INET6). + * @return Interface index, or @c 0 if index cannot determine. */ -static DWORD pubnub_get_best_interface_index(void) +static DWORD get_best_interface_index(ADDRESS_FAMILY family) { - DWORD best_if_index = 0; - struct in_addr dest_addr; + SOCKADDR_INET dest_addr; + SOCKADDR_INET source_addr; + ZeroMemory(&dest_addr, sizeof(dest_addr)); + ZeroMemory(&source_addr, sizeof(source_addr)); + dest_addr.si_family = family; + + if (family == AF_INET) { + if (inet_pton(AF_INET, "8.8.8.8", &dest_addr.Ipv4.sin_addr) != 1) + return 0; + } +#if PUBNUB_USE_IPV6 + else if (family == AF_INET6) { + if (inet_pton(AF_INET6, "2001:4860:4860::8888", &dest_addr.Ipv6.sin6_addr) + != 1) + return 0; + } +#endif + else { + return 0; + } - if (inet_pton(AF_INET, PUBLIC_DNS_TEST_IP_1, &dest_addr) == 1) { - if (GetBestInterface(dest_addr.S_un.S_addr, &best_if_index) == NO_ERROR && best_if_index != 0) { - return best_if_index; - } + MIB_IPFORWARD_ROW2 route; + DWORD ret = GetBestRoute2(NULL, 0, NULL, &dest_addr, 0, &route, &source_addr); + + if (ret != NO_ERROR) { + PUBNUB_LOG_DEBUG("GetBestRoute2(family=%lu) failed: %lu\n", + family, + (unsigned long)ret); + return 0; } - if (inet_pton(AF_INET, PUBLIC_DNS_TEST_IP_2, &dest_addr) == 1) { - if (GetBestInterface(dest_addr.S_un.S_addr, &best_if_index) == NO_ERROR && best_if_index != 0) { - return best_if_index; - } + NET_LUID luid = route.InterfaceLuid; + NET_IFINDEX if_index; + ret = ConvertInterfaceLuidToIndex(&luid, &if_index); + + if (ret != NO_ERROR) { + PUBNUB_LOG_DEBUG( + "ConvertInterfaceLuidToIndex(family=%lu) failed: %lu\n", + family, + (unsigned long)ret); + return 0; } - return 0; + PUBNUB_LOG_TRACE("Best interface index for %s: %lu\n", + family == AF_INET ? "IPv4" : "IPv6", + (unsigned long)if_index); + + return (DWORD)if_index; } -/** Check if DNS server is reachable from the adapter. -* A DNS server is reachable if: -* 1. It's on the same subnet as one of the adapter's unicast addresses, OR -* 2. The adapter has a valid gateway (can route to it) -* -* @param dns_addr_net DNS address in network byte order. -* @param aa Pointer to the adapter that should be tested for subnet unicast -* match with provided DNS server address. -* @return @c true if the adapter can route the request to the provided DNS -* server address, @c false otherwise. -*/ -static bool pubnub_dns_reachable_from_adapter(DWORD dns_addr_net, IP_ADAPTER_ADDRESSES* aa) + +/** Comparison function for qsort - prioritize adapters. */ +static int compare_adapter_priority(const void* a, const void* b) { - DWORD dns_addr = ntohl(dns_addr_net); + const struct adapter_info* aa = (const struct adapter_info*)a; + const struct adapter_info* bb = (const struct adapter_info*)b; - /* Check if DNS is on the same subnet as any unicast address */ - for (IP_ADAPTER_UNICAST_ADDRESS* ua = aa->FirstUnicastAddress; - ua != NULL; - ua = ua->Next) { - if (ua->Address.lpSockaddr && ua->Address.lpSockaddr->sa_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)ua->Address.lpSockaddr; - DWORD if_addr = ntohl(sin->sin_addr.S_un.S_addr); + /* Best route adapters first */ + if (aa->is_best_route && !bb->is_best_route) + return -1; + if (!aa->is_best_route && bb->is_best_route) + return 1; - /* Get subnet mask from prefix length */ - ULONG prefix_len = ua->OnLinkPrefixLength; - if (prefix_len > 0 && prefix_len <= 32) { - DWORD mask = (prefix_len == 32) ? 0xFFFFFFFF : ~((1UL << (32 - prefix_len)) - 1); + /* Then by metric (lower is better) */ + if (aa->metric < bb->metric) + return -1; + if (aa->metric > bb->metric) + return 1; - /* Check if DNS is on same subnet */ - if ((dns_addr & mask) == (if_addr & mask)) { - return true; - } - } - } - } - - /* Check if adapter has a valid gateway (can route off-subnet) */ - for (IP_ADAPTER_GATEWAY_ADDRESS* gw = aa->FirstGatewayAddress; gw != NULL; gw = gw->Next) { - if (gw->Address.lpSockaddr && gw->Address.lpSockaddr->sa_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)gw->Address.lpSockaddr; - if (pubnub_ipv4_address_valid(ntohl(sin->sin_addr.S_un.S_addr))) - return true; - } - } - - return false; + return 0; } -/** Retrieve DNS servers from network adapters, prioritized by routing metrics. -* - * @param o_ipv4 Pointer to the array of @c pubnub_ipv4_address structures with - * previously discovered DNS server addresses. - * @param n Maximum number of discovered DNS server addresses. - * @return Number of discovered system DNS servers, @c zero otherwise. +/** Process provided list of adapter addresses to find useful DNS server + * addresses. + * + * @param family Adapter address family (@c AF_INET or @c AF_INET6). + * @param addrs List of @c family adapter addresses. + * @param n Maximum number of required DNS server addresses. + * @param[out] count Number of verified adapters with DNS servers. + * @return List of @c adapter_info with suitable adapters. */ -int pubnub_dns_read_system_servers_ipv4( - struct pubnub_ipv4_address* o_ipv4, - size_t n) +struct adapter_info* pubnub_dns_read_system_servers(ULONG family, + IP_ADAPTER_ADDRESSES* addrs, + const size_t n, + size_t* count) { - if (!o_ipv4 || n == 0) return 0; - - IP_ADAPTER_ADDRESSES* addrs = NULL; - if (!pubnub_adapter_addresses_get(&addrs)) return 0; + if (!addrs) + return NULL; - /* Get the best interface index for routing to public Internet. */ - DWORD best_if_index = pubnub_get_best_interface_index(); - - /* Count suitable adapters. */ size_t adapter_count = 0; for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { - if (pubnub_adapter_is_suitable(aa)) adapter_count++; + adapter_count++; } if (adapter_count == 0) { - FREE(addrs); - return 0; + PUBNUB_LOG_WARNING("No suitable adapters found (family=%lu)\n", family); + *count = 0; + return NULL; } - /* Build array of adapters with their metrics for sorting. */ - struct adapter_with_metric* adapter_list = - (struct adapter_with_metric*)MALLOC(adapter_count * sizeof(struct adapter_with_metric)); - + struct adapter_info* adapter_list = + (struct adapter_info*)MALLOC(adapter_count * sizeof(struct adapter_info)); if (!adapter_list) { - FREE(addrs); - return 0; + *count = 0; + return NULL; } - size_t idx = 0; + DWORD best_if_index = get_best_interface_index(family); + size_t idx = 0; for (IP_ADAPTER_ADDRESSES* aa = addrs; aa != NULL; aa = aa->Next) { - if (pubnub_adapter_is_suitable(aa)) { + if (is_adapter_suitable(family, aa)) { adapter_list[idx].adapter = aa; - adapter_list[idx].metric = aa->Ipv4Metric; + adapter_list[idx].metric = + AF_INET == family ? aa->Ipv4Metric : aa->Ipv6Metric; adapter_list[idx].is_best_route = (best_if_index != 0 && aa->IfIndex == best_if_index); + adapter_list[idx].dns_count = 0; idx++; } } - /* Sort adapters by priority. */ - qsort(adapter_list, adapter_count, sizeof(struct adapter_with_metric), - compare_adapters_by_priority); + adapter_count = idx; + if (adapter_count == 0) { + PUBNUB_LOG_WARNING("No suitable adapters found (family=%lu)\n", family); + FREE(adapter_list); + *count = 0; + return NULL; + } - /* Extract DNS servers from adapters in priority order. */ - unsigned dns_count = 0; - for (size_t i = 0; i < adapter_count && dns_count < n; i++) { + qsort(adapter_list, + adapter_count, + sizeof(struct adapter_info), + compare_adapter_priority); + + size_t total_dns_count = 0; + for (size_t i = 0; i < adapter_count && total_dns_count < n; i++) { IP_ADAPTER_ADDRESSES* aa = adapter_list[i].adapter; - PUBNUB_LOG_TRACE("Processing adapter: Index=%lu, Metric=%lu, IsBestRoute=%d\n", - (unsigned long)aa->IfIndex, - (unsigned long)adapter_list[i].metric, - adapter_list[i].is_best_route); + PUBNUB_LOG_TRACE( + "Processing adapter idx=%lu metric=%lu best=%d (family=%lu)\n", + (unsigned long)aa->IfIndex, + (unsigned long)adapter_list[i].metric, + adapter_list[i].is_best_route, + family); + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; + ds && adapter_list[i].dns_count < MAX_DNS_SERVERS_PER_ADAPTER + && total_dns_count < n; + ds = ds->Next) { - for (IP_ADAPTER_DNS_SERVER_ADDRESS* ds = aa->FirstDnsServerAddress; ds && dns_count < n; ds = ds->Next) { - if (!ds->Address.lpSockaddr || ds->Address.lpSockaddr->sa_family != AF_INET) + if (!ds->Address.lpSockaddr) continue; - const struct sockaddr_in* sin = (const struct sockaddr_in*)ds->Address.lpSockaddr; - DWORD net_addr = sin->sin_addr.S_un.S_addr; - if (net_addr == 0) continue; + ULONG dns_family = ds->Address.lpSockaddr->sa_family; - /* Verifying adapter can be used to reach DNS server. */ - if (!pubnub_dns_reachable_from_adapter(net_addr, aa)) { - PUBNUB_LOG_DEBUG("Skipping DNS server - not reachable from " - "adapter\n"); + /* IPv4 can't handle IPv6 addresses and should skip such. */ + if (AF_INET == family && dns_family != family) continue; + + uint8_t* dns_bytes = NULL; + + if (AF_INET == dns_family) { + struct sockaddr_in* sin = + (struct sockaddr_in*)ds->Address.lpSockaddr; + dns_bytes = (uint8_t*)&sin->sin_addr.s_addr; + } +#if PUBNUB_USE_IPV6 + else if (AF_INET6 == dns_family) { + struct sockaddr_in6* sin6 = + (struct sockaddr_in6*)ds->Address.lpSockaddr; + dns_bytes = (uint8_t*)&sin6->sin6_addr; } +#endif /* PUBNUB_USE_IPV6 */ - /* Verify DNS server is actually reachable via TCP connection test. - * This detects stale VPN connections where the adapter appears UP but - * the DNS server is unreachable. This is critical because GetBestInterface - * may return a stale VPN adapter if Windows routing table hasn't been updated. - * We test ALL adapters to catch this scenario. - */ - if (!pubnub_dns_server_reachable(net_addr)) { - PUBNUB_LOG_WARNING("Skipping DNS server - TCP connection test " - "failed (possibly stale VPN)\n"); + if (!dns_bytes || !is_valid_address(dns_family, dns_bytes)) continue; + + /* Check for duplicates across all already verified DNS servers */ + bool is_duplicate = false; + for (size_t j = 0; j <= i; j++) { + for (size_t k = 0; k < adapter_list[j].dns_count; k++) { + struct sockaddr_storage* stored = + &adapter_list[j].dns_servers[k]; + if (AF_INET == dns_family && stored->ss_family == AF_INET) { + struct sockaddr_in* stored_sin = (struct sockaddr_in*)stored; + if (memcmp(&stored_sin->sin_addr.s_addr, dns_bytes, 4) == 0) { + is_duplicate = true; + break; + } + } +#if PUBNUB_USE_IPV6 + else if (AF_INET6 == dns_family + && stored->ss_family == AF_INET6) { + struct sockaddr_in6* stored_sin6 = + (struct sockaddr_in6*)stored; + if (memcmp(&stored_sin6->sin6_addr, dns_bytes, 16) == 0) { + is_duplicate = true; + break; + } + } +#endif + } + if (is_duplicate) + break; + } + + /* Skipping already added DNS server. */ + if (is_duplicate) + continue; + +#if PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT + if (!validate_dns_server_udp(dns_family, dns_bytes, aa->IfIndex)) { + PUBNUB_LOG_WARNING( + "Skipping DNS - validation failed (family=%lu)\n", family); + continue; + } +#endif /* PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT */ + + /* Store the verified DNS server */ + struct sockaddr_storage* verified = + &adapter_list[i].dns_servers[adapter_list[i].dns_count]; + ZeroMemory(verified, sizeof(struct sockaddr_storage)); + + if (AF_INET == dns_family) { + struct sockaddr_in* sin_out = (struct sockaddr_in*)verified; + sin_out->sin_family = AF_INET; + sin_out->sin_port = htons(53); + memcpy(&sin_out->sin_addr.s_addr, dns_bytes, 4); } +#if PUBNUB_USE_IPV6 + else if (AF_INET6 == dns_family) { + struct sockaddr_in6* sin6_out = (struct sockaddr_in6*)verified; + sin6_out->sin6_family = AF_INET6; + sin6_out->sin6_port = htons(53); + memcpy(&sin6_out->sin6_addr, dns_bytes, 16); + } +#endif /* PUBNUB_USE_IPV6 */ + + adapter_list[i].dns_count++; + total_dns_count++; + } + } + + *count = adapter_count; + return adapter_list; +} + + +/** + * Read system DNS servers for IPv4. + * + * Thread-safe implementation using GetAdaptersAddresses with optional + * DNS validation to filter out stale VPN servers. + * + * @param o_ipv4 Output array (must be allocated by caller). + * @param n Maximum number of DNS servers to return. + * @return Number of DNS servers found, or -1 on error. + */ +int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size_t n) +{ + PUBNUB_ASSERT_OPT(o_ipv4 != NULL); + PUBNUB_ASSERT_OPT(n > 0); + + if (!o_ipv4 || n == 0) + return -1; + + IP_ADAPTER_ADDRESSES* addrs = get_adapter_addresses(AF_INET); + if (!addrs) + return -1; + + size_t count = 0; + struct adapter_info* adapter_list = + pubnub_dns_read_system_servers(AF_INET, addrs, n, &count); + if (!adapter_list || 0 == count) { + FREE(addrs); + if (adapter_list) + FREE(adapter_list); + return -1; + } + + unsigned dns_count = 0; + for (size_t i = 0; i < count; i++) { + if (!adapter_list[i].dns_count) + continue; + for (size_t d = 0; d < adapter_list[i].dns_count; d++) { + struct sockaddr_in* sin = + (struct sockaddr_in*)&adapter_list[i].dns_servers[d]; + uint8_t* dns_bytes = (uint8_t*)&sin->sin_addr.s_addr; + memcpy(o_ipv4[dns_count].ipv4, dns_bytes, 4); + + PUBNUB_LOG_TRACE("Added IPv4 DNS: %u.%u.%u.%u\n", + dns_bytes[0], + dns_bytes[1], + dns_bytes[2], + dns_bytes[3]); + dns_count++; + } + } - /* Add to output if not duplicate */ - if (pubnub_copy_ipv4_bytes_from_be_dword(o_ipv4, dns_count, net_addr, o_ipv4[dns_count].ipv4)) { - PUBNUB_LOG_TRACE("Added DNS server from adapter index %lu\n", - (unsigned long)aa->IfIndex); - ++dns_count; + FREE(adapter_list); + FREE(addrs); + + if (dns_count == 0) { + PUBNUB_LOG_ERROR("No valid DNS servers found\n"); + return -1; + } + + PUBNUB_LOG_DEBUG("Discovered %u DNS server(s)\n", dns_count); + return (int)dns_count; +} + + +#if PUBNUB_USE_IPV6 +int pubnub_dns_read_system_servers_ipv6(struct pubnub_ipv6_address* o_ipv6, size_t n) +{ + PUBNUB_ASSERT_OPT(o_ipv6 != NULL); + PUBNUB_ASSERT_OPT(n > 0); + + if (!o_ipv6 || n == 0) + return -1; + + IP_ADAPTER_ADDRESSES* addrs = get_adapter_addresses(AF_INET6); + if (!addrs) + return -1; + + size_t count = 0; + struct adapter_info* adapter_list = + pubnub_dns_read_system_servers(AF_INET6, addrs, n, &count); + if (!adapter_list || 0 == count) { + FREE(addrs); + if (adapter_list) + FREE(adapter_list); + return -1; + } + + bool has_ipv4_addresses = false; + unsigned dns_count = 0; + for (size_t i = 0; i < count; i++) { + if (!adapter_list[i].dns_count) + continue; + for (size_t d = 0; d < adapter_list[i].dns_count; d++) { + struct sockaddr_storage* stored = &adapter_list[i].dns_servers[d]; + uint8_t dns_bytes_buffer[16]; + uint8_t* dns_bytes = NULL; + + if (AF_INET == stored->ss_family) { + struct sockaddr_in* sin = (struct sockaddr_in*)stored; + memset(dns_bytes_buffer, 0, 10); + dns_bytes_buffer[10] = 0xff; + dns_bytes_buffer[11] = 0xff; + memcpy(&dns_bytes_buffer[12], &sin->sin_addr.s_addr, 4); + dns_bytes = dns_bytes_buffer; + has_ipv4_addresses = true; + } + else { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)stored; + dns_bytes = (uint8_t*)&sin6->sin6_addr; } + memcpy(o_ipv6[dns_count].ipv6, dns_bytes, 16); + + char str[INET6_ADDRSTRLEN]; + PUBNUB_LOG_TRACE( + "Added %s DNS (%s).\n", + AF_INET == stored->ss_family ? "IPv4-mapped IPv6" : "IPv6", + inet_ntop(AF_INET6, dns_bytes, str, INET6_ADDRSTRLEN)); + dns_count++; } } FREE(adapter_list); FREE(addrs); + if (dns_count == 0) { + PUBNUB_LOG_WARNING("No IPv6 DNS servers found\n"); + return 0; + } + + PUBNUB_LOG_DEBUG("Discovered %u %s DNS server(s)\n", + dns_count, + has_ipv4_addresses ? "IPv4-mapped IPv6/IPv6" : "IPv6"); return (int)dns_count; } +#endif /* PUBNUB_USE_IPV6 */ From 3459b4ef10c312287f7b14888f53d92ad2f905dd Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Tue, 2 Dec 2025 16:23:06 +0200 Subject: [PATCH 14/20] refactor(default): move default DNS definitions --- core/test/pubnub_config.h | 5 ++--- lib/sockets/pbpal_resolv_and_connect_sockets.c | 2 ++ openssl/pubnub_config.h | 5 ++--- posix/pubnub_config.h | 5 ++--- windows/pubnub_config.h | 5 ++--- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/core/test/pubnub_config.h b/core/test/pubnub_config.h index 00425407..9fd3f2b3 100644 --- a/core/test/pubnub_config.h +++ b/core/test/pubnub_config.h @@ -73,9 +73,6 @@ #define PUBNUB_USE_MDNS 1 #endif -#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" -#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" - #if defined(PUBNUB_CALLBACK_API) #if !defined(PUBNUB_USE_IPV6) /** If true (!=0), enable support for Ipv6 network addresses */ @@ -106,6 +103,8 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif +#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" + /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS diff --git a/lib/sockets/pbpal_resolv_and_connect_sockets.c b/lib/sockets/pbpal_resolv_and_connect_sockets.c index e431f8e4..9f189dd1 100644 --- a/lib/sockets/pbpal_resolv_and_connect_sockets.c +++ b/lib/sockets/pbpal_resolv_and_connect_sockets.c @@ -22,6 +22,8 @@ #include #endif +#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" +#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" #define HTTP_PORT 80 #define TLS_PORT 443 diff --git a/openssl/pubnub_config.h b/openssl/pubnub_config.h index 12232b1c..201ca667 100644 --- a/openssl/pubnub_config.h +++ b/openssl/pubnub_config.h @@ -96,9 +96,6 @@ #define PUBNUB_PROXY_API 1 #endif -#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" -#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" - #if defined(PUBNUB_CALLBACK_API) /** The size of the stack (in kilobytes) for the "polling" thread, when using the callback interface. We don't need much, so, if you want to conserve @@ -137,6 +134,8 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif +#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" + /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS diff --git a/posix/pubnub_config.h b/posix/pubnub_config.h index ff0c5ae0..222942af 100644 --- a/posix/pubnub_config.h +++ b/posix/pubnub_config.h @@ -99,9 +99,6 @@ */ #define PUBNUB_CALLBACK_THREAD_STACK_SIZE_KB 0 -#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" -#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" - #if !defined(PUBNUB_USE_IPV6) /** If true (!=0), enable support for Ipv6 network addresses */ #define PUBNUB_USE_IPV6 1 @@ -129,6 +126,8 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif +#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" + /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS diff --git a/windows/pubnub_config.h b/windows/pubnub_config.h index 928897dd..b0bb02f2 100644 --- a/windows/pubnub_config.h +++ b/windows/pubnub_config.h @@ -89,9 +89,6 @@ #define PUBNUB_PROXY_API 1 #endif -#define PUBNUB_DEFAULT_IPV4_DNS_SERVER "8.8.8.8" -#define PUBNUB_DEFAULT_IPV6_DNS_SERVER "2001:4860:4860:0000:0000:0000:0000:8888" - #if defined(PUBNUB_CALLBACK_API) /** The size of the stack (in kilobytes) for the "polling" thread, when using the callback interface. We don't need much, so, if you want to conserve @@ -128,6 +125,8 @@ #define PUBNUB_CHANGE_DNS_SERVERS 1 #endif +#define PUBNUB_DEFAULT_DNS_SERVER "8.8.8.8" + /** Maximum number of consecutive retries when sending DNS query in a single transaction */ #define PUBNUB_MAX_DNS_QUERIES 3 #if PUBNUB_CHANGE_DNS_SERVERS From d52a81b01f7b96a78d3c6bb4c50fe9a3af9c0f6c Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Fri, 5 Dec 2025 00:40:11 +0200 Subject: [PATCH 15/20] fix(await): fix `pubnub_await` after `pubnub_cancel` Fix issue because of which `pubnub_await` wasn't waiting after `pubnub_cancel` has been called. fix(proxy-windows): fix auto-detection PAC proxy Fix `WinHttpGetProxyForUrl` usage with URL with schema instead of domain only. fix(dns-posix): fix `resolv.conf` parsing Fix issue with `/etc/resolv.conf` parsing where untrimmed string produced wrong address. --- core/pubnub_ntf_sync.c | 1 + lib/pubnub_parse_ipv6_addr.c | 4 +- .../pbpal_resolv_and_connect_sockets.c | 9 +- posix/pubnub_dns_system_servers.c | 18 ++- .../pubnub_set_proxy_from_system_windows.c | 107 +++++++++++++----- 5 files changed, 108 insertions(+), 31 deletions(-) diff --git a/core/pubnub_ntf_sync.c b/core/pubnub_ntf_sync.c index 14b9fa50..536ba33f 100644 --- a/core/pubnub_ntf_sync.c +++ b/core/pubnub_ntf_sync.c @@ -256,6 +256,7 @@ enum pubnub_res pubnub_await(pubnub_t* pb) } #endif /* PUBNUB_NTF_RUNTIME_SELECTION */ + pb->should_stop_await = false; t0 = pbms_start(); while (!pbnc_can_start_transaction(pb)) { // Checking whether await cycle should be stopped or not. diff --git a/lib/pubnub_parse_ipv6_addr.c b/lib/pubnub_parse_ipv6_addr.c index 3af1663a..e7319f1c 100644 --- a/lib/pubnub_parse_ipv6_addr.c +++ b/lib/pubnub_parse_ipv6_addr.c @@ -94,12 +94,12 @@ int pubnub_parse_ipv6_addr(char const* addr, struct pubnub_ipv6_address* p) previous_colon = true; continue; } - else if (isdigit(*pos)) { + else if (isdigit((unsigned char)*pos)) { digit_value = *pos - '0'; } else if ((('a' <= *pos) && (*pos <= 'f')) || (('A' <= *pos) && (*pos <= 'F'))) { - digit_value = toupper(*pos) - 'A' + 10; + digit_value = toupper((unsigned char)*pos) - 'A' + 10; } else { PUBNUB_LOG_ERROR("Error :pubnub_parse_ipv6_addr('%s') - " diff --git a/lib/sockets/pbpal_resolv_and_connect_sockets.c b/lib/sockets/pbpal_resolv_and_connect_sockets.c index 9f189dd1..ecb1c6ea 100644 --- a/lib/sockets/pbpal_resolv_and_connect_sockets.c +++ b/lib/sockets/pbpal_resolv_and_connect_sockets.c @@ -156,9 +156,8 @@ static void prepare_port_and_hostname(const pubnub_t* pb, #ifdef PUBNUB_CALLBACK_API static void get_default_ipv4_dns_ip(struct pubnub_ipv4_address* addr) { - if (pubnub_dns_read_system_servers_ipv4(addr, 1) != 1) { + if (pubnub_dns_read_system_servers_ipv4(addr, 1) != 1) inet_pton(AF_INET, PUBNUB_DEFAULT_IPV4_DNS_SERVER, addr); - } } #if PUBNUB_USE_IPV6 @@ -222,6 +221,7 @@ static void get_dns_ip(sa_family_t family, if (AF_INET == family) { // If only IPv4 supported we don't have to fall back to the IPv6. get_default_ipv4_dns_ip((struct pubnub_ipv4_address*)p); + addr->sa_family = AF_INET; } } } @@ -233,6 +233,7 @@ static void get_dns_ip(sa_family_t family, && 0 == ((struct sockaddr_in*)addr)->sin_addr.s_addr)) { void* pv6 = ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr; get_default_ipv6_dns_ip((struct pubnub_ipv6_address*)pv6); + addr->sa_family = AF_INET6; if (is_ipv4_mapped_ipv6(addr)) remap_ipv6_to_ipv4(addr); @@ -275,6 +276,7 @@ static void get_dns_ip(sa_family_t family, struct sockaddr* addr) && AF_INET == family) { // If only IPv4 supported we don't have to fall back to the IPv6. get_default_ipv4_dns_ip((struct pubnub_ipv4_address*)p); + addr->sa_family = AF_INET; } #if PUBNUB_USE_IPV6 } @@ -284,6 +286,7 @@ static void get_dns_ip(sa_family_t family, struct sockaddr* addr) && 0 == ((struct sockaddr_in*)addr)->sin_addr.s_addr)) { struct in6_addr* pv6 = &((struct sockaddr_in6*)addr)->sin6_addr; get_default_ipv6_dns_ip((struct pubnub_ipv6_address*)pv6->s6_addr); + addr->sa_family = AF_INET6; if (is_ipv4_mapped_ipv6(addr)) remap_ipv6_to_ipv4(addr); @@ -305,11 +308,13 @@ static void get_dns_ip(sa_family_t family, struct sockaddr* addr) struct pubnub_ipv4_address* p = (struct pubnub_ipv4_address*)&( ((struct sockaddr_in*)addr)->sin_addr.s_addr); get_default_ipv4_dns_ip(p); + addr->sa_family = AF_INET; } #if PUBNUB_USE_IPV6 else { struct in6_addr* pv6 = &((struct sockaddr_in6*)addr)->sin6_addr; get_default_ipv6_dns_ip((struct pubnub_ipv6_address*)pv6->s6_addr); + addr->sa_family = AF_INET6; if (is_ipv4_mapped_ipv6(addr)) remap_ipv6_to_ipv4(addr); diff --git a/posix/pubnub_dns_system_servers.c b/posix/pubnub_dns_system_servers.c index b7b81067..0592c30f 100644 --- a/posix/pubnub_dns_system_servers.c +++ b/posix/pubnub_dns_system_servers.c @@ -32,7 +32,17 @@ int pubnub_dns_read_system_servers_ipv4(struct pubnub_ipv4_address* o_ipv4, size /* Reads new line */ fgets(buffer, sizeof buffer, fp); if (strncmp(buffer, "nameserver", 10) == 0) { - if (pubnub_parse_ipv4_addr(buffer + 10, &o_ipv4[i]) != 0) { + char* addr_start = buffer + 10; + while (*addr_start == ' ' || *addr_start == '\t') { + addr_start++; + } + char* end = addr_start; + while (*end && *end != ' ' && *end != '\n' && *end != '\r' && *end != '\t') { + end++; + } + *end = '\0'; + + if (pubnub_parse_ipv4_addr(addr_start, &o_ipv4[i]) != 0) { PUBNUB_LOG_ERROR( "pubnub_dns_read_system_servers_ipv4():" "- ipv4 'numbers-and-dots' notation string(%s)" @@ -87,6 +97,12 @@ int pubnub_dns_read_system_servers_ipv6(struct pubnub_ipv6_address* o_ipv6, size addr_start++; } + char* end = addr_start; + while (*end && *end != ' ' && *end != '\n' && *end != '\r' && *end != '\t') { + end++; + } + *end = '\0'; + /* Try parsing as IPv6 first */ if (pubnub_parse_ipv6_addr(addr_start, &o_ipv6[dns_count]) == 0) { dns_bytes = o_ipv6[dns_count].ipv6; diff --git a/windows/pubnub_set_proxy_from_system_windows.c b/windows/pubnub_set_proxy_from_system_windows.c index 9e8839e9..0a9011ee 100644 --- a/windows/pubnub_set_proxy_from_system_windows.c +++ b/windows/pubnub_set_proxy_from_system_windows.c @@ -45,43 +45,82 @@ static int set_from_url4proxy(pubnub_t *p, wchar_t *url4proxy) PUBNUB_LOG_TRACE("set_from_url4proxy(url4proxy=%S)\n", url4proxy); for (it = url4proxy; *end != '\0'; it = end + 1) { wchar_t *port; - size_t separator_position = wcscspn(it, L"; "); + wchar_t *hostname_start; + wchar_t *hostname_end; + const size_t separator_position = wcscspn(it, L"; "); end = it + separator_position; - port = wmemchr(it, L':', end - it); - if (port != NULL) { - if (port[1] == L'/') { - it = port + 3; - port = wmemchr(it, L':', end - it); - if (NULL == port) { - port = end; - } + + // Skipping schema (if 'http://' or 'https://' is present). + if (end - it > 3 && it[0] != L'[') { + wchar_t *scheme_end = wmemchr(it, L':', end - it); + if (scheme_end != NULL && scheme_end[1] == L'/') + it = scheme_end + 3; + } + + if (it[0] == L'[') { + // IPv6 format: [2001:db8::1]:8080 or [2001:db8::1] + hostname_start = it + 1; + hostname_end = wmemchr(hostname_start, L']', end - hostname_start); + + if (hostname_end == NULL) { + PUBNUB_LOG_WARNING("Malformed IPv6 proxy address (missing ']'): %S\n", it); + continue; } + + if (hostname_end < end && hostname_end[1] == L':') + port = hostname_end + 1; + else + port = end; + } else { + // IPv4 or hostname format: 192.168.1.1:8080 or test.proxy.com:3128 + hostname_start = it; + port = NULL; + + for (wchar_t *p_scan = it; p_scan < end; p_scan++) { + if (*p_scan == L':') + port = p_scan; + } + + hostname_end = (port != NULL) ? port : end; } - else { - port = end; + + int bytes_written = WideCharToMultiByte(CP_UTF8, + 0, + hostname_start, + hostname_end - hostname_start, + p->proxy_hostname, + sizeof(p->proxy_hostname) - 1, + NULL, + NULL); + + if (bytes_written == 0) { + PUBNUB_LOG_WARNING("WideCharToMultiByte failed for proxy hostname: %lu\n", + GetLastError()); + continue; } - if (0 == WideCharToMultiByte( - CP_UTF8, 0, it, port - it, p->proxy_hostname, - sizeof p->proxy_hostname / sizeof p->proxy_hostname[0], - NULL, NULL)) { + if (bytes_written >= sizeof(p->proxy_hostname)) { + PUBNUB_LOG_WARNING("Proxy hostname too long (needs %d bytes)\n", + bytes_written); continue; } - p->proxy_hostname[port - it] = '\0'; + + p->proxy_hostname[bytes_written] = '\0'; + PUBNUB_LOG_TRACE("Set proxy_hostname = %s\n", p->proxy_hostname); - if (port != end) { + + if (port != NULL && port < end) { + const wchar_t saved_char = *end; *end = L'\0'; p->proxy_port = (uint16_t)wcstol(port + 1, NULL, 10); + *end = saved_char; + PUBNUB_LOG_TRACE("Set proxy_port = %hu\n", p->proxy_port); - if (0 == p->proxy_port) { + if (0 == p->proxy_port) continue; - } } - else { + else p->proxy_port = 80; - } - - return 0; } return -1; @@ -118,13 +157,29 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) if (use_auto_proxy) { char const *origin = PUBNUB_ORIGIN_SETTABLE ? p->origin : PUBNUB_ORIGIN; - wchar_t wide_origin[PUBNUB_MAX_PROXY_HOSTNAME_LENGTH + 1]; + char url_for_proxy[256]; + wchar_t wide_origin[256]; HINTERNET winhttp; + int url_len; + +#if PUBNUB_USE_SSL + char const *scheme = p->options.useSSL ? "https" : "http"; +#else /* PUBNUB_USE_SSL */ + char const *scheme = "http"; +#endif /* !PUBNUB_USE_SSL */ + + url_len = snprintf(url_for_proxy, sizeof(url_for_proxy), + "%s://%s", scheme, origin); + if (url_len < 0 || url_len >= sizeof(url_for_proxy)) { + PUBNUB_LOG_ERROR("Origin URL too long: '%s'\n", origin); + return -1; + } if (0 == - MultiByteToWideChar(CP_UTF8, 0, origin, -1, wide_origin, + MultiByteToWideChar(CP_UTF8, 0, url_for_proxy, -1, wide_origin, sizeof wide_origin / sizeof wide_origin[0])) { - PUBNUB_LOG_ERROR("Origin '%s' to wide string failed\n", origin); + PUBNUB_LOG_ERROR("Origin '%s' to wide string failed: %lu\n", + url_for_proxy, GetLastError()); return -1; } winhttp = WinHttpOpen(L"C-core", WINHTTP_ACCESS_TYPE_NO_PROXY, From b683d5c613ec4a9806dfa1272494373a94240227 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Fri, 5 Dec 2025 16:05:57 +0200 Subject: [PATCH 16/20] fix(windows): fix missing types --- lib/sockets/pbpal_resolv_and_connect_sockets.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/sockets/pbpal_resolv_and_connect_sockets.c b/lib/sockets/pbpal_resolv_and_connect_sockets.c index ecb1c6ea..56a730fc 100644 --- a/lib/sockets/pbpal_resolv_and_connect_sockets.c +++ b/lib/sockets/pbpal_resolv_and_connect_sockets.c @@ -17,6 +17,7 @@ #include "windows/pubnub_get_native_socket.h" #include #include +typedef ADDRESS_FAMILY sa_family_t; #else #include "posix/pubnub_get_native_socket.h" #include @@ -367,7 +368,7 @@ static enum pbpal_resolv_n_connect_result connect_TCP_socket(pubnub_t* pb, } #if defined(_WIN32) const BOOL enabled = - pbccTrue == options->tcp_keepalive.keepalive.enabled ? TRUE : FALSE; + pbccTrue == options->tcp_keepalive.enabled ? TRUE : FALSE; (void)setsockopt( *skt, SOL_SOCKET, SO_KEEPALIVE, (const char*)&enabled, sizeof(enabled)); // The most reliable time to set idle/interval/count From 7d7ed1662b825fd2e73f35bd08c99892c2020bf6 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 8 Dec 2025 11:17:47 +0200 Subject: [PATCH 17/20] fix(memory): fix layout of `pubnub_` struct Some Windows-related interfaces, because of different `pubnub_` struct layouts caused by different `pubnub_config.h` type aliases and pre-processing macros, accessed and modified wrong fields. feat(pac-windows): add IPv6 support for automated proxy discovery Add handling of the IPv6 proxy address provided by automated (or PAC) proxy discovery API. fix(pac-windows): fix cached PAC result handling on Windows Fix `set_from_url4proxy` for proper `lpszProxy` parsing. refactor(config): add missing pre-processing macro for all `pubnub_config.h` Add defaults for `PUBNUB_ADVANCED_KEEP_ALIVE` in all related `pubnub_config.h` files. --- .../pbpal_resolv_and_connect_sockets.c | 14 ++-- openssl/pubnub_config.h | 9 +++ posix/pubnub_config.h | 8 +++ windows/pbpal_windows_blocking_io.c | 4 ++ windows/pubnub_dns_system_servers.c | 4 ++ windows/pubnub_get_native_socket.c | 4 ++ windows/pubnub_ntf_callback_windows.c | 4 ++ .../pubnub_set_proxy_from_system_windows.c | 67 ++++++++++++++++++- 8 files changed, 103 insertions(+), 11 deletions(-) diff --git a/lib/sockets/pbpal_resolv_and_connect_sockets.c b/lib/sockets/pbpal_resolv_and_connect_sockets.c index 56a730fc..ce8934a0 100644 --- a/lib/sockets/pbpal_resolv_and_connect_sockets.c +++ b/lib/sockets/pbpal_resolv_and_connect_sockets.c @@ -613,15 +613,14 @@ enum pbpal_resolv_n_connect_result pbpal_resolv_and_connect(pubnub_t* pb) memcpy(dest.sin6_addr.s6_addr, pb->proxy_ipv6_address.ipv6, sizeof dest.sin6_addr.s6_addr); -#if defined(_WIN32) enum pbpal_resolv_n_connect_result rslt = connect_TCP_socket(pb, (struct sockaddr*)&dest, port); +#if defined(_WIN32) // Complete TCP Keep-Alive configuration if connection established. if (pbpal_connect_success == rslt) pbpal_set_tcp_keepalive(pb); -#else /* defined(_WIN32) */ - return connect_TCP_socket(pb, (struct sockaddr*)&dest, port); -#endif /* !defined(_WIN32) */ +#endif /* defined(_WIN32) */ + return rslt; } #endif /* PUBNUB_USE_IPV6 */ if (0 != pb->proxy_ipv4_address.ipv4[0] @@ -632,15 +631,14 @@ enum pbpal_resolv_n_connect_result pbpal_resolv_and_connect(pubnub_t* pb) memcpy(&(dest.sin_addr.s_addr), pb->proxy_ipv4_address.ipv4, sizeof dest.sin_addr.s_addr); -#if defined(_WIN32) enum pbpal_resolv_n_connect_result rslt = connect_TCP_socket(pb, (struct sockaddr*)&dest, port); +#if defined(_WIN32) // Complete TCP Keep-Alive configuration if connection established. if (pbpal_connect_success == rslt) pbpal_set_tcp_keepalive(pb); -#else - return connect_TCP_socket(pb, (struct sockaddr*)&dest, port); -#endif +#endif /* defined(_WIN32) */ + return rslt; } #endif /* PUBNUB_PROXY_API */ #if PUBNUB_USE_MULTIPLE_ADDRESSES diff --git a/openssl/pubnub_config.h b/openssl/pubnub_config.h index 201ca667..0d2fb366 100644 --- a/openssl/pubnub_config.h +++ b/openssl/pubnub_config.h @@ -241,6 +241,15 @@ these things all by himself using pubnub_heartbeat() transaction */ #define PUBNUB_USE_AUTO_HEARTBEAT 1 #endif + +#if !defined(PUBNUB_ADVANCED_KEEP_ALIVE) +/** If true (!=0) will enable use of advanced keep-alive parameters, + see pubnub_set_keep_alive_param() and will observe the + `Connection: close` if received from server or proxy. +*/ +#define PUBNUB_ADVANCED_KEEP_ALIVE 1 +#endif + #define PUBNUB_MAX_URL_PARAMS 12 #ifndef PUBNUB_RAND_INIT_VECTOR diff --git a/posix/pubnub_config.h b/posix/pubnub_config.h index 222942af..bf7979e2 100644 --- a/posix/pubnub_config.h +++ b/posix/pubnub_config.h @@ -233,6 +233,14 @@ #define PUBNUB_USE_AUTO_HEARTBEAT 1 #endif +#if !defined(PUBNUB_ADVANCED_KEEP_ALIVE) +/** If true (!=0) will enable use of advanced keep-alive parameters, + see pubnub_set_keep_alive_param() and will observe the + `Connection: close` if received from server or proxy. +*/ +#define PUBNUB_ADVANCED_KEEP_ALIVE 1 +#endif + #define PUBNUB_MAX_URL_PARAMS 12 #ifndef PUBNUB_RAND_INIT_VECTOR diff --git a/windows/pbpal_windows_blocking_io.c b/windows/pbpal_windows_blocking_io.c index 744f031f..e159f11d 100644 --- a/windows/pbpal_windows_blocking_io.c +++ b/windows/pbpal_windows_blocking_io.c @@ -4,7 +4,11 @@ #include "core/pubnub_assert.h" +#if PUBNUB_USE_SSL +#include "openssl/pubnub_internal.h" +#else #include "pubnub_internal.h" +#endif #include diff --git a/windows/pubnub_dns_system_servers.c b/windows/pubnub_dns_system_servers.c index 601d48ea..a9199e13 100644 --- a/windows/pubnub_dns_system_servers.c +++ b/windows/pubnub_dns_system_servers.c @@ -12,7 +12,11 @@ * Minimum Windows version: Windows 8 (NT 6.2) */ +#if PUBNUB_USE_SSL +#include "openssl/pubnub_internal.h" +#else #include "pubnub_internal.h" +#endif #include "core/pubnub_dns_servers.h" #include "core/pubnub_assert.h" #include "core/pubnub_log.h" diff --git a/windows/pubnub_get_native_socket.c b/windows/pubnub_get_native_socket.c index 07dc1cd5..d518c872 100644 --- a/windows/pubnub_get_native_socket.c +++ b/windows/pubnub_get_native_socket.c @@ -1,5 +1,9 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#if PUBNUB_USE_SSL +#include "openssl/pubnub_internal.h" +#else #include "pubnub_internal.h" +#endif #include "pubnub_get_native_socket.h" #include "core/pubnub_log.h" diff --git a/windows/pubnub_ntf_callback_windows.c b/windows/pubnub_ntf_callback_windows.c index c70e4970..9e33d3bb 100644 --- a/windows/pubnub_ntf_callback_windows.c +++ b/windows/pubnub_ntf_callback_windows.c @@ -1,5 +1,9 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#if PUBNUB_USE_SSL +#include "openssl/pubnub_internal.h" +#else #include "pubnub_internal.h" +#endif #include "core/pubnub_ntf_callback.h" #include "pbtimespec_elapsed_ms.h" diff --git a/windows/pubnub_set_proxy_from_system_windows.c b/windows/pubnub_set_proxy_from_system_windows.c index 0a9011ee..37ddec04 100644 --- a/windows/pubnub_set_proxy_from_system_windows.c +++ b/windows/pubnub_set_proxy_from_system_windows.c @@ -1,7 +1,14 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#include "core/pubnub_proxy.h" +/* When building with OpenSSL, we need the OpenSSL version of pubnub_internal.h + because it has a different struct pubnub_pal definition. The build system + should define PUBNUB_USE_SSL=1 for OpenSSL builds. */ +#if PUBNUB_USE_SSL +#include "openssl/pubnub_internal.h" +#else #include "pubnub_internal.h" +#endif +#include "core/pubnub_proxy.h" #include "core/pubnub_log.h" #include "core/pubnub_assert.h" @@ -51,6 +58,13 @@ static int set_from_url4proxy(pubnub_t *p, wchar_t *url4proxy) end = it + separator_position; + // Check for protocol prefix format (e.g., "http=127.0.0.1:8888" or "https=[::1]:8888") + wchar_t *equals_sign = wmemchr(it, L'=', end - it); + if (equals_sign != NULL) { + // Skip the protocol prefix (e.g., "http=" or "https=") + it = equals_sign + 1; + } + // Skipping schema (if 'http://' or 'https://' is present). if (end - it > 3 && it[0] != L'[') { wchar_t *scheme_end = wmemchr(it, L':', end - it); @@ -119,8 +133,15 @@ static int set_from_url4proxy(pubnub_t *p, wchar_t *url4proxy) if (0 == p->proxy_port) continue; } - else + else { +#if PUBNUB_USE_SSL + p->proxy_port = p->options.useSSL ? 443 : 80; +#else /* PUBNUB_USE_SSL */ p->proxy_port = 80; +#endif /* !PUBNUB_USE_SSL */ + } + + return 0; } return -1; @@ -129,6 +150,19 @@ static int set_from_url4proxy(pubnub_t *p, wchar_t *url4proxy) int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) { + PUBNUB_ASSERT_OPT(p != NULL); + + check_struct_layout_windows(); + + switch (protocol) { + case pbproxyHTTP_GET: + case pbproxyHTTP_CONNECT: + break; + default: + /* other proxy protocols not yet supported */ + return -1; + } + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_cfg = {0}; WINHTTP_AUTOPROXY_OPTIONS autoproxy_opts = {0}; WINHTTP_PROXY_INFO proxy_info = {0}; @@ -138,7 +172,7 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) if (WinHttpGetIEProxyConfigForCurrentUser(&ie_proxy_cfg)) { if (ie_proxy_cfg.lpszProxy != NULL) { - PUBNUB_LOG_INFO("Found proxy in Registry: %S", + PUBNUB_LOG_INFO("Found proxy in Registry: %S\n", ie_proxy_cfg.lpszProxy); url4proxy = ie_proxy_cfg.lpszProxy; /* This may be overriden if auto-detect is also on */ @@ -155,6 +189,7 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) } } + pubnub_mutex_lock(p->monitor); if (use_auto_proxy) { char const *origin = PUBNUB_ORIGIN_SETTABLE ? p->origin : PUBNUB_ORIGIN; char url_for_proxy[256]; @@ -172,6 +207,7 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) "%s://%s", scheme, origin); if (url_len < 0 || url_len >= sizeof(url_for_proxy)) { PUBNUB_LOG_ERROR("Origin URL too long: '%s'\n", origin); + pubnub_mutex_unlock(p->monitor); return -1; } @@ -180,6 +216,7 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) sizeof wide_origin / sizeof wide_origin[0])) { PUBNUB_LOG_ERROR("Origin '%s' to wide string failed: %lu\n", url_for_proxy, GetLastError()); + pubnub_mutex_unlock(p->monitor); return -1; } winhttp = WinHttpOpen(L"C-core", WINHTTP_ACCESS_TYPE_NO_PROXY, @@ -220,8 +257,32 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) WATCH_INT(rslt); if (0 == rslt) { p->proxy_type = protocol; +#if defined(PUBNUB_CALLBACK_API) +#if defined(PUBNUB_NTF_RUNTIME_SELECTION) + if (PNA_CALLBACK == p->api_policy) { +#endif + /* If we haven't got numerical address for proxy we'll have to do DNS resolution(from proxy + host name) later on, but in order to do that we have to have all proxy addresses(on the + given context) set to zeros. + */ + if (0 != pubnub_parse_ipv4_addr(p->proxy_hostname, &(p->proxy_ipv4_address))) { + memset(&(p->proxy_ipv4_address), 0, sizeof p->proxy_ipv4_address); +#if PUBNUB_USE_IPV6 + if (0 != pubnub_parse_ipv6_addr(p->proxy_hostname, &(p->proxy_ipv6_address))) { + memset(&(p->proxy_ipv6_address), 0, sizeof p->proxy_ipv6_address); + } +#endif + } +#if PUBNUB_USE_MULTIPLE_ADDRESSES + pbpal_multiple_addresses_reset_counters(&p->spare_addresses); +#endif +#if defined(PUBNUB_NTF_RUNTIME_SELECTION) + } /* if (PNA_CALLBACK == p->api_policy) */ +#endif +#endif /* defined(PUBNUB_CALLBACK_API) */ } free_winhttp_stuff(&ie_proxy_cfg, &proxy_info); + pubnub_mutex_unlock(p->monitor); return rslt; } From 298e40563341c7168a266f444a2f1326bf647ad2 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 8 Dec 2025 14:17:10 +0200 Subject: [PATCH 18/20] refactor: remove debug function call --- windows/pubnub_set_proxy_from_system_windows.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/windows/pubnub_set_proxy_from_system_windows.c b/windows/pubnub_set_proxy_from_system_windows.c index 37ddec04..b02c71b9 100644 --- a/windows/pubnub_set_proxy_from_system_windows.c +++ b/windows/pubnub_set_proxy_from_system_windows.c @@ -152,8 +152,6 @@ int pubnub_set_proxy_from_system(pubnub_t *p, enum pubnub_proxy_type protocol) { PUBNUB_ASSERT_OPT(p != NULL); - check_struct_layout_windows(); - switch (protocol) { case pbproxyHTTP_GET: case pbproxyHTTP_CONNECT: From 386dd694ee8a53c9c03c722757ffcf3a1486c51f Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Wed, 10 Dec 2025 11:02:25 +0200 Subject: [PATCH 19/20] refactor: apply reviewer's suggestion --- lib/sockets/pbpal_adns_sockets.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/sockets/pbpal_adns_sockets.c b/lib/sockets/pbpal_adns_sockets.c index c42fa284..e400408b 100644 --- a/lib/sockets/pbpal_adns_sockets.c +++ b/lib/sockets/pbpal_adns_sockets.c @@ -158,7 +158,7 @@ int read_dns_response(pb_socket_t skt, if (responses_received >= expected_responses) { PUBNUB_LOG_WARNING( "read_dns_response: Already received all responses\n"); - goto select_address; + return 0; } PUBNUB_LOG_TRACE("Expecting %d response(s), already received %d\n", @@ -258,7 +258,6 @@ int read_dns_response(pb_socket_t skt, } } -select_address: PUBNUB_LOG_TRACE("All %d DNS responses received\n", expected_responses); return 0; From de94f4210ba9ca536652a0866a01e2fb289724d4 Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:44:18 +0000 Subject: [PATCH 20/20] PubNub SDK v6.2.0 release. --- .pubnub.yml | 41 +++++++++++++++++++++++++++------- CHANGELOG.md | 20 +++++++++++++++++ core/pubnub_version_internal.h | 2 +- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 17640d8f..d245ed9b 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,33 @@ name: c-core schema: 1 -version: "6.1.0" +version: "6.2.0" scm: github.com/pubnub/c-core changelog: + - date: 2025-12-10 + version: v6.2.0 + changes: + - type: feature + text: "Add working IPv6 route detection to decide the preferable connection interface." + - type: feature + text: "When built with `PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT` set to delay in milliseconds, system DNS servers' discovery will make an actual DNS query, and the value will be used as a request timeout." + - type: feature + text: "Add handling of the IPv6 proxy address provided by automated (or PAC) proxy discovery API." + - type: bug + text: "Fix issue because of which `pubnub_await` wasn't waiting after `pubnub_cancel` has been called." + - type: bug + text: "Fix `WinHttpGetProxyForUrl` usage with URL with schema instead of domain only." + - type: bug + text: "Fix issue with `/etc/resolv.conf` parsing where untrimmed string produced wrong address." + - type: bug + text: "Some Windows-related interfaces, because of different `pubnub_` struct layouts caused by different `pubnub_config.h` type aliases and pre-processing macros, accessed and modified wrong fields." + - type: bug + text: "Fix `set_from_url4proxy` for proper `lpszProxy` parsing." + - type: improvement + text: "Add defaults for `PUBNUB_ADVANCED_KEEP_ALIVE` in all related `pubnub_config.h` files." + - type: improvement + text: "When built with IPv6 support, first try to use any of the user-provided IPv6 DNS servers before falling back to the user-provided IPv4 before falling back to the address provided by a well-known DNS provider." + - type: improvement + text: "When built with IPv6 support, SDK will send two queries to receive both records and decide which will be most suitable for currently available routing." - date: 2025-11-17 version: v6.1.0 changes: @@ -1065,7 +1090,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" @@ -1131,7 +1156,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" @@ -1197,7 +1222,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" @@ -1259,7 +1284,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" @@ -1320,7 +1345,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" @@ -1376,7 +1401,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" @@ -1429,7 +1454,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v6.1.0 + location: https://github.com/pubnub/c-core/releases/tag/v6.2.0 requires: - name: "miniz" diff --git a/CHANGELOG.md b/CHANGELOG.md index ea2cd4f6..fc11e5d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## v6.2.0 +December 10 2025 + +#### Added +- Add working IPv6 route detection to decide the preferable connection interface. +- When built with `PUBNUB_DNS_SERVERS_VALIDATION_TIMEOUT` set to delay in milliseconds, system DNS servers' discovery will make an actual DNS query, and the value will be used as a request timeout. +- Add handling of the IPv6 proxy address provided by automated (or PAC) proxy discovery API. + +#### Fixed +- Fix issue because of which `pubnub_await` wasn't waiting after `pubnub_cancel` has been called. +- Fix `WinHttpGetProxyForUrl` usage with URL with schema instead of domain only. +- Fix issue with `/etc/resolv.conf` parsing where untrimmed string produced wrong address. +- Some Windows-related interfaces, because of different `pubnub_` struct layouts caused by different `pubnub_config.h` type aliases and pre-processing macros, accessed and modified wrong fields. +- Fix `set_from_url4proxy` for proper `lpszProxy` parsing. + +#### Modified +- Add defaults for `PUBNUB_ADVANCED_KEEP_ALIVE` in all related `pubnub_config.h` files. +- When built with IPv6 support, first try to use any of the user-provided IPv6 DNS servers before falling back to the user-provided IPv4 before falling back to the address provided by a well-known DNS provider. +- When built with IPv6 support, SDK will send two queries to receive both records and decide which will be most suitable for currently available routing. + ## v6.1.0 November 17 2025 diff --git a/core/pubnub_version_internal.h b/core/pubnub_version_internal.h index 2398d35f..56beb97a 100644 --- a/core/pubnub_version_internal.h +++ b/core/pubnub_version_internal.h @@ -3,7 +3,7 @@ #define INC_PUBNUB_VERSION_INTERNAL -#define PUBNUB_SDK_VERSION "6.1.0" +#define PUBNUB_SDK_VERSION "6.2.0" #endif /* !defined INC_PUBNUB_VERSION_INTERNAL */