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.
ipv6
Lukas Werling 2017-01-10 21:13:01 +01:00
parent fd857ef771
commit 76327b62a7
3 changed files with 110 additions and 87 deletions

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,7 +54,6 @@ int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); }
#endif
#ifdef _MSC_VER
#pragma warning (disable : 4355)
#endif
@ -591,6 +594,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
@ -1802,9 +1865,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)
@ -2260,13 +2323,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;

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;
@ -209,6 +210,8 @@ public:
};
typedef EndpointAddress addr_t;
static std::vector<HostAddress> GetLocalAddresses();
// callback class
class CBClass
{

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)
@ -187,84 +177,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);
}
if (addr.GetScopeId())
InterfaceIDs.insert(addr.GetScopeId());
}
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);
}
if (addr.GetScopeId())
InterfaceIDs.insert(addr.GetScopeId());
}
}
freeifaddrs(addrs);
#endif
}
void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn)