Compare commits

...

18 Commits
master ... ipv6

Author SHA1 Message Date
Lukas Werling d7659713dc Implement Happy Eyeballs for C4Network2HTTPClient
This should make masterserver requests more reliable for users with a
bad IPv6 connection.

See RFC6555
2017-01-15 20:20:49 +01:00
Lukas Werling f9c97e91f0 Allow adding connections from other addresses
Forcing a static address does not work for IPv6 where everyone has
multiple addresses that change over time. For example, adding a new
connection would fail if the preferred privacy address changes during
a game.
2017-01-15 17:43:32 +01:00
Lukas Werling b595e96b83 Fix adding connections to link-local IPv6 addresses 2017-01-15 17:35:30 +01:00
Lukas Werling cc1335fef9 Serialize addresses in UDP connection packages
The previous approach of just embedding the raw struct fails miserably
when trying to transfer AF_INET6 across platforms.
2017-01-14 23:29:16 +01:00
Lukas Werling 6addce2f95 Don't try to enable dual stack for IPv4 TCP sockets
When connecting via TCP, C4NetIO still creates IPv4 sockets, so no dual
stack option is required.
2017-01-13 23:03:50 +01:00
Lukas Werling 7411e458a0 Fix IPv6 sockets on Windows not being dual-stack
On Linux, all IPv6 sockets are dual-stack per default; on Windows, they
are not. It's still a good idea to set the option on Linux as well as
the default value can be changed.
2017-01-13 22:22:41 +01:00
Lukas Werling 23078c6e69 Fix missing IPv6 defines on Mac 2017-01-13 18:48:59 +01:00
Lukas Werling 76327b62a7 Change C4NetIOUDP broadcast to IPv6
It's not actually used anywhere, but it's not broken now!

This also moves the low-level and OS-specific GetLocalAddresses code to
C4NetIO where it's fitting better than in C4Network2Client.
2017-01-13 18:32:11 +01:00
Lukas Werling fd857ef771 Improve "unexpected address family" asserts 2017-01-13 18:32:11 +01:00
Lukas Werling 16b511b75d Use UDP address from puncher to derive TCP address
This is necessary for dual stack connections where the masterserver will
only an IPv6 address and the netpuncher will only return a UDP address/port.
2017-01-13 18:32:11 +01:00
Lukas Werling 33a32cb6bd Implement netpunching for IPv6 2017-01-13 18:32:01 +01:00
Lukas Werling c94b4cc3b0 Remove ResolveAddress()
As setting a default port is a common operation, add a helper function
for this.
2017-01-09 20:35:44 +01:00
Lukas Werling 60560125dc Fix local network discovery 2017-01-09 20:35:43 +01:00
Lukas Werling 65d8c11450 netpuncher: Add runtime error handling
Previously, the netpuncher would just exit silently when encountering an
error.
2017-01-09 20:35:43 +01:00
Lukas Werling 725e99bb9c Convert addresses from puncher to IPv4
Addresses from the puncher would show as [::ffff:1.2.3.4] and be a bit
confusing.
2017-01-09 20:35:43 +01:00
Lukas Werling f4bfd8c080 Remove obsolete CompileFunc for in_addr 2017-01-09 20:35:43 +01:00
Lukas Werling 758e7ca41e Change (discovery) multicast to IPv6
We use ff02::1 as discovery multicast address.

This "all nodes" multicast address is good enough for discovery in the
local network as packets there are likely broadcasted over ethernet
anyways.
2017-01-09 20:35:04 +01:00
Lukas Werling 0137c5f929 Fix host connections on link-local IPv6 addresses
Link-local IPv6 addresses are valid on all interfaces and thus need an
interface specifier / scope id, e.g. fe80::1%eth0.

This commit adds scope ids for initial host connections only. While not
optimal, this is probably enough in practise as the link-local addresses
are likely only important when there is no internet connectivity. In
this case, connecting clients directly is less of an advantage.
2017-01-09 20:35:04 +01:00
18 changed files with 490 additions and 235 deletions

View File

@ -76,7 +76,8 @@ namespace std {
auto unpack = make_tuple(v6.sin6_family, v6.sin6_port, v6.sin6_flowinfo, std::string((char*) v6.sin6_addr.s6_addr, 16), v6.sin6_scope_id); auto unpack = make_tuple(v6.sin6_family, v6.sin6_port, v6.sin6_flowinfo, std::string((char*) v6.sin6_addr.s6_addr, 16), v6.sin6_scope_id);
return hash<decltype(unpack)>()(unpack); return hash<decltype(unpack)>()(unpack);
} }
default: case C4NetIO::HostAddress::UnknownFamily:
assert(!"Unexpected address family");
return 0; return 0;
} }
} }

View File

@ -18,6 +18,11 @@
#include "network/C4Network2Address.h" #include "network/C4Network2Address.h"
#include <sstream> #include <sstream>
void C4NetpuncherID::CompileFunc(StdCompiler *pComp) {
pComp->Value(mkNamingAdapt(v4, "IPv4", 0u));
pComp->Value(mkNamingAdapt(v6, "IPv6", 0u));
}
std::unique_ptr<C4NetpuncherPacket> C4NetpuncherPacket::Construct(const C4NetIOPacket& rpack) { std::unique_ptr<C4NetpuncherPacket> C4NetpuncherPacket::Construct(const C4NetIOPacket& rpack) {
if (!rpack.getPData()) return nullptr; if (!rpack.getPData()) return nullptr;
try { try {

View File

@ -27,7 +27,14 @@ enum C4NetpuncherPacketType {
// extend this with exchanging ICE parameters, some day? // extend this with exchanging ICE parameters, some day?
}; };
typedef uint32_t C4NetpuncherID_t; struct C4NetpuncherID {
typedef uint32_t value;
value v4 = 0, v6 = 0;
void CompileFunc(StdCompiler *pComp);
bool operator==(const C4NetpuncherID& other) const { return v4 == other.v4 && v6 == other.v6; }
};
class C4NetpuncherPacket { class C4NetpuncherPacket {
public: public:
@ -38,7 +45,7 @@ public:
C4NetIOPacket PackTo(const C4NetIO::addr_t&) const; C4NetIOPacket PackTo(const C4NetIO::addr_t&) const;
protected: protected:
virtual StdBuf PackInto() const = 0; virtual StdBuf PackInto() const = 0;
typedef C4NetpuncherID_t CID; typedef C4NetpuncherID::value CID;
}; };
template<C4NetpuncherPacketType TYPE> template<C4NetpuncherPacketType TYPE>

View File

@ -29,7 +29,7 @@
class C4PuncherServer : public C4NetIOUDP, private C4NetIO::CBClass class C4PuncherServer : public C4NetIOUDP, private C4NetIO::CBClass
{ {
public: public:
typedef C4NetpuncherID_t CID; typedef C4NetpuncherID::value CID;
C4PuncherServer() { C4PuncherServer() {
C4NetIOUDP::SetCallback(this); C4NetIOUDP::SetCallback(this);
rng = std::bind(std::uniform_int_distribution<CID>(1/*, max*/), std::ref(random_device)); rng = std::bind(std::uniform_int_distribution<CID>(1/*, max*/), std::ref(random_device));
@ -96,7 +96,11 @@ int main(int argc, char * argv[])
printf("Listening on port %d...\n", iPort); printf("Listening on port %d...\n", iPort);
// Execute forever // Execute forever
Puncher.ExecuteUntil(-1); for (;;)
{
Puncher.ExecuteUntil(-1);
fprintf(stderr, "ERROR: %s\n", Puncher.GetError());
}
return 0; return 0;
} }

View File

@ -31,6 +31,8 @@
#include <process.h> #include <process.h>
#include <share.h> #include <share.h>
#include <winsock2.h>
#include <iphlpapi.h>
typedef int socklen_t; typedef int socklen_t;
int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); } int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); }
@ -43,6 +45,8 @@ int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); }
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netdb.h> #include <netdb.h>
#include <stdlib.h> #include <stdlib.h>
#include <ifaddrs.h>
#include <net/if.h>
#define ioctlsocket ioctl #define ioctlsocket ioctl
#define closesocket close #define closesocket close
@ -50,11 +54,16 @@ int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); }
#endif #endif
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning (disable : 4355) #pragma warning (disable : 4355)
#endif #endif
// These are named differently on mac.
#if !defined(IPV6_ADD_MEMBERSHIP) && defined(IPV6_JOIN_GROUP)
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
#endif
// constants definition // constants definition
const int C4NetIO::TO_INF = -1; const int C4NetIO::TO_INF = -1;
@ -244,6 +253,16 @@ bool C4NetIO::HostAddress::IsLoopback() const
return false; return false;
} }
bool C4NetIO::HostAddress::IsLocal() const
{
if (gen.sa_family == AF_INET6)
return IN6_IS_ADDR_LINKLOCAL(&v6.sin6_addr) != 0;
// We don't really care about local 169.256.0.0/16 addresses here as users will either have a
// router doing DHCP (which will prevent usage of these addresses) or have a network that
// doesn't care about IP and IPv6 link-local addresses will work.
return false;
}
void C4NetIO::HostAddress::SetScopeId(int scopeId) void C4NetIO::HostAddress::SetScopeId(int scopeId)
{ {
if (gen.sa_family != AF_INET6) return; if (gen.sa_family != AF_INET6) return;
@ -280,11 +299,27 @@ C4NetIO::HostAddress C4NetIO::HostAddress::AsIPv6() const
return nrv; return nrv;
} }
C4NetIO::HostAddress C4NetIO::HostAddress::AsIPv4() const
{
HostAddress nrv(*this);
if (gen.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&v6.sin6_addr))
{
nrv.v4.sin_family = AF_INET;
memcpy((char*) &nrv.v4.sin_addr, (char*) &v6.sin6_addr.s6_addr[12], sizeof(v4.sin_addr));
}
return nrv;
}
C4NetIO::EndpointAddress C4NetIO::EndpointAddress::AsIPv6() const C4NetIO::EndpointAddress C4NetIO::EndpointAddress::AsIPv6() const
{ {
return EndpointAddress(HostAddress::AsIPv6(), GetPort()); return EndpointAddress(HostAddress::AsIPv6(), GetPort());
} }
C4NetIO::EndpointAddress C4NetIO::EndpointAddress::AsIPv4() const
{
return EndpointAddress(HostAddress::AsIPv4(), GetPort());
}
void C4NetIO::HostAddress::SetHost(const sockaddr *addr) void C4NetIO::HostAddress::SetHost(const sockaddr *addr)
{ {
// Copy all but port number // Copy all but port number
@ -346,7 +381,19 @@ void C4NetIO::HostAddress::SetHost(uint32_t v4addr)
memset(&v4.sin_zero, 0, sizeof(v4.sin_zero)); memset(&v4.sin_zero, 0, sizeof(v4.sin_zero));
} }
void C4NetIO::EndpointAddress::SetAddress(const StdStrBuf &addr) void C4NetIO::HostAddress::SetHost(const StdStrBuf &addr, AddressFamily family)
{
addrinfo hints = addrinfo();
hints.ai_family = family;
addrinfo *addresses = nullptr;
if (getaddrinfo(addr.getData(), nullptr, &hints, &addresses) != 0)
// GAI failed
return;
SetHost(addresses->ai_addr);
freeaddrinfo(addresses);
}
void C4NetIO::EndpointAddress::SetAddress(const StdStrBuf &addr, AddressFamily family)
{ {
Clear(); Clear();
@ -402,6 +449,7 @@ void C4NetIO::EndpointAddress::SetAddress(const StdStrBuf &addr)
} }
addrinfo hints = addrinfo(); addrinfo hints = addrinfo();
hints.ai_family = family;
//hints.ai_flags = AI_NUMERICHOST; //hints.ai_flags = AI_NUMERICHOST;
addrinfo *addresses = nullptr; addrinfo *addresses = nullptr;
if (getaddrinfo(std::string(ab, ae).c_str(), pb != end ? std::string(pb, pe).c_str() : nullptr, &hints, &addresses) != 0) if (getaddrinfo(std::string(ab, ae).c_str(), pb != end ? std::string(pb, pe).c_str() : nullptr, &hints, &addresses) != 0)
@ -462,6 +510,12 @@ void C4NetIO::EndpointAddress::SetPort(uint16_t port)
} }
} }
void C4NetIO::EndpointAddress::SetDefaultPort(uint16_t port)
{
if (GetPort() == IPPORT_NONE)
SetPort(port);
}
uint16_t C4NetIO::EndpointAddress::GetPort() const uint16_t C4NetIO::EndpointAddress::GetPort() const
{ {
switch (gen.sa_family) switch (gen.sa_family)
@ -546,6 +600,66 @@ void C4NetIO::EndpointAddress::CompileFunc(StdCompiler *comp)
} }
} }
std::vector<C4NetIO::HostAddress> C4NetIO::GetLocalAddresses()
{
std::vector<HostAddress> result;
#ifdef HAVE_WINSOCK
HostAddress addr;
const size_t BUFFER_SIZE = 16000;
PIP_ADAPTER_ADDRESSES addresses = nullptr;
for (int i = 0; i < 3; ++i)
{
addresses = (PIP_ADAPTER_ADDRESSES) realloc(addresses, BUFFER_SIZE * (i+1));
if (!addresses)
// allocation failed
return result;
ULONG bufsz = BUFFER_SIZE * (i+1);
DWORD rv = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER|GAA_FLAG_SKIP_FRIENDLY_NAME,
nullptr, addresses, &bufsz);
if (rv == ERROR_BUFFER_OVERFLOW)
// too little space, try again
continue;
if (rv != NO_ERROR)
{
// Something else happened
free(addresses);
return result;
}
// All okay, add addresses
for (PIP_ADAPTER_ADDRESSES address = addresses; address; address = address->Next)
{
for (PIP_ADAPTER_UNICAST_ADDRESS unicast = address->FirstUnicastAddress; unicast; unicast = unicast->Next)
{
addr.SetHost(unicast->Address.lpSockaddr);
if (addr.IsLoopback())
continue;
result.push_back(addr);
}
}
}
free(addresses);
#else
struct ifaddrs* addrs;
if (getifaddrs(&addrs) < 0)
return result;
for (struct ifaddrs* ifaddr = addrs; ifaddr != nullptr; ifaddr = ifaddr->ifa_next)
{
struct sockaddr* ad = ifaddr->ifa_addr;
if (ad == nullptr) continue;
if ((ad->sa_family == AF_INET || ad->sa_family == AF_INET6) && (~ifaddr->ifa_flags & IFF_LOOPBACK)) // Choose only non-loopback IPv4/6 devices
{
result.emplace_back(ad);
}
}
freeifaddrs(addrs);
#endif
return result;
}
// *** C4NetIO // *** C4NetIO
// construction / destruction // construction / destruction
@ -560,6 +674,17 @@ C4NetIO::~C4NetIO()
} }
bool C4NetIO::EnableDualStack(SOCKET socket)
{
int opt = 0;
if (setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char*>(&opt), sizeof(opt)) == SOCKET_ERROR)
{
SetError("could not enable dual-stack socket", true);
return false;
}
return true;
}
void C4NetIO::SetError(const char *strnError, bool fSockErr) void C4NetIO::SetError(const char *strnError, bool fSockErr)
{ {
fSockErr &= HaveSocketError(); fSockErr &= HaveSocketError();
@ -1310,6 +1435,8 @@ bool C4NetIOTCP::Listen(uint16_t inListenPort)
SetError("socket creation failed", true); SetError("socket creation failed", true);
return false; return false;
} }
if (!EnableDualStack(lsock))
return false;
// To be able to reuse the port after close // To be able to reuse the port after close
#if !defined(_DEBUG) && !defined(_WIN32) #if !defined(_DEBUG) && !defined(_WIN32)
int reuseaddr = 1; int reuseaddr = 1;
@ -1682,6 +1809,9 @@ bool C4NetIOSimpleUDP::Init(uint16_t inPort)
return false; return false;
} }
if (!EnableDualStack(sock))
return false;
// set reuse socket option // set reuse socket option
if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&fAllowReUse), sizeof fAllowReUse) == SOCKET_ERROR) if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&fAllowReUse), sizeof fAllowReUse) == SOCKET_ERROR)
{ {
@ -1757,9 +1887,9 @@ bool C4NetIOSimpleUDP::InitBroadcast(addr_t *pBroadcastAddr)
if (fMultiCast) CloseBroadcast(); if (fMultiCast) CloseBroadcast();
// broadcast addr valid? // broadcast addr valid?
if (pBroadcastAddr->IsMulticast()) if (!pBroadcastAddr->IsMulticast() || pBroadcastAddr->GetFamily() != HostAddress::IPv6)
{ {
SetError("invalid broadcast address"); SetError("invalid broadcast address (only IPv6 multicast addresses are supported)");
return false; return false;
} }
if (pBroadcastAddr->GetPort() != iPort) if (pBroadcastAddr->GetPort() != iPort)
@ -1769,8 +1899,8 @@ bool C4NetIOSimpleUDP::InitBroadcast(addr_t *pBroadcastAddr)
} }
// set mc ttl to somewhat about "same net" // set mc ttl to somewhat about "same net"
int iTTL = 16; int TTL = 16;
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, reinterpret_cast<char*>(&iTTL), sizeof(iTTL)) == SOCKET_ERROR) if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, reinterpret_cast<char*>(&TTL), sizeof(TTL)) == SOCKET_ERROR)
{ {
SetError("could not set mc ttl", true); SetError("could not set mc ttl", true);
return false; return false;
@ -1778,11 +1908,12 @@ bool C4NetIOSimpleUDP::InitBroadcast(addr_t *pBroadcastAddr)
// set up multicast group information // set up multicast group information
this->MCAddr = *pBroadcastAddr; this->MCAddr = *pBroadcastAddr;
MCGrpInfo.imr_multiaddr = static_cast<sockaddr_in*>(&MCAddr)->sin_addr; MCGrpInfo.ipv6mr_multiaddr = static_cast<sockaddr_in6>(MCAddr).sin6_addr;
MCGrpInfo.imr_interface.s_addr = INADDR_ANY; // TODO: do multicast on all interfaces?
MCGrpInfo.ipv6mr_interface = 0; // default interface
// join multicast group // join multicast group
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
reinterpret_cast<const char *>(&MCGrpInfo), sizeof(MCGrpInfo)) == SOCKET_ERROR) reinterpret_cast<const char *>(&MCGrpInfo), sizeof(MCGrpInfo)) == SOCKET_ERROR)
{ {
SetError("could not join multicast group"); // to do: more error information SetError("could not join multicast group"); // to do: more error information
@ -1844,10 +1975,10 @@ bool C4NetIOSimpleUDP::CloseBroadcast()
if (!fMultiCast) return true; if (!fMultiCast) return true;
// leave multicast group // leave multicast group
if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, if (setsockopt(sock, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP,
reinterpret_cast<const char *>(&MCGrpInfo), sizeof(MCGrpInfo)) == SOCKET_ERROR) reinterpret_cast<const char *>(&MCGrpInfo), sizeof(MCGrpInfo)) == SOCKET_ERROR)
{ {
SetError("could not join multicast group"); // to do: more error information SetError("could not leave multicast group"); // to do: more error information
return false; return false;
} }
@ -2049,10 +2180,10 @@ enum C4NetIOSimpleUDP::WaitResult C4NetIOSimpleUDP::WaitForSocket(int iTimeout)
bool C4NetIOSimpleUDP::SetMCLoopback(int fLoopback) bool C4NetIOSimpleUDP::SetMCLoopback(int fLoopback)
{ {
// enable/disable MC loopback // enable/disable MC loopback
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast<char *>(&fLoopback), sizeof fLoopback); setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, reinterpret_cast<char *>(&fLoopback), sizeof fLoopback);
// read result // read result
socklen_t iSize = sizeof(fLoopback); socklen_t iSize = sizeof(fLoopback);
if (getsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast<char *>(&fLoopback), &iSize) == SOCKET_ERROR) if (getsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, reinterpret_cast<char *>(&fLoopback), &iSize) == SOCKET_ERROR)
return false; return false;
fMCLoopback = !! fLoopback; fMCLoopback = !! fLoopback;
return true; return true;
@ -2086,6 +2217,76 @@ const unsigned int C4NetIOUDP::iUDPHeaderSize = 8 + 24; // (bytes)
#pragma pack (push, 1) #pragma pack (push, 1)
// We need to adapt C4NetIO::addr_t to put it in our UDP packages.
// Previously, the sockaddr_in struct was just put in directly. This is
// horribly non-portable though, especially as the value of AF_INET6 differs
// between platforms.
struct C4NetIOUDP::BinAddr
{
BinAddr() : type(0) {}
BinAddr(const C4NetIO::addr_t& addr)
{
switch (addr.GetFamily())
{
case C4NetIO::HostAddress::IPv4:
{
type = 1;
auto addr4 = static_cast<const sockaddr_in*>(&addr);
static_assert(sizeof(v4) == sizeof(addr4->sin_addr), "unexpected IPv4 address size");
memcpy(&v4, &addr4->sin_addr, sizeof(v4));
break;
}
case C4NetIO::HostAddress::IPv6:
{
type = 2;
auto addr6 = static_cast<const sockaddr_in6*>(&addr);
static_assert(sizeof(v6) == sizeof(addr6->sin6_addr), "unexpected IPv6 address size");
memcpy(&v6, &addr6->sin6_addr, sizeof(v6));
break;
}
default:
assert(!"Unexpected address family");
}
port = addr.GetPort();
}
operator C4NetIO::addr_t() const
{
C4NetIO::addr_t result;
switch (type)
{
case 1:
{
sockaddr_in addr4 = sockaddr_in();
addr4.sin_family = AF_INET;
memcpy(&addr4.sin_addr, &v4, sizeof(v4));
result.SetAddress(reinterpret_cast<sockaddr*>(&addr4));
break;
}
case 2:
{
sockaddr_in6 addr6 = sockaddr_in6();
addr6.sin6_family = AF_INET6;
memcpy(&addr6.sin6_addr, &v6, sizeof(v6));
result.SetAddress(reinterpret_cast<sockaddr*>(&addr6));
break;
}
default:
assert(!"Invalid address type");
}
result.SetPort(port);
return result;
}
uint16_t port;
uint8_t type;
union
{
uint8_t v4[4];
uint8_t v6[16];
};
};
// packet structures // packet structures
struct C4NetIOUDP::PacketHdr struct C4NetIOUDP::PacketHdr
{ {
@ -2096,20 +2297,20 @@ struct C4NetIOUDP::PacketHdr
struct C4NetIOUDP::ConnPacket : public PacketHdr struct C4NetIOUDP::ConnPacket : public PacketHdr
{ {
uint32_t ProtocolVer; uint32_t ProtocolVer;
C4NetIO::addr_t Addr; BinAddr Addr;
C4NetIO::addr_t MCAddr; BinAddr MCAddr;
}; };
struct C4NetIOUDP::ConnOKPacket : public PacketHdr struct C4NetIOUDP::ConnOKPacket : public PacketHdr
{ {
enum { MCM_NoMC, MCM_MC, MCM_MCOK } MCMode; enum { MCM_NoMC, MCM_MC, MCM_MCOK } MCMode;
C4NetIO::addr_t Addr; BinAddr Addr;
}; };
struct C4NetIOUDP::AddAddrPacket : public PacketHdr struct C4NetIOUDP::AddAddrPacket : public PacketHdr
{ {
C4NetIO::addr_t Addr; BinAddr Addr;
C4NetIO::addr_t NewAddr; BinAddr NewAddr;
}; };
struct C4NetIOUDP::DataPacketHdr : public PacketHdr struct C4NetIOUDP::DataPacketHdr : public PacketHdr
@ -2126,7 +2327,7 @@ struct C4NetIOUDP::CheckPacketHdr : public PacketHdr
struct C4NetIOUDP::ClosePacket : public PacketHdr struct C4NetIOUDP::ClosePacket : public PacketHdr
{ {
C4NetIO::addr_t Addr; BinAddr Addr;
}; };
@ -2214,13 +2415,40 @@ bool C4NetIOUDP::InitBroadcast(addr_t *pBroadcastAddr)
SetError("broadcast address is not valid"); SetError("broadcast address is not valid");
return false; return false;
} }
// set up adress // Set up address as unicast-prefix-based IPv6 multicast address (RFC 3306).
MCAddr.SetAddress(addr_t::AnyIPv4, iPort); sockaddr_in6 saddrgen = sockaddr_in6();
// search for a free one saddrgen.sin6_family = AF_INET6;
uint8_t *addrgen = saddrgen.sin6_addr.s6_addr;
// ff3e ("global multicast based on network prefix") : 64 (length of network prefix)
static const uint8_t mcast_prefix[4] = { 0xff, 0x3e, 0, 64};
memcpy(addrgen, mcast_prefix, sizeof(mcast_prefix));
addrgen += sizeof(mcast_prefix);
// 64 bit network prefix
addr_t prefixAddr;
for (auto& addr : GetLocalAddresses())
if (addr.GetFamily() == HostAddress::IPv6 && !addr.IsLocal())
{
prefixAddr.SetAddress(addr);
break;
}
if (prefixAddr.IsNull())
{
SetError("no IPv6 unicast address available");
return false;
}
static const size_t network_prefix_size = 8;
memcpy(addrgen, &static_cast<sockaddr_in6*>(&prefixAddr)->sin6_addr, network_prefix_size);
addrgen += network_prefix_size;
// 32 bit group id: search for a free one
for (int iRetries = 1000; iRetries; iRetries--) for (int iRetries = 1000; iRetries; iRetries--)
{ {
uint32_t rnd = UnsyncedRandom();
memcpy(addrgen, &rnd, sizeof(rnd));
// "high-order bit of the Group ID will be the same value as the T flag"
addrgen[0] |= 0x80;
// create new - random - address // create new - random - address
MCAddr.SetAddress(C4NetIO::HostAddress(0x000000ef | (UnsyncedRandom(0x1000000) << 8))); MCAddr.SetAddress((sockaddr*) &saddrgen);
MCAddr.SetPort(iPort);
// init broadcast // init broadcast
if (!C4NetIOSimpleUDP::InitBroadcast(&MCAddr)) if (!C4NetIOSimpleUDP::InitBroadcast(&MCAddr))
return false; return false;
@ -2977,14 +3205,16 @@ void C4NetIOUDP::Peer::OnRecv(const C4NetIOPacket &rPacket) // (mt-safe)
iLastPacketAsked = iLastMCPacketAsked = 0; iLastPacketAsked = iLastMCPacketAsked = 0;
// Activate Multicast? // Activate Multicast?
if (!pParent->fMultiCast) if (!pParent->fMultiCast)
if (!pPkt->MCAddr.IsNull()) {
addr_t MCAddr = pPkt->MCAddr;
if (!MCAddr.IsNull())
{ {
addr_t MCAddr = pPkt->MCAddr;
// Init Broadcast (with delayed loopback test) // Init Broadcast (with delayed loopback test)
pParent->fDelayedLoopbackTest = true; pParent->fDelayedLoopbackTest = true;
if (!pParent->InitBroadcast(&MCAddr)) if (!pParent->InitBroadcast(&MCAddr))
pParent->fDelayedLoopbackTest = false; pParent->fDelayedLoopbackTest = false;
} }
}
// build ConnOk Packet // build ConnOk Packet
ConnOKPacket nPack; ConnOKPacket nPack;
@ -3158,7 +3388,7 @@ bool C4NetIOUDP::Peer::DoConn(bool fMC) // (mt-safe)
if (pParent->fMultiCast) if (pParent->fMultiCast)
Pkt.MCAddr = pParent->C4NetIOSimpleUDP::getMCAddr(); Pkt.MCAddr = pParent->C4NetIOSimpleUDP::getMCAddr();
else else
Pkt.MCAddr.Clear(); Pkt.MCAddr = C4NetIO::addr_t();
return SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt), false, addr)); return SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt), false, addr));
} }
@ -3693,50 +3923,3 @@ void C4NetIOMan::EnlargeIO(int iBy)
delete[] ppNetIO; delete[] ppNetIO;
ppNetIO = ppnNetIO; ppNetIO = ppnNetIO;
} }
// *** helpers
bool ResolveAddress(const char *szAddress, C4NetIO::addr_t *paddr, uint16_t iPort)
{
assert(szAddress && paddr);
// port?
StdStrBuf Buf;
const char *pColon = strchr(szAddress, ':');
if (pColon)
{
// get port
iPort = atoi(pColon + 1);
// copy address
Buf.CopyUntil(szAddress, ':');
szAddress = Buf.getData();
}
// set up address
sockaddr_in raddr; ZeroMem(&raddr, sizeof raddr);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(iPort);
// no plain IP address?
if ((raddr.sin_addr.s_addr = inet_addr(szAddress)) == INADDR_NONE)
{
#ifdef HAVE_WINSOCK
if (!AcquireWinSock()) return false;
#endif
// resolve
hostent *pHost;
if (!(pHost = gethostbyname(szAddress)))
#ifdef HAVE_WINSOCK
{ ReleaseWinSock(); return false; }
ReleaseWinSock();
#else
return false;
#endif
// correct type?
if (pHost->h_addrtype != AF_INET || pHost->h_length != sizeof(in_addr))
return false;
// get address
raddr.sin_addr = *reinterpret_cast<in_addr *>(pHost->h_addr_list[0]);
}
// ok
paddr->SetAddress(reinterpret_cast<sockaddr*>(&raddr));
paddr->SetPort(iPort);
return true;
}

View File

@ -92,6 +92,7 @@ public:
HostAddress(SpecialAddress addr) { SetHost(addr); } HostAddress(SpecialAddress addr) { SetHost(addr); }
explicit HostAddress(uint32_t addr) { SetHost(addr); } explicit HostAddress(uint32_t addr) { SetHost(addr); }
HostAddress(const StdStrBuf &addr) { SetHost(addr); } HostAddress(const StdStrBuf &addr) { SetHost(addr); }
HostAddress(const sockaddr *addr) { SetHost(addr); }
AddressFamily GetFamily() const; AddressFamily GetFamily() const;
@ -102,15 +103,17 @@ public:
void SetHost(const sockaddr *addr); void SetHost(const sockaddr *addr);
void SetHost(const HostAddress &host); void SetHost(const HostAddress &host);
void SetHost(SpecialAddress host); void SetHost(SpecialAddress host);
void SetHost(const StdStrBuf &host); void SetHost(const StdStrBuf &host, AddressFamily family = UnknownFamily);
void SetHost(uint32_t host); void SetHost(uint32_t host);
C4NetIO::HostAddress AsIPv6() const; // convert an IPv4 address to an IPv6-mapped IPv4 address C4NetIO::HostAddress AsIPv6() const; // convert an IPv4 address to an IPv6-mapped IPv4 address
C4NetIO::HostAddress AsIPv4() const; // try to convert an IPv6-mapped IPv4 address to an IPv4 address (returns unchanged address if not possible)
// General categories // General categories
bool IsNull() const; bool IsNull() const;
bool IsMulticast() const; bool IsMulticast() const;
bool IsLoopback() const; bool IsLoopback() const;
bool IsLocal() const;
// bool IsBroadcast() const; // bool IsBroadcast() const;
StdStrBuf ToString(int flags = 0) const; StdStrBuf ToString(int flags = 0) const;
@ -146,12 +149,14 @@ public:
void SetAddress(const EndpointAddress &other); void SetAddress(const EndpointAddress &other);
void SetAddress(HostAddress::SpecialAddress addr, uint16_t port = IPPORT_NONE); void SetAddress(HostAddress::SpecialAddress addr, uint16_t port = IPPORT_NONE);
void SetAddress(const HostAddress &host, uint16_t port = IPPORT_NONE); void SetAddress(const HostAddress &host, uint16_t port = IPPORT_NONE);
void SetAddress(const StdStrBuf &addr); void SetAddress(const StdStrBuf &addr, AddressFamily family = UnknownFamily);
HostAddress GetHost() const { return *this; } // HostAddress copy ctor slices off port information HostAddress GetHost() const { return *this; } // HostAddress copy ctor slices off port information
EndpointAddress AsIPv6() const; // convert an IPv4 address to an IPv6-mapped IPv4 address EndpointAddress AsIPv6() const; // convert an IPv4 address to an IPv6-mapped IPv4 address
EndpointAddress AsIPv4() const; // try to convert an IPv6-mapped IPv4 address to an IPv4 address (returns unchanged address if not possible)
void SetPort(uint16_t port); void SetPort(uint16_t port);
void SetDefaultPort(uint16_t port); // set a port only if there is none
uint16_t GetPort() const; uint16_t GetPort() const;
bool IsNull() const; bool IsNull() const;
@ -192,8 +197,8 @@ public:
// conversions // conversions
operator sockaddr() const { return gen; } operator sockaddr() const { return gen; }
/* operator sockaddr_in() const { assert(gen.sa_family == AF_INET); return v4; } operator sockaddr_in() const { assert(gen.sa_family == AF_INET); return v4; }
operator sockaddr_in6() const { assert(gen.sa_family == AF_INET6); return v6; }*/ operator sockaddr_in6() const { assert(gen.sa_family == AF_INET6); return v6; }
// StdCompiler // StdCompiler
void CompileFunc(StdCompiler *comp); void CompileFunc(StdCompiler *comp);
@ -205,6 +210,8 @@ public:
}; };
typedef EndpointAddress addr_t; typedef EndpointAddress addr_t;
static std::vector<HostAddress> GetLocalAddresses();
// callback class // callback class
class CBClass class CBClass
{ {
@ -266,6 +273,9 @@ public:
protected: protected:
// virtual SOCKET CreateSocket() = 0; // virtual SOCKET CreateSocket() = 0;
// Makes IPv4 connections from an IPv6 socket work.
bool EnableDualStack(SOCKET socket);
// *** errors // *** errors
protected: protected:
StdCopyStrBuf Error; StdCopyStrBuf Error;
@ -532,7 +542,7 @@ private:
#endif #endif
// multicast // multicast
addr_t MCAddr; ip_mreq MCGrpInfo; addr_t MCAddr; ipv6_mreq MCGrpInfo;
bool fMCLoopback; bool fMCLoopback;
// multibind // multibind
@ -616,6 +626,7 @@ protected:
}; };
// packet structures // packet structures
struct BinAddr;
struct PacketHdr; struct TestPacket; struct ConnPacket; struct ConnOKPacket; struct AddAddrPacket; struct PacketHdr; struct TestPacket; struct ConnPacket; struct ConnOKPacket; struct AddAddrPacket;
struct DataPacketHdr; struct CheckPacketHdr; struct ClosePacket; struct DataPacketHdr; struct CheckPacketHdr; struct ClosePacket;
@ -943,26 +954,9 @@ private:
void EnlargeIO(int iBy); void EnlargeIO(int iBy);
}; };
// helpers
// there seems to be no standard way to get these numbers, so let's do it the dirty way...
inline uint8_t &in_addr_b(in_addr &addr, int i)
{
assert(0 <= i && i < 4);
return *(reinterpret_cast<uint8_t *>(&addr.s_addr) + i);
}
inline void CompileFunc(in_addr &ip, StdCompiler *pComp)
{
pComp->Value(in_addr_b(ip, 0)); pComp->Separator(StdCompiler::SEP_PART);
pComp->Value(in_addr_b(ip, 1)); pComp->Separator(StdCompiler::SEP_PART);
pComp->Value(in_addr_b(ip, 2)); pComp->Separator(StdCompiler::SEP_PART);
pComp->Value(in_addr_b(ip, 3));
}
#ifdef HAVE_WINSOCK #ifdef HAVE_WINSOCK
bool AcquireWinSock(); bool AcquireWinSock();
void ReleaseWinSock(); void ReleaseWinSock();
#endif #endif
bool ResolveAddress(const char *szAddress, C4NetIO::addr_t *paddr, uint16_t iPort);
#endif #endif

View File

@ -148,7 +148,7 @@ C4Network2::C4Network2()
fPausedForVote(false), fPausedForVote(false),
iLastOwnVoting(0), iLastOwnVoting(0),
fStreaming(false), fStreaming(false),
NetpuncherGameID(0) NetpuncherGameID(C4NetpuncherID())
{ {
} }
@ -169,7 +169,7 @@ bool C4Network2::InitHost(bool fLobby)
fChasing = false; fChasing = false;
fAllowJoin = false; fAllowJoin = false;
iNextClientID = C4ClientIDStart; iNextClientID = C4ClientIDStart;
NetpuncherGameID = 0; NetpuncherGameID = C4NetpuncherID();
NetpuncherAddr = ::Config.Network.PuncherAddress; NetpuncherAddr = ::Config.Network.PuncherAddress;
// initialize client list // initialize client list
Clients.Init(&Game.Clients, true); Clients.Init(&Game.Clients, true);
@ -310,9 +310,25 @@ C4Network2::InitResult C4Network2::InitClient(const class C4Network2Address *pAd
for (int i = 0; i < iAddrCount; i++) for (int i = 0; i < iAddrCount; i++)
if (!pAddrs[i].isIPNull()) if (!pAddrs[i].isIPNull())
{ {
auto addr = pAddrs[i].getAddr();
std::vector<C4NetIO::addr_t> addrs;
if (addr.IsLocal())
{
// Local IPv6 addresses need a scope id.
for (auto& id : Clients.GetLocal()->getInterfaceIDs())
{
addr.SetScopeId(id);
addrs.push_back(addr);
}
}
else
addrs.push_back(addr);
// connection // connection
if (!NetIO.Connect(pAddrs[i].getAddr(), pAddrs[i].getProtocol(), HostCore, szPassword)) int cnt = 0;
continue; for (auto& a : addrs)
if (NetIO.Connect(a, pAddrs[i].getProtocol(), HostCore, szPassword))
cnt++;
if (cnt == 0) continue;
// format for message // format for message
if (strAddresses.getLength()) if (strAddresses.getLength())
strAddresses.Append(", "); strAddresses.Append(", ");
@ -673,7 +689,7 @@ void C4Network2::Clear()
delete pVoteDialog; pVoteDialog = nullptr; delete pVoteDialog; pVoteDialog = nullptr;
fPausedForVote = false; fPausedForVote = false;
iLastOwnVoting = 0; iLastOwnVoting = 0;
NetpuncherGameID = 0; NetpuncherGameID = C4NetpuncherID();
Votes.Clear(); Votes.Clear();
// don't clear fPasswordNeeded here, it's needed by InitClient // don't clear fPasswordNeeded here, it's needed by InitClient
} }
@ -891,7 +907,7 @@ void C4Network2::HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C
if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient); if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient);
} }
bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt) bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt, C4NetIO::HostAddress::AddressFamily family)
{ {
// TODO: is this all thread-safe? // TODO: is this all thread-safe?
assert(pkt); assert(pkt);
@ -912,26 +928,48 @@ bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt)
case PID_Puncher_AssID: case PID_Puncher_AssID:
if (isHost()) if (isHost())
{ {
NetpuncherGameID = GETPKT(AssID)->GetID(); getNetpuncherGameID(family) = GETPKT(AssID)->GetID();
InvalidateReference(); InvalidateReference();
} }
else else
{ {
// While we don't need the ID as a client, this nicely serves as the signal that we can start using the netpuncher // While we don't need the ID as a client, this nicely serves as the signal that we can start using the netpuncher
if (Status.getState() == GS_Init && getNetpuncherGameID()) if (Status.getState() == GS_Init && getNetpuncherGameID(family))
NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID())); NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID(family)), family);
} }
return true; return true;
default: return false; default: return false;
} }
} }
C4NetpuncherID::value& C4Network2::getNetpuncherGameID(C4NetIO::HostAddress::AddressFamily family)
{
switch (family)
{
case C4NetIO::HostAddress::IPv4: return NetpuncherGameID.v4;
case C4NetIO::HostAddress::IPv6: return NetpuncherGameID.v6;
case C4NetIO::HostAddress::UnknownFamily: assert(!"Unexpected address family");
}
// We need to return a valid reference to satisfy the compiler, even though the code here is unreachable.
return NetpuncherGameID.v4;
}
void C4Network2::InitPuncher() void C4Network2::InitPuncher()
{ {
// We have an internet connection, so let's punch the puncher server here in order to open an udp port // We have an internet connection, so let's punch the puncher server here in order to open an udp port
C4NetIO::addr_t PuncherAddr; C4NetIO::addr_t PuncherAddr;
if (ResolveAddress(getNetpuncherAddr().getData(), &PuncherAddr, C4NetStdPortPuncher)) PuncherAddr.SetAddress(getNetpuncherAddr(), C4NetIO::HostAddress::IPv4);
NetIO.InitPuncher(PuncherAddr); if (!PuncherAddr.IsNull())
{
PuncherAddr.SetDefaultPort(C4NetStdPortPuncher);
NetIO.InitPuncher(PuncherAddr);
}
PuncherAddr.SetAddress(getNetpuncherAddr(), C4NetIO::HostAddress::IPv6);
if (!PuncherAddr.IsNull())
{
PuncherAddr.SetDefaultPort(C4NetStdPortPuncher);
NetIO.InitPuncher(PuncherAddr);
}
} }
void C4Network2::OnGameSynchronized() void C4Network2::OnGameSynchronized()
@ -1198,9 +1236,6 @@ bool C4Network2::CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pC
// check core // check core
if (CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch) if (CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
{ *szReply = "wrong client core"; return false; } { *szReply = "wrong client core"; return false; }
// check address
if (pClient->isConnected() && pClient->getMsgConn()->getPeerAddr() != pConn->getPeerAddr())
{ *szReply = "wrong address"; return false; }
// accept // accept
return true; return true;
} }

View File

@ -194,7 +194,7 @@ protected:
unsigned int iCurrentStreamAmount, iCurrentStreamPosition; unsigned int iCurrentStreamAmount, iCurrentStreamPosition;
// puncher // puncher
C4NetpuncherID_t NetpuncherGameID; C4NetpuncherID NetpuncherGameID;
StdCopyStrBuf NetpuncherAddr; StdCopyStrBuf NetpuncherAddr;
public: public:
@ -257,7 +257,7 @@ public:
void OnDisconn(C4Network2IOConnection *pConn); void OnDisconn(C4Network2IOConnection *pConn);
void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn);
void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn);
bool HandlePuncherPacket(C4NetpuncherPacket::uptr); bool HandlePuncherPacket(C4NetpuncherPacket::uptr, C4NetIO::HostAddress::AddressFamily family);
// runtime join stuff // runtime join stuff
void OnGameSynchronized(); void OnGameSynchronized();
@ -303,7 +303,8 @@ public:
bool StopStreaming(); bool StopStreaming();
// netpuncher // netpuncher
C4NetpuncherID_t getNetpuncherGameID() const { return NetpuncherGameID; } C4NetpuncherID::value& getNetpuncherGameID(C4NetIO::HostAddress::AddressFamily family);
C4NetpuncherID getNetpuncherGameID() const { return NetpuncherGameID; };
StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; } StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; }
protected: protected:

View File

@ -25,16 +25,6 @@
#include "game/C4Game.h" #include "game/C4Game.h"
#include "player/C4PlayerList.h" #include "player/C4PlayerList.h"
#ifdef _WIN32
#include <winsock2.h>
#include <iphlpapi.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif
// *** C4Network2Client // *** C4Network2Client
C4Network2Client::C4Network2Client(C4Client *pClient) C4Network2Client::C4Network2Client(C4Client *pClient)
@ -147,10 +137,22 @@ bool C4Network2Client::DoConnectAttempt(C4Network2IO *pIO)
{ iNextConnAttempt = time(nullptr) + 10; return true; } { iNextConnAttempt = time(nullptr) + 10; return true; }
// save attempt // save attempt
AddrAttempts[iBestAddress]++; iNextConnAttempt = time(nullptr) + C4NetClientConnectInterval; AddrAttempts[iBestAddress]++; iNextConnAttempt = time(nullptr) + C4NetClientConnectInterval;
// log auto addr = Addr[iBestAddress].getAddr();
LogSilentF("Network: connecting client %s on %s...", getName(), Addr[iBestAddress].toString().getData()); std::set<int> interfaceIDs;
// connect if (addr.IsLocal())
return pIO->Connect(Addr[iBestAddress].getAddr(), Addr[iBestAddress].getProtocol(), pClient->getCore()); interfaceIDs = Network.Clients.GetLocal()->getInterfaceIDs();
else
interfaceIDs = {0};
for (auto id : interfaceIDs)
{
addr.SetScopeId(id);
// log
LogSilentF("Network: connecting client %s on %s...", getName(), addr.ToString().getData());
// connect
if (pIO->Connect(addr, Addr[iBestAddress].getProtocol(), pClient->getCore()))
return true;
}
return false;
} }
bool C4Network2Client::hasAddr(const C4Network2Address &addr) const bool C4Network2Client::hasAddr(const C4Network2Address &addr) const
@ -187,80 +189,24 @@ bool C4Network2Client::AddAddr(const C4Network2Address &addr, bool fAnnounce)
void C4Network2Client::AddLocalAddrs(int16_t iPortTCP, int16_t iPortUDP) void C4Network2Client::AddLocalAddrs(int16_t iPortTCP, int16_t iPortUDP)
{ {
// set up address struct
C4NetIO::addr_t addr; C4NetIO::addr_t addr;
// get local address(es) for (auto& ha : C4NetIO::GetLocalAddresses())
#ifdef HAVE_WINSOCK
const size_t BUFFER_SIZE = 16000;
PIP_ADAPTER_ADDRESSES addresses = nullptr;
for (int i = 0; i < 3; ++i)
{ {
addresses = (PIP_ADAPTER_ADDRESSES) realloc(addresses, BUFFER_SIZE * (i+1)); addr.SetAddress(ha);
if (!addresses) if (iPortTCP)
// allocation failed
return;
ULONG bufsz = BUFFER_SIZE * (i+1);
DWORD rv = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER|GAA_FLAG_SKIP_FRIENDLY_NAME,
nullptr, addresses, &bufsz);
if (rv == ERROR_BUFFER_OVERFLOW)
// too little space, try again
continue;
if (rv != NO_ERROR)
{ {
// Something else happened addr.SetPort(iPortTCP);
free(addresses); AddAddr(C4Network2Address(addr, P_TCP), false);
return;
} }
// All okay, add addresses if (iPortUDP)
for (PIP_ADAPTER_ADDRESSES address = addresses; address; address = address->Next)
{ {
for (PIP_ADAPTER_UNICAST_ADDRESS unicast = address->FirstUnicastAddress; unicast; unicast = unicast->Next) addr.SetPort(iPortUDP);
{ AddAddr(C4Network2Address(addr, P_UDP), false);
addr.SetHost(unicast->Address.lpSockaddr);
if (addr.IsLoopback())
continue;
if (iPortTCP)
{
addr.SetPort(iPortTCP);
AddAddr(C4Network2Address(addr, P_TCP), false);
}
if (iPortUDP)
{
addr.SetPort(iPortUDP);
AddAddr(C4Network2Address(addr, P_UDP), false);
}
}
} }
if (addr.GetScopeId())
InterfaceIDs.insert(addr.GetScopeId());
} }
free(addresses);
#else
struct ifaddrs* addrs;
if (getifaddrs(&addrs) < 0)
return;
for (struct ifaddrs* ifaddr = addrs; ifaddr != nullptr; ifaddr = ifaddr->ifa_next)
{
struct sockaddr* ad = ifaddr->ifa_addr;
if (ad == nullptr) continue;
if ((ad->sa_family == AF_INET || ad->sa_family == AF_INET6) && (~ifaddr->ifa_flags & IFF_LOOPBACK)) // Choose only non-loopback IPv4/6 devices
{
addr.SetHost(ad);
if (iPortTCP >= 0)
{
addr.SetPort(iPortTCP);
AddAddr(C4Network2Address(addr, P_TCP), false);
}
if (iPortUDP >= 0)
{
addr.SetPort(iPortUDP);
AddAddr(C4Network2Address(addr, P_UDP), false);
}
}
}
freeifaddrs(addrs);
#endif
} }
void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn) void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn)

View File

@ -57,6 +57,9 @@ protected:
int32_t AddrAttempts[C4ClientMaxAddr]; int32_t AddrAttempts[C4ClientMaxAddr];
int32_t iAddrCnt; int32_t iAddrCnt;
// interface ids
std::set<int> InterfaceIDs;
// status // status
C4Network2ClientStatus eStatus; C4Network2ClientStatus eStatus;
@ -88,6 +91,8 @@ public:
int32_t getAddrCnt() const { return iAddrCnt; } int32_t getAddrCnt() const { return iAddrCnt; }
const C4Network2Address &getAddr(int32_t i) const { return Addr[i]; } const C4Network2Address &getAddr(int32_t i) const { return Addr[i]; }
const std::set<int> &getInterfaceIDs() const { return InterfaceIDs; }
C4Network2ClientStatus getStatus() const { return eStatus; } C4Network2ClientStatus getStatus() const { return eStatus; }
bool hasJoinData() const { return getStatus() != NCS_Joining; } bool hasJoinData() const { return getStatus() != NCS_Joining; }
bool isChasing() const { return getStatus() == NCS_Chasing; } bool isChasing() const { return getStatus() == NCS_Chasing; }

View File

@ -17,6 +17,17 @@
#include "network/C4Network2Discover.h" #include "network/C4Network2Discover.h"
// *** C4Network2IODiscover // *** C4Network2IODiscover
//
// Quick multicast discovery guide by Luchs:
//
// All engines in network mode join a multicast group (defined by C4NetDiscoveryAddress).
//
// Engines searching for a game ("client") send a single byte c = 3 to that multicast group. This
// happens while on the network list on each refresh.
//
// Engines hosting a game (when going into the lobby) send a byte c = 4 plus their reference server
// port to the multicast group. Additionally, they listen for the c = 3 bytes and will reply with
// another multicast answer.
struct C4Network2IODiscoverReply struct C4Network2IODiscoverReply
{ {
@ -53,7 +64,7 @@ bool C4Network2IODiscover::Init(uint16_t iPort)
bool C4Network2IODiscover::Announce() bool C4Network2IODiscover::Announce()
{ {
// Announce our presence // Announce our presence
C4Network2IODiscoverReply Reply = { 4, htons(iRefServerPort) }; C4Network2IODiscoverReply Reply = { 4, iRefServerPort };
return Send(C4NetIOPacket(&Reply, sizeof(Reply), false, DiscoveryAddr)); return Send(C4NetIOPacket(&Reply, sizeof(Reply), false, DiscoveryAddr));
} }

View File

@ -20,7 +20,7 @@
const int C4NetMaxDiscover = 64; const int C4NetMaxDiscover = 64;
const C4NetIO::HostAddress C4NetDiscoveryAddress = C4NetIO::HostAddress(0xef000000); // 239.0.0.0 const C4NetIO::HostAddress C4NetDiscoveryAddress = C4NetIO::HostAddress(StdStrBuf("ff02::1"));
class C4Network2IODiscover : public C4NetIOSimpleUDP, private C4NetIO::CBClass class C4Network2IODiscover : public C4NetIOSimpleUDP, private C4NetIO::CBClass
{ {

View File

@ -460,28 +460,49 @@ bool C4Network2IO::InitPuncher(C4NetIO::addr_t nPuncherAddr)
if (!pNetIO_UDP) if (!pNetIO_UDP)
return false; return false;
// save address // save address
PuncherAddr = nPuncherAddr; switch (nPuncherAddr.GetFamily())
{
case C4NetIO::HostAddress::IPv4:
PuncherAddrIPv4 = nPuncherAddr;
break;
case C4NetIO::HostAddress::IPv6:
PuncherAddrIPv6 = nPuncherAddr;
break;
case C4NetIO::HostAddress::UnknownFamily:
assert(!"Unexpected address family");
}
// let's punch // let's punch
return pNetIO_UDP->Connect(PuncherAddr); return pNetIO_UDP->Connect(nPuncherAddr);
} }
void C4Network2IO::Punch(const C4NetIO::addr_t &punchee_addr) { void C4Network2IO::Punch(const C4NetIO::addr_t &punchee_addr)
{
if (!pNetIO_UDP) if (!pNetIO_UDP)
return; return;
C4PacketPing PktPeng; C4PacketPing PktPeng;
dynamic_cast<C4NetIOUDP*>(pNetIO_UDP)->SendDirect(MkC4NetIOPacket(PID_Pong, PktPeng, punchee_addr)); dynamic_cast<C4NetIOUDP*>(pNetIO_UDP)->SendDirect(MkC4NetIOPacket(PID_Pong, PktPeng, punchee_addr));
} }
void C4Network2IO::SendPuncherPacket(const C4NetpuncherPacket& p) { void C4Network2IO::SendPuncherPacket(const C4NetpuncherPacket& p, C4NetIO::HostAddress::AddressFamily family)
if (!pNetIO_UDP || PuncherAddr.IsNull()) return; {
pNetIO_UDP->Send(p.PackTo(PuncherAddr)); if (!pNetIO_UDP) return;
if (family == C4NetIO::HostAddress::IPv4 && !PuncherAddrIPv4.IsNull())
pNetIO_UDP->Send(p.PackTo(PuncherAddrIPv4));
else if (family == C4NetIO::HostAddress::IPv6 && !PuncherAddrIPv6.IsNull())
pNetIO_UDP->Send(p.PackTo(PuncherAddrIPv6));
}
bool C4Network2IO::IsPuncherAddr(const C4NetIO::addr_t& addr) const
{
return (!PuncherAddrIPv4.IsNull() && PuncherAddrIPv4 == addr)
|| (!PuncherAddrIPv6.IsNull() && PuncherAddrIPv6 == addr);
} }
// C4NetIO interface // C4NetIO interface
bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO)
{ {
// puncher answer? // puncher answer?
if (pNetIO == pNetIO_UDP && !PuncherAddr.IsNull() && PuncherAddr == ConnectAddr) if (pNetIO == pNetIO_UDP && IsPuncherAddr(ConnectAddr))
{ {
// got an address? // got an address?
if (pOwnAddr) if (pOwnAddr)
@ -537,9 +558,12 @@ bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t
void C4Network2IO::OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason) void C4Network2IO::OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason)
{ {
// punch? // punch?
if (pNetIO == pNetIO_UDP && !PuncherAddr.IsNull() && PuncherAddr == addr) if (pNetIO == pNetIO_UDP && IsPuncherAddr(addr))
{ {
PuncherAddr.Clear(); if (PuncherAddrIPv4 == addr)
PuncherAddrIPv4.Clear();
else
PuncherAddrIPv6.Clear();
return; return;
} }
#if(C4NET2IO_DUMP_LEVEL > 1) #if(C4NET2IO_DUMP_LEVEL > 1)
@ -579,7 +603,7 @@ void C4Network2IO::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
C4TimeMilliseconds::Now().AsString().getData(), C4TimeMilliseconds::Now().AsString().getData(),
rPacket.getStatus(), getNetIOName(pNetIO)); rPacket.getStatus(), getNetIOName(pNetIO));
#endif #endif
if (pNetIO == pNetIO_UDP && !PuncherAddr.IsNull() && PuncherAddr == rPacket.getAddr()) if (pNetIO == pNetIO_UDP && IsPuncherAddr(rPacket.getAddr()))
{ {
HandlePuncherPacket(rPacket); HandlePuncherPacket(rPacket);
return; return;
@ -1141,7 +1165,7 @@ void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection
void C4Network2IO::HandlePuncherPacket(const C4NetIOPacket& rPacket) void C4Network2IO::HandlePuncherPacket(const C4NetIOPacket& rPacket)
{ {
auto pkt = C4NetpuncherPacket::Construct(rPacket); auto pkt = C4NetpuncherPacket::Construct(rPacket);
if (pkt && ::Network.HandlePuncherPacket(move(pkt))); if (pkt && ::Network.HandlePuncherPacket(move(pkt), rPacket.getAddr().GetFamily()));
else else
{ {
assert(pNetIO_UDP); assert(pNetIO_UDP);
@ -1268,15 +1292,23 @@ void C4Network2IO::SendConnPackets()
void C4Network2IO::OnPuncherConnect(C4NetIO::addr_t addr) void C4Network2IO::OnPuncherConnect(C4NetIO::addr_t addr)
{ {
// Sanity check // NAT punching is only relevant for IPv4, so convert here to show a proper address.
if (addr.GetFamily() != C4NetIO::HostAddress::IPv4) auto maybe_v4 = addr.AsIPv4();
return; Application.InteractiveThread.ThreadLogS("Adding address from puncher: %s", maybe_v4.ToString().getData());
Application.InteractiveThread.ThreadLogS("Adding address from puncher: %s", addr.ToString().getData());
// Add for local client // Add for local client
C4Network2Client *pLocal = ::Network.Clients.GetLocal(); C4Network2Client *pLocal = ::Network.Clients.GetLocal();
if (pLocal) if (pLocal)
pLocal->AddAddr(C4Network2Address(addr, P_UDP), true); {
pLocal->AddAddr(C4Network2Address(maybe_v4, P_UDP), true);
// If the outside port matches the inside port, there is no port translation and the
// TCP address will probably work as well.
if (addr.GetPort() == Config.Network.PortUDP && Config.Network.PortTCP > 0)
{
maybe_v4.SetPort(Config.Network.PortTCP);
pLocal->AddAddr(C4Network2Address(maybe_v4, P_TCP), true);
}
// Do not ::Network.InvalidateReference(); yet, we're expecting an ID from the netpuncher // Do not ::Network.InvalidateReference(); yet, we're expecting an ID from the netpuncher
}
} }
// *** C4Network2IOConnection // *** C4Network2IOConnection

View File

@ -97,7 +97,8 @@ protected:
iUDPIRate, iUDPORate, iUDPBCRate; iUDPIRate, iUDPORate, iUDPBCRate;
// punching // punching
C4NetIO::addr_t PuncherAddr; C4NetIO::addr_t PuncherAddrIPv4, PuncherAddrIPv6;
bool IsPuncherAddr(const C4NetIO::addr_t& addr) const;
public: public:
@ -137,7 +138,7 @@ public:
// punch // punch
bool InitPuncher(C4NetIO::addr_t PuncherAddr); // by main thread bool InitPuncher(C4NetIO::addr_t PuncherAddr); // by main thread
void SendPuncherPacket(const C4NetpuncherPacket&); void SendPuncherPacket(const C4NetpuncherPacket&, C4NetIO::HostAddress::AddressFamily family);
void Punch(const C4NetIO::addr_t&); // sends a ping packet void Punch(const C4NetIO::addr_t&); // sends a ping packet
// stuff // stuff

View File

@ -318,8 +318,10 @@ bool C4Network2IRCClient::Connect(const char *szServer, const char *szNick, cons
if (!Init()) if (!Init())
return false; return false;
// Resolve address // Resolve address
if (!ResolveAddress(szServer, &ServerAddr, 6666)) ServerAddr.SetAddress(StdStrBuf(szServer));
if (ServerAddr.IsNull())
{ SetError("Could no resolve server address!"); return false; } { SetError("Could no resolve server address!"); return false; }
ServerAddr.SetDefaultPort(6666);
// Set connection data // Set connection data
Nick = szNick; RealName = szRealName; Nick = szNick; RealName = szRealName;
Password = szPassword; AutoJoin = szAutoJoin; Password = szPassword; AutoJoin = szAutoJoin;

View File

@ -30,7 +30,7 @@
C4Network2Reference::C4Network2Reference() C4Network2Reference::C4Network2Reference()
: Icon(0), GameMode(), Time(0), Frame(0), StartTime(0), LeaguePerformance(0), : Icon(0), GameMode(), Time(0), Frame(0), StartTime(0), LeaguePerformance(0),
JoinAllowed(true), ObservingAllowed(true), PasswordNeeded(false), OfficialServer(false), JoinAllowed(true), ObservingAllowed(true), PasswordNeeded(false), OfficialServer(false),
IsEditor(false), iAddrCnt(0), NetpuncherGameID(0) IsEditor(false), iAddrCnt(0), NetpuncherGameID(C4NetpuncherID())
{ {
} }
@ -127,7 +127,7 @@ void C4Network2Reference::CompileFunc(StdCompiler *pComp)
pComp->Value(mkNamingAdapt(Game.sEngineName, "Game", "None")); pComp->Value(mkNamingAdapt(Game.sEngineName, "Game", "None"));
pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Game.iVer,0),"Version" )); pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Game.iVer,0),"Version" ));
pComp->Value(mkNamingAdapt(OfficialServer, "OfficialServer", false)); pComp->Value(mkNamingAdapt(OfficialServer, "OfficialServer", false));
pComp->Value(mkNamingAdapt(NetpuncherGameID, "NetpuncherID", 0, false, false)); pComp->Value(mkNamingAdapt(NetpuncherGameID, "NetpuncherID", C4NetpuncherID(), false, false));
pComp->Value(mkNamingAdapt(NetpuncherAddr, "NetpuncherAddr", "", false, false)); pComp->Value(mkNamingAdapt(NetpuncherAddr, "NetpuncherAddr", "", false, false));
pComp->Value(Parameters); pComp->Value(Parameters);
@ -407,7 +407,7 @@ bool C4Network2HTTPClient::Decompress(StdBuf *pData)
bool C4Network2HTTPClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) bool C4Network2HTTPClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO)
{ {
// Make sure we're actually waiting for this connection // Make sure we're actually waiting for this connection
if (AddrConnect != ServerAddr) if (fConnected || (AddrConnect != ServerAddr && AddrConnect != ServerAddrFallback))
return false; return false;
// Save pack peer address // Save pack peer address
PeerAddr = AddrPeer; PeerAddr = AddrPeer;
@ -445,10 +445,19 @@ void C4Network2HTTPClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO
bool C4Network2HTTPClient::Execute(int iMaxTime) bool C4Network2HTTPClient::Execute(int iMaxTime)
{ {
// Check timeout // Check timeout
if (fBusy && time(nullptr) > iRequestTimeout) if (fBusy)
{ {
Cancel("Request timeout"); if (C4TimeMilliseconds::Now() > HappyEyeballsTimeout)
return true; {
HappyEyeballsTimeout = C4TimeMilliseconds::PositiveInfinity;
Application.InteractiveThread.ThreadLogS("HTTP: Starting fallback connection to %s (%s)", Server.getData(), ServerAddrFallback.ToString().getData());
Connect(ServerAddrFallback);
}
if (time(nullptr) > iRequestTimeout)
{
Cancel("Request timeout");
return true;
}
} }
// Execute normally // Execute normally
return C4NetIOTCP::Execute(iMaxTime); return C4NetIOTCP::Execute(iMaxTime);
@ -462,7 +471,9 @@ C4TimeMilliseconds C4Network2HTTPClient::GetNextTick(C4TimeMilliseconds tNow)
C4TimeMilliseconds tHTTPClientTick = tNow + 1000 * std::max<time_t>(iRequestTimeout - time(nullptr), 0); C4TimeMilliseconds tHTTPClientTick = tNow + 1000 * std::max<time_t>(iRequestTimeout - time(nullptr), 0);
return std::max(tNetIOTCPTick, tHTTPClientTick); C4TimeMilliseconds HappyEyeballsTick = tNow + std::max(HappyEyeballsTimeout - C4TimeMilliseconds::Now(), 0);
return std::min({tNetIOTCPTick, tHTTPClientTick, HappyEyeballsTick});
} }
bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary) bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary)
@ -512,6 +523,11 @@ bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary)
// Start connecting // Start connecting
if (!Connect(ServerAddr)) if (!Connect(ServerAddr))
return false; return false;
// Also try the fallback address after some time (if there is one)
if (!ServerAddrFallback.IsNull())
HappyEyeballsTimeout = C4TimeMilliseconds::Now() + C4Network2HTTPHappyEyeballsTimeout;
else
HappyEyeballsTimeout = C4TimeMilliseconds::PositiveInfinity;
// Okay, request will be performed when connection is complete // Okay, request will be performed when connection is complete
fBusy = true; fBusy = true;
iDataOffset = 0; iDataOffset = 0;
@ -529,7 +545,7 @@ void C4Network2HTTPClient::ResetRequestTimeout()
void C4Network2HTTPClient::Cancel(const char *szReason) void C4Network2HTTPClient::Cancel(const char *szReason)
{ {
// Close connection - and connection attempt // Close connection - and connection attempt
Close(ServerAddr); Close(PeerAddr); Close(ServerAddr); Close(ServerAddrFallback); Close(PeerAddr);
// Reset flags // Reset flags
fBusy = fSuccess = fConnected = fBinary = false; fBusy = fSuccess = fConnected = fBinary = false;
iDownloadedSize = iTotalSize = iDataOffset = 0; iDownloadedSize = iTotalSize = iDataOffset = 0;
@ -566,14 +582,23 @@ bool C4Network2HTTPClient::SetServer(const char *szServerAddress)
SetError(FormatString("Could not resolve server address %s!", Server.getData()).getData()); SetError(FormatString("Could not resolve server address %s!", Server.getData()).getData());
return false; return false;
} }
if (ServerAddr.GetPort() == C4NetIO::EndpointAddress::IPPORT_NONE) ServerAddr.SetDefaultPort(GetDefaultPort());
if (ServerAddr.GetFamily() == C4NetIO::HostAddress::IPv6)
{ {
ServerAddr.SetPort(GetDefaultPort()); // Try to find a fallback IPv4 address for Happy Eyeballs.
ServerAddrFallback.SetAddress(Server, C4NetIO::HostAddress::IPv4);
ServerAddrFallback.SetDefaultPort(GetDefaultPort());
} }
else
ServerAddrFallback.Clear();
// Remove port // Remove port
const char *pColon = strchr(Server.getData(), ':'); const char *firstColon = strchr(Server.getData(), ':');
if (pColon) const char *lastColon = strrchr(Server.getData(), ':');
Server.SetLength(pColon - Server.getData()); if (firstColon)
// hostname/IPv4 address or IPv6 address with port (e.g. [::1]:1234)
if (firstColon == lastColon || (Server[0] == '[' && *(lastColon - 1) == ']'))
Server.SetLength(lastColon - Server.getData());
// Done // Done
ResetError(); ResetError();
return true; return true;

View File

@ -24,6 +24,7 @@
#include "lib/C4InputValidation.h" #include "lib/C4InputValidation.h"
const int C4Network2HTTPQueryTimeout = 10; // (s) const int C4Network2HTTPQueryTimeout = 10; // (s)
const uint32_t C4Network2HTTPHappyEyeballsTimeout = 300; // (ms)
// Session data // Session data
class C4Network2Reference class C4Network2Reference
@ -51,7 +52,7 @@ private:
bool PasswordNeeded; bool PasswordNeeded;
bool OfficialServer; bool OfficialServer;
bool IsEditor; bool IsEditor;
C4NetpuncherID_t NetpuncherGameID; C4NetpuncherID NetpuncherGameID;
StdCopyStrBuf NetpuncherAddr; StdCopyStrBuf NetpuncherAddr;
// Engine information // Engine information
@ -79,7 +80,7 @@ public:
int32_t getStartTime() const { return StartTime; } int32_t getStartTime() const { return StartTime; }
StdStrBuf getGameGoalString() const; StdStrBuf getGameGoalString() const;
bool isEditor() const { return IsEditor; } bool isEditor() const { return IsEditor; }
C4NetpuncherID_t getNetpuncherGameID() const { return NetpuncherGameID; } C4NetpuncherID getNetpuncherGameID() const { return NetpuncherGameID; }
StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; } StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; }
void SetSourceAddress(const C4NetIO::EndpointAddress &ip); void SetSourceAddress(const C4NetIO::EndpointAddress &ip);
@ -129,7 +130,7 @@ public:
private: private:
// Address information // Address information
C4NetIO::addr_t ServerAddr, PeerAddr; C4NetIO::addr_t ServerAddr, ServerAddrFallback, PeerAddr;
StdCopyStrBuf Server, RequestPath; StdCopyStrBuf Server, RequestPath;
bool fBinary; bool fBinary;
@ -137,6 +138,7 @@ private:
size_t iDataOffset; size_t iDataOffset;
StdCopyBuf Request; StdCopyBuf Request;
time_t iRequestTimeout; time_t iRequestTimeout;
C4TimeMilliseconds HappyEyeballsTimeout;
// Response header data // Response header data
size_t iDownloadedSize, iTotalSize; size_t iDownloadedSize, iTotalSize;

View File

@ -158,7 +158,8 @@ bool C4AulDebug::SetAllowed(const char *szHost)
// No host? // No host?
if (!szHost || !*szHost) return true; if (!szHost || !*szHost) return true;
// Resolve the address // Resolve the address
return ResolveAddress(szHost, &AllowedAddr, 0); AllowedAddr.SetAddress(StdStrBuf(szHost));
return !AllowedAddr.IsNull();
} }
bool C4AulDebug::Init(uint16_t iPort) bool C4AulDebug::Init(uint16_t iPort)