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);
return hash<decltype(unpack)>()(unpack);
}
default:
case C4NetIO::HostAddress::UnknownFamily:
assert(!"Unexpected address family");
return 0;
}
}

View File

@ -18,6 +18,11 @@
#include "network/C4Network2Address.h"
#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) {
if (!rpack.getPData()) return nullptr;
try {

View File

@ -27,7 +27,14 @@ enum C4NetpuncherPacketType {
// 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 {
public:
@ -38,7 +45,7 @@ public:
C4NetIOPacket PackTo(const C4NetIO::addr_t&) const;
protected:
virtual StdBuf PackInto() const = 0;
typedef C4NetpuncherID_t CID;
typedef C4NetpuncherID::value CID;
};
template<C4NetpuncherPacketType TYPE>

View File

@ -29,7 +29,7 @@
class C4PuncherServer : public C4NetIOUDP, private C4NetIO::CBClass
{
public:
typedef C4NetpuncherID_t CID;
typedef C4NetpuncherID::value CID;
C4PuncherServer() {
C4NetIOUDP::SetCallback(this);
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);
// Execute forever
Puncher.ExecuteUntil(-1);
for (;;)
{
Puncher.ExecuteUntil(-1);
fprintf(stderr, "ERROR: %s\n", Puncher.GetError());
}
return 0;
}

View File

@ -31,6 +31,8 @@
#include <process.h>
#include <share.h>
#include <winsock2.h>
#include <iphlpapi.h>
typedef int socklen_t;
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 <netdb.h>
#include <stdlib.h>
#include <ifaddrs.h>
#include <net/if.h>
#define ioctlsocket ioctl
#define closesocket close
@ -50,11 +54,16 @@ int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); }
#endif
#ifdef _MSC_VER
#pragma warning (disable : 4355)
#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
const int C4NetIO::TO_INF = -1;
@ -244,6 +253,16 @@ bool C4NetIO::HostAddress::IsLoopback() const
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)
{
if (gen.sa_family != AF_INET6) return;
@ -280,11 +299,27 @@ C4NetIO::HostAddress C4NetIO::HostAddress::AsIPv6() const
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
{
return EndpointAddress(HostAddress::AsIPv6(), GetPort());
}
C4NetIO::EndpointAddress C4NetIO::EndpointAddress::AsIPv4() const
{
return EndpointAddress(HostAddress::AsIPv4(), GetPort());
}
void C4NetIO::HostAddress::SetHost(const sockaddr *addr)
{
// 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));
}
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();
@ -402,6 +449,7 @@ void C4NetIO::EndpointAddress::SetAddress(const StdStrBuf &addr)
}
addrinfo hints = addrinfo();
hints.ai_family = family;
//hints.ai_flags = AI_NUMERICHOST;
addrinfo *addresses = nullptr;
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
{
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
// 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)
{
fSockErr &= HaveSocketError();
@ -1310,6 +1435,8 @@ bool C4NetIOTCP::Listen(uint16_t inListenPort)
SetError("socket creation failed", true);
return false;
}
if (!EnableDualStack(lsock))
return false;
// To be able to reuse the port after close
#if !defined(_DEBUG) && !defined(_WIN32)
int reuseaddr = 1;
@ -1682,6 +1809,9 @@ bool C4NetIOSimpleUDP::Init(uint16_t inPort)
return false;
}
if (!EnableDualStack(sock))
return false;
// set reuse socket option
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();
// 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;
}
if (pBroadcastAddr->GetPort() != iPort)
@ -1769,8 +1899,8 @@ bool C4NetIOSimpleUDP::InitBroadcast(addr_t *pBroadcastAddr)
}
// set mc ttl to somewhat about "same net"
int iTTL = 16;
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, reinterpret_cast<char*>(&iTTL), sizeof(iTTL)) == SOCKET_ERROR)
int TTL = 16;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, reinterpret_cast<char*>(&TTL), sizeof(TTL)) == SOCKET_ERROR)
{
SetError("could not set mc ttl", true);
return false;
@ -1778,11 +1908,12 @@ bool C4NetIOSimpleUDP::InitBroadcast(addr_t *pBroadcastAddr)
// set up multicast group information
this->MCAddr = *pBroadcastAddr;
MCGrpInfo.imr_multiaddr = static_cast<sockaddr_in*>(&MCAddr)->sin_addr;
MCGrpInfo.imr_interface.s_addr = INADDR_ANY;
MCGrpInfo.ipv6mr_multiaddr = static_cast<sockaddr_in6>(MCAddr).sin6_addr;
// TODO: do multicast on all interfaces?
MCGrpInfo.ipv6mr_interface = 0; // default interface
// 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)
{
SetError("could not join multicast group"); // to do: more error information
@ -1844,10 +1975,10 @@ bool C4NetIOSimpleUDP::CloseBroadcast()
if (!fMultiCast) return true;
// 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)
{
SetError("could not join multicast group"); // to do: more error information
SetError("could not leave multicast group"); // to do: more error information
return false;
}
@ -2049,10 +2180,10 @@ enum C4NetIOSimpleUDP::WaitResult C4NetIOSimpleUDP::WaitForSocket(int iTimeout)
bool C4NetIOSimpleUDP::SetMCLoopback(int fLoopback)
{
// 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
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;
fMCLoopback = !! fLoopback;
return true;
@ -2086,6 +2217,76 @@ const unsigned int C4NetIOUDP::iUDPHeaderSize = 8 + 24; // (bytes)
#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
struct C4NetIOUDP::PacketHdr
{
@ -2096,20 +2297,20 @@ struct C4NetIOUDP::PacketHdr
struct C4NetIOUDP::ConnPacket : public PacketHdr
{
uint32_t ProtocolVer;
C4NetIO::addr_t Addr;
C4NetIO::addr_t MCAddr;
BinAddr Addr;
BinAddr MCAddr;
};
struct C4NetIOUDP::ConnOKPacket : public PacketHdr
{
enum { MCM_NoMC, MCM_MC, MCM_MCOK } MCMode;
C4NetIO::addr_t Addr;
BinAddr Addr;
};
struct C4NetIOUDP::AddAddrPacket : public PacketHdr
{
C4NetIO::addr_t Addr;
C4NetIO::addr_t NewAddr;
BinAddr Addr;
BinAddr NewAddr;
};
struct C4NetIOUDP::DataPacketHdr : public PacketHdr
@ -2126,7 +2327,7 @@ struct C4NetIOUDP::CheckPacketHdr : 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");
return false;
}
// set up adress
MCAddr.SetAddress(addr_t::AnyIPv4, iPort);
// search for a free one
// Set up address as unicast-prefix-based IPv6 multicast address (RFC 3306).
sockaddr_in6 saddrgen = sockaddr_in6();
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--)
{
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
MCAddr.SetAddress(C4NetIO::HostAddress(0x000000ef | (UnsyncedRandom(0x1000000) << 8)));
MCAddr.SetAddress((sockaddr*) &saddrgen);
MCAddr.SetPort(iPort);
// init broadcast
if (!C4NetIOSimpleUDP::InitBroadcast(&MCAddr))
return false;
@ -2977,14 +3205,16 @@ void C4NetIOUDP::Peer::OnRecv(const C4NetIOPacket &rPacket) // (mt-safe)
iLastPacketAsked = iLastMCPacketAsked = 0;
// Activate Multicast?
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)
pParent->fDelayedLoopbackTest = true;
if (!pParent->InitBroadcast(&MCAddr))
pParent->fDelayedLoopbackTest = false;
}
}
// build ConnOk Packet
ConnOKPacket nPack;
@ -3158,7 +3388,7 @@ bool C4NetIOUDP::Peer::DoConn(bool fMC) // (mt-safe)
if (pParent->fMultiCast)
Pkt.MCAddr = pParent->C4NetIOSimpleUDP::getMCAddr();
else
Pkt.MCAddr.Clear();
Pkt.MCAddr = C4NetIO::addr_t();
return SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt), false, addr));
}
@ -3693,50 +3923,3 @@ void C4NetIOMan::EnlargeIO(int iBy)
delete[] ppNetIO;
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); }
explicit HostAddress(uint32_t addr) { SetHost(addr); }
HostAddress(const StdStrBuf &addr) { SetHost(addr); }
HostAddress(const sockaddr *addr) { SetHost(addr); }
AddressFamily GetFamily() const;
@ -102,15 +103,17 @@ public:
void SetHost(const sockaddr *addr);
void SetHost(const HostAddress &host);
void SetHost(SpecialAddress host);
void SetHost(const StdStrBuf &host);
void SetHost(const StdStrBuf &host, AddressFamily family = UnknownFamily);
void SetHost(uint32_t host);
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
bool IsNull() const;
bool IsMulticast() const;
bool IsLoopback() const;
bool IsLocal() const;
// bool IsBroadcast() const;
StdStrBuf ToString(int flags = 0) const;
@ -146,12 +149,14 @@ public:
void SetAddress(const EndpointAddress &other);
void SetAddress(HostAddress::SpecialAddress addr, 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
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 SetDefaultPort(uint16_t port); // set a port only if there is none
uint16_t GetPort() const;
bool IsNull() const;
@ -192,8 +197,8 @@ public:
// conversions
operator sockaddr() const { return gen; }
/* 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_in() const { assert(gen.sa_family == AF_INET); return v4; }
operator sockaddr_in6() const { assert(gen.sa_family == AF_INET6); return v6; }
// StdCompiler
void CompileFunc(StdCompiler *comp);
@ -205,6 +210,8 @@ public:
};
typedef EndpointAddress addr_t;
static std::vector<HostAddress> GetLocalAddresses();
// callback class
class CBClass
{
@ -266,6 +273,9 @@ public:
protected:
// virtual SOCKET CreateSocket() = 0;
// Makes IPv4 connections from an IPv6 socket work.
bool EnableDualStack(SOCKET socket);
// *** errors
protected:
StdCopyStrBuf Error;
@ -532,7 +542,7 @@ private:
#endif
// multicast
addr_t MCAddr; ip_mreq MCGrpInfo;
addr_t MCAddr; ipv6_mreq MCGrpInfo;
bool fMCLoopback;
// multibind
@ -616,6 +626,7 @@ protected:
};
// packet structures
struct BinAddr;
struct PacketHdr; struct TestPacket; struct ConnPacket; struct ConnOKPacket; struct AddAddrPacket;
struct DataPacketHdr; struct CheckPacketHdr; struct ClosePacket;
@ -943,26 +954,9 @@ private:
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
bool AcquireWinSock();
void ReleaseWinSock();
#endif
bool ResolveAddress(const char *szAddress, C4NetIO::addr_t *paddr, uint16_t iPort);
#endif

View File

@ -148,7 +148,7 @@ C4Network2::C4Network2()
fPausedForVote(false),
iLastOwnVoting(0),
fStreaming(false),
NetpuncherGameID(0)
NetpuncherGameID(C4NetpuncherID())
{
}
@ -169,7 +169,7 @@ bool C4Network2::InitHost(bool fLobby)
fChasing = false;
fAllowJoin = false;
iNextClientID = C4ClientIDStart;
NetpuncherGameID = 0;
NetpuncherGameID = C4NetpuncherID();
NetpuncherAddr = ::Config.Network.PuncherAddress;
// initialize client list
Clients.Init(&Game.Clients, true);
@ -310,9 +310,25 @@ C4Network2::InitResult C4Network2::InitClient(const class C4Network2Address *pAd
for (int i = 0; i < iAddrCount; i++)
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
if (!NetIO.Connect(pAddrs[i].getAddr(), pAddrs[i].getProtocol(), HostCore, szPassword))
continue;
int cnt = 0;
for (auto& a : addrs)
if (NetIO.Connect(a, pAddrs[i].getProtocol(), HostCore, szPassword))
cnt++;
if (cnt == 0) continue;
// format for message
if (strAddresses.getLength())
strAddresses.Append(", ");
@ -673,7 +689,7 @@ void C4Network2::Clear()
delete pVoteDialog; pVoteDialog = nullptr;
fPausedForVote = false;
iLastOwnVoting = 0;
NetpuncherGameID = 0;
NetpuncherGameID = C4NetpuncherID();
Votes.Clear();
// 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);
}
bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt)
bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt, C4NetIO::HostAddress::AddressFamily family)
{
// TODO: is this all thread-safe?
assert(pkt);
@ -912,26 +928,48 @@ bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt)
case PID_Puncher_AssID:
if (isHost())
{
NetpuncherGameID = GETPKT(AssID)->GetID();
getNetpuncherGameID(family) = GETPKT(AssID)->GetID();
InvalidateReference();
}
else
{
// 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())
NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID()));
if (Status.getState() == GS_Init && getNetpuncherGameID(family))
NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID(family)), family);
}
return true;
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()
{
// We have an internet connection, so let's punch the puncher server here in order to open an udp port
C4NetIO::addr_t PuncherAddr;
if (ResolveAddress(getNetpuncherAddr().getData(), &PuncherAddr, C4NetStdPortPuncher))
NetIO.InitPuncher(PuncherAddr);
PuncherAddr.SetAddress(getNetpuncherAddr(), C4NetIO::HostAddress::IPv4);
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()
@ -1198,9 +1236,6 @@ bool C4Network2::CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pC
// check core
if (CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
{ *szReply = "wrong client core"; return false; }
// check address
if (pClient->isConnected() && pClient->getMsgConn()->getPeerAddr() != pConn->getPeerAddr())
{ *szReply = "wrong address"; return false; }
// accept
return true;
}

View File

@ -194,7 +194,7 @@ protected:
unsigned int iCurrentStreamAmount, iCurrentStreamPosition;
// puncher
C4NetpuncherID_t NetpuncherGameID;
C4NetpuncherID NetpuncherGameID;
StdCopyStrBuf NetpuncherAddr;
public:
@ -257,7 +257,7 @@ public:
void OnDisconn(C4Network2IOConnection *pConn);
void HandlePacket(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
void OnGameSynchronized();
@ -303,7 +303,8 @@ public:
bool StopStreaming();
// netpuncher
C4NetpuncherID_t getNetpuncherGameID() const { return NetpuncherGameID; }
C4NetpuncherID::value& getNetpuncherGameID(C4NetIO::HostAddress::AddressFamily family);
C4NetpuncherID getNetpuncherGameID() const { return NetpuncherGameID; };
StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; }
protected:

View File

@ -25,16 +25,6 @@
#include "game/C4Game.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(C4Client *pClient)
@ -147,10 +137,22 @@ bool C4Network2Client::DoConnectAttempt(C4Network2IO *pIO)
{ iNextConnAttempt = time(nullptr) + 10; return true; }
// save attempt
AddrAttempts[iBestAddress]++; iNextConnAttempt = time(nullptr) + C4NetClientConnectInterval;
// log
LogSilentF("Network: connecting client %s on %s...", getName(), Addr[iBestAddress].toString().getData());
// connect
return pIO->Connect(Addr[iBestAddress].getAddr(), Addr[iBestAddress].getProtocol(), pClient->getCore());
auto addr = Addr[iBestAddress].getAddr();
std::set<int> interfaceIDs;
if (addr.IsLocal())
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
@ -187,80 +189,24 @@ bool C4Network2Client::AddAddr(const C4Network2Address &addr, bool fAnnounce)
void C4Network2Client::AddLocalAddrs(int16_t iPortTCP, int16_t iPortUDP)
{
// set up address struct
C4NetIO::addr_t addr;
// get local address(es)
#ifdef HAVE_WINSOCK
const size_t BUFFER_SIZE = 16000;
PIP_ADAPTER_ADDRESSES addresses = nullptr;
for (int i = 0; i < 3; ++i)
for (auto& ha : C4NetIO::GetLocalAddresses())
{
addresses = (PIP_ADAPTER_ADDRESSES) realloc(addresses, BUFFER_SIZE * (i+1));
if (!addresses)
// 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)
addr.SetAddress(ha);
if (iPortTCP)
{
// Something else happened
free(addresses);
return;
addr.SetPort(iPortTCP);
AddAddr(C4Network2Address(addr, P_TCP), false);
}
// All okay, add addresses
for (PIP_ADAPTER_ADDRESSES address = addresses; address; address = address->Next)
if (iPortUDP)
{
for (PIP_ADAPTER_UNICAST_ADDRESS unicast = address->FirstUnicastAddress; unicast; unicast = unicast->Next)
{
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);
}
}
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)

View File

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

View File

@ -17,6 +17,17 @@
#include "network/C4Network2Discover.h"
// *** 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
{
@ -53,7 +64,7 @@ bool C4Network2IODiscover::Init(uint16_t iPort)
bool C4Network2IODiscover::Announce()
{
// Announce our presence
C4Network2IODiscoverReply Reply = { 4, htons(iRefServerPort) };
C4Network2IODiscoverReply Reply = { 4, iRefServerPort };
return Send(C4NetIOPacket(&Reply, sizeof(Reply), false, DiscoveryAddr));
}

View File

@ -20,7 +20,7 @@
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
{

View File

@ -460,28 +460,49 @@ bool C4Network2IO::InitPuncher(C4NetIO::addr_t nPuncherAddr)
if (!pNetIO_UDP)
return false;
// 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
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)
return;
C4PacketPing PktPeng;
dynamic_cast<C4NetIOUDP*>(pNetIO_UDP)->SendDirect(MkC4NetIOPacket(PID_Pong, PktPeng, punchee_addr));
}
void C4Network2IO::SendPuncherPacket(const C4NetpuncherPacket& p) {
if (!pNetIO_UDP || PuncherAddr.IsNull()) return;
pNetIO_UDP->Send(p.PackTo(PuncherAddr));
void C4Network2IO::SendPuncherPacket(const C4NetpuncherPacket& p, C4NetIO::HostAddress::AddressFamily family)
{
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
bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO)
{
// puncher answer?
if (pNetIO == pNetIO_UDP && !PuncherAddr.IsNull() && PuncherAddr == ConnectAddr)
if (pNetIO == pNetIO_UDP && IsPuncherAddr(ConnectAddr))
{
// got an address?
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)
{
// 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;
}
#if(C4NET2IO_DUMP_LEVEL > 1)
@ -579,7 +603,7 @@ void C4Network2IO::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
C4TimeMilliseconds::Now().AsString().getData(),
rPacket.getStatus(), getNetIOName(pNetIO));
#endif
if (pNetIO == pNetIO_UDP && !PuncherAddr.IsNull() && PuncherAddr == rPacket.getAddr())
if (pNetIO == pNetIO_UDP && IsPuncherAddr(rPacket.getAddr()))
{
HandlePuncherPacket(rPacket);
return;
@ -1141,7 +1165,7 @@ void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection
void C4Network2IO::HandlePuncherPacket(const C4NetIOPacket& rPacket)
{
auto pkt = C4NetpuncherPacket::Construct(rPacket);
if (pkt && ::Network.HandlePuncherPacket(move(pkt)));
if (pkt && ::Network.HandlePuncherPacket(move(pkt), rPacket.getAddr().GetFamily()));
else
{
assert(pNetIO_UDP);
@ -1268,15 +1292,23 @@ void C4Network2IO::SendConnPackets()
void C4Network2IO::OnPuncherConnect(C4NetIO::addr_t addr)
{
// Sanity check
if (addr.GetFamily() != C4NetIO::HostAddress::IPv4)
return;
Application.InteractiveThread.ThreadLogS("Adding address from puncher: %s", addr.ToString().getData());
// NAT punching is only relevant for IPv4, so convert here to show a proper address.
auto maybe_v4 = addr.AsIPv4();
Application.InteractiveThread.ThreadLogS("Adding address from puncher: %s", maybe_v4.ToString().getData());
// Add for local client
C4Network2Client *pLocal = ::Network.Clients.GetLocal();
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
}
}
// *** C4Network2IOConnection

View File

@ -97,7 +97,8 @@ protected:
iUDPIRate, iUDPORate, iUDPBCRate;
// punching
C4NetIO::addr_t PuncherAddr;
C4NetIO::addr_t PuncherAddrIPv4, PuncherAddrIPv6;
bool IsPuncherAddr(const C4NetIO::addr_t& addr) const;
public:
@ -137,7 +138,7 @@ public:
// punch
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
// stuff

View File

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

View File

@ -30,7 +30,7 @@
C4Network2Reference::C4Network2Reference()
: Icon(0), GameMode(), Time(0), Frame(0), StartTime(0), LeaguePerformance(0),
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(mkArrayAdaptDM(Game.iVer,0),"Version" ));
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(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)
{
// Make sure we're actually waiting for this connection
if (AddrConnect != ServerAddr)
if (fConnected || (AddrConnect != ServerAddr && AddrConnect != ServerAddrFallback))
return false;
// Save pack peer address
PeerAddr = AddrPeer;
@ -445,10 +445,19 @@ void C4Network2HTTPClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO
bool C4Network2HTTPClient::Execute(int iMaxTime)
{
// Check timeout
if (fBusy && time(nullptr) > iRequestTimeout)
if (fBusy)
{
Cancel("Request timeout");
return true;
if (C4TimeMilliseconds::Now() > HappyEyeballsTimeout)
{
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
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);
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)
@ -512,6 +523,11 @@ bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary)
// Start connecting
if (!Connect(ServerAddr))
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
fBusy = true;
iDataOffset = 0;
@ -529,7 +545,7 @@ void C4Network2HTTPClient::ResetRequestTimeout()
void C4Network2HTTPClient::Cancel(const char *szReason)
{
// Close connection - and connection attempt
Close(ServerAddr); Close(PeerAddr);
Close(ServerAddr); Close(ServerAddrFallback); Close(PeerAddr);
// Reset flags
fBusy = fSuccess = fConnected = fBinary = false;
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());
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
const char *pColon = strchr(Server.getData(), ':');
if (pColon)
Server.SetLength(pColon - Server.getData());
const char *firstColon = strchr(Server.getData(), ':');
const char *lastColon = strrchr(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
ResetError();
return true;

View File

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

View File

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