/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2010-2014 QuakeSpasm developers
Copyright (C) 2020-2021 Vittorio Romeo

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "q_stdinc.hpp"
#include "arch_def.hpp"
#include "net_sys.hpp"
#include "quakedef.hpp"
#include "net_defs.hpp"
#include "common.hpp"
#include "console.hpp"
#include "sys.hpp"

#include <cstring>

// QSS

static sys_socket_t net_acceptsocket4 =
    INVALID_SOCKET; // socket for fielding new connections
static sys_socket_t net_controlsocket4;
static sys_socket_t net_broadcastsocket4 = INVALID_SOCKET;
static struct sockaddr_in broadcastaddr4;

static in_addr_t myAddr4;

static sys_socket_t net_acceptsocket6 =
    INVALID_SOCKET; // socket for fielding new connections
static sys_socket_t net_controlsocket6;

static struct in6_addr myAddrv6;

#include "net_udp.hpp"

//=============================================================================

sys_socket_t UDP4_Init()
{
    int err;
    char* tst;
    char buff[MAXHOSTNAMELEN];
    struct hostent* local;
    struct qsockaddr addr;

    if(COM_CheckParm("-noudp") || COM_CheckParm("-noudp4"))
    {
        return INVALID_SOCKET;
    }

    // determine my name & address
    myAddr4 = htonl(INADDR_LOOPBACK);
    if(gethostname(buff, MAXHOSTNAMELEN) != 0)
    {
        err = SOCKETERRNO;
        Con_SafePrintf(
            "UDP4_Init: gethostname failed (%s)\n", socketerror(err));
    }
    else
    {
        buff[MAXHOSTNAMELEN - 1] = 0;
#ifdef PLATFORM_OSX
        // ericw -- if our hostname ends in ".local" (a macOS thing),
        // don't bother calling gethostbyname(), because it blocks for a few
        // seconds and then fails (on my system anyway.)
        tst = strstr(buff, ".local");
        if(tst && tst[6] == '\0')
        {
            Con_SafePrintf("UDP_Init: skipping gethostbyname for %s\n", buff);
        }
        else
#endif
            if(!(local = gethostbyname(buff)))
        {
            Con_SafePrintf(
                "UDP4_Init: gethostbyname failed (%s)\n", hstrerror(h_errno));
        }
        else if(local->h_addrtype != AF_INET)
        {
            Con_SafePrintf("UDP4_Init: address from gethostbyname not IPv4\n");
        }
        else
        {
            myAddr4 = *(in_addr_t*)local->h_addr_list[0];
        }
    }

    if((net_controlsocket4 = UDP4_OpenSocket(0)) == INVALID_SOCKET)
    {
        Con_SafePrintf(
            "UDP4_Init: Unable to open control socket, UDP disabled\n");
        return INVALID_SOCKET;
    }

    broadcastaddr4.sin_family = AF_INET;
    broadcastaddr4.sin_addr.s_addr = INADDR_BROADCAST;
    broadcastaddr4.sin_port = htons((unsigned short)net_hostport);

    UDP_GetSocketAddr(net_controlsocket4, &addr);
    strcpy(my_ipv4_address, UDP_AddrToString(&addr, false));
    tst = strrchr(my_ipv4_address, ':');
    if(tst)
    {
        *tst = 0;
    }

    Con_SafePrintf("UDP4 Initialized\n");
    ipv4Available = true;

    return net_controlsocket4;
}

//=============================================================================

void UDP4_Shutdown()
{
    UDP4_Listen(false);
    UDP_CloseSocket(net_controlsocket4);
}

//=============================================================================

sys_socket_t UDP4_Listen(bool state)
{
    if(state)
    {
        // enable listening
        if(net_acceptsocket4 == INVALID_SOCKET)
        {
            if((net_acceptsocket4 = UDP4_OpenSocket(net_hostport)) ==
                INVALID_SOCKET)
            {
                Sys_Error("UDP4_Listen: Unable to open accept socket");
            }
        }
    }
    else
    {
        // disable listening
        if(net_acceptsocket4 != INVALID_SOCKET)
        {
            UDP_CloseSocket(net_acceptsocket4);
            net_acceptsocket4 = INVALID_SOCKET;
        }
    }
    return net_acceptsocket4;
}

//=============================================================================

sys_socket_t UDP4_OpenSocket(int port)
{
    sys_socket_t newsocket;
    struct sockaddr_in address;
    int _true = 1;
    int err;

    if((newsocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
    {
        err = SOCKETERRNO;
        Con_SafePrintf("UDP4_OpenSocket: %s\n", socketerror(err));
        return INVALID_SOCKET;
    }

    if(ioctlsocket(newsocket, FIONBIO, &_true) == SOCKET_ERROR)
        goto ErrorReturn;

    memset(&address, 0, sizeof(struct sockaddr_in));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons((unsigned short)port);
    if(bind(newsocket, (struct sockaddr*)&address, sizeof(address)) == 0)
    {
        return newsocket;
    }

ErrorReturn:
    err = SOCKETERRNO;
    Con_SafePrintf("UDP4_OpenSocket: %s\n", socketerror(err));
    UDP_CloseSocket(newsocket);
    return INVALID_SOCKET;
}

//=============================================================================

int UDP_CloseSocket(sys_socket_t socketid)
{
    if(socketid == net_broadcastsocket4)
    {
        net_broadcastsocket4 = INVALID_SOCKET;
    }
    return closesocket(socketid);
}

//=============================================================================

/*
============
PartialIPAddress

this lets you type only as much of the net address as required, using
the local network components to fill in the rest
============
*/
static int PartialIPAddress(const char* in, struct qsockaddr* hostaddr)
{
    char buff[256];
    char* b;
    int addr, mask, num, port, run;

    buff[0] = '.';
    b = buff;
    strcpy(buff + 1, in);
    if(buff[1] == '.')
    {
        b++;
    }

    addr = 0;
    mask = -1;
    while(*b == '.')
    {
        b++;
        num = 0;
        run = 0;
        while(!(*b < '0' || *b > '9'))
        {
            num = num * 10 + *b++ - '0';
            if(++run > 3)
            {
                return -1;
            }
        }
        if((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0)
        {
            return -1;
        }
        if(num < 0 || num > 255)
        {
            return -1;
        }
        mask <<= 8;
        addr = (addr << 8) + num;
    }

    if(*b++ == ':')
    {
        port = atoi(b);
    }
    else
    {
        port = net_hostport;
    }

    hostaddr->qsa_family = AF_INET;
    ((struct sockaddr_in*)hostaddr)->sin_port = htons((unsigned short)port);
    ((struct sockaddr_in*)hostaddr)->sin_addr.s_addr =
        (myAddr4 & htonl(mask)) | htonl(addr);

    return 0;
}

//=============================================================================

int UDP_Connect(sys_socket_t socketid, struct qsockaddr* addr)
{
    return 0;
}

//=============================================================================

sys_socket_t UDP4_CheckNewConnections()
{
    int available;
    struct sockaddr_in from;
    socklen_t fromlen;
    char buff[1];

    if(net_acceptsocket4 == INVALID_SOCKET)
    {
        return INVALID_SOCKET;
    }

    if(ioctl(net_acceptsocket4, FIONREAD, &available) == -1)
    {
        int err = SOCKETERRNO;
        Sys_Error("UDP: ioctlsocket (FIONREAD) failed (%s)", socketerror(err));
    }
    if(available)
    {
        return net_acceptsocket4;
    }
    // quietly absorb empty packets
    recvfrom(net_acceptsocket4, buff, 0, 0, (struct sockaddr*)&from, &fromlen);
    return INVALID_SOCKET;
}

//=============================================================================

int UDP_Read(sys_socket_t socketid, byte* buf, int len, struct qsockaddr* addr)
{
    socklen_t addrlen = sizeof(struct qsockaddr);
    int ret;

    ret = recvfrom(socketid, buf, len, 0, (struct sockaddr*)addr, &addrlen);
    if(ret == SOCKET_ERROR)
    {
        int err = SOCKETERRNO;
        if(err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED)
        {
            return 0;
        }
        Con_SafePrintf("UDP_Read, recvfrom: %s\n", socketerror(err));
    }
    return ret;
}

//=============================================================================

static int UDP4_MakeSocketBroadcastCapable(sys_socket_t socketid)
{
    int i = 1;

    // make this socket broadcast capable
    if(setsockopt(socketid, SOL_SOCKET, SO_BROADCAST, (char*)&i, sizeof(i)) ==
        SOCKET_ERROR)
    {
        int err = SOCKETERRNO;
        Con_SafePrintf("UDP, setsockopt: %s\n", socketerror(err));
        return -1;
    }
    net_broadcastsocket4 = socketid;

    return 0;
}

//=============================================================================

int UDP4_Broadcast(sys_socket_t socketid, byte* buf, int len)
{
    int ret;

    if(socketid != net_broadcastsocket4)
    {
        if(net_broadcastsocket4 != INVALID_SOCKET)
        {
            Sys_Error("Attempted to use multiple broadcasts sockets");
        }
        ret = UDP4_MakeSocketBroadcastCapable(socketid);
        if(ret == -1)
        {
            Con_SafePrintf("Unable to make socket broadcast capable\n");
            return ret;
        }
    }

    return UDP_Write(socketid, buf, len, (struct qsockaddr*)&broadcastaddr4);
}

//=============================================================================

int UDP_Write(sys_socket_t socketid, byte* buf, int len, struct qsockaddr* addr)
{
    int ret;
    socklen_t addrsize;
    if(addr->qsa_family == AF_INET)
    {
        addrsize = sizeof(struct sockaddr_in);
    }
    else if(addr->qsa_family == AF_INET6)
    {
        addrsize = sizeof(struct sockaddr_in6);
    }
    else
    {
        Con_SafePrintf("UDP_Write: unknown family\n");
        return -1; // some kind of error. a few systems get pissy if the size
                   // doesn't exactly match the address family
    }

    ret = sendto(socketid, buf, len, 0, (struct sockaddr*)addr, addrsize);
    if(!addr->qsa_family)
    {
        Con_SafePrintf("UDP_Write: family was cleared\n");
    }
    if(ret == SOCKET_ERROR)
    {
        int err = SOCKETERRNO;
        if(err == NET_EWOULDBLOCK)
        {
            return 0;
        }
        if(err == ENETUNREACH)
        {
            Con_SafePrintf("UDP_Write: %s (%s)\n", socketerror(err),
                UDP_AddrToString(addr, false));
        }
        else
        {
            Con_SafePrintf("UDP_Write, sendto: %s\n", socketerror(err));
        }
    }
    return ret;
}

//=============================================================================

const char* UDP_AddrToString(struct qsockaddr* addr, bool masked)
{
    static char buffer[64];

    if(addr->qsa_family == AF_INET)
    {
        int haddr = ntohl(((struct sockaddr_in*)addr)->sin_addr.s_addr);
        q_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d:%d",
            (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff,
            haddr & 0xff, ntohs(((struct sockaddr_in*)addr)->sin_port));
    }
    else if(addr->qsa_family == AF_INET6)
    {
        // evil type punning.
        unsigned short* s =
            (unsigned short*)&((struct sockaddr_in6*)addr)->sin6_addr;
        if(((struct sockaddr_in6*)addr)->sin6_scope_id)
        {
            q_snprintf(buffer, sizeof(buffer),
                "[%x:%x:%x:%x:%x:%x:%x:%x%%%i]:%d", ntohs(s[0]), ntohs(s[1]),
                ntohs(s[2]), ntohs(s[3]), ntohs(s[4]), ntohs(s[5]), ntohs(s[6]),
                ntohs(s[7]), (int)((struct sockaddr_in6*)addr)->sin6_scope_id,
                ntohs(((struct sockaddr_in6*)addr)->sin6_port));
        }
        else
        {
            q_snprintf(buffer, sizeof(buffer), "[%x:%x:%x:%x:%x:%x:%x:%x]:%d",
                ntohs(s[0]), ntohs(s[1]), ntohs(s[2]), ntohs(s[3]), ntohs(s[4]),
                ntohs(s[5]), ntohs(s[6]), ntohs(s[7]),
                ntohs(((struct sockaddr_in6*)addr)->sin6_port));
        }
    }
    else
    {
        strcpy(buffer, "?");
    }
    return buffer;
}

//=============================================================================

int UDP4_StringToAddr(const char* string, struct qsockaddr* addr)
{
    int ha1, ha2, ha3, ha4, hp, ipaddr;

    sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp);
    ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4;

    addr->qsa_family = AF_INET;
    ((struct sockaddr_in*)addr)->sin_addr.s_addr = htonl(ipaddr);
    ((struct sockaddr_in*)addr)->sin_port = htons((unsigned short)hp);
    return 0;
}

//=============================================================================

int UDP_GetSocketAddr(sys_socket_t socketid, struct qsockaddr* addr)
{
    socklen_t addrlen = sizeof(struct qsockaddr);
    in_addr_t a;

    memset(addr, 0, sizeof(struct qsockaddr));
    if(getsockname(socketid, (struct sockaddr*)addr, &addrlen) != 0)
    {
        return -1;
    }

    if(addr->qsa_family == AF_INET)
    {
        a = ((struct sockaddr_in*)addr)->sin_addr.s_addr;
        if(a == 0 || a == htonl(INADDR_LOOPBACK))
        {
            ((struct sockaddr_in*)addr)->sin_addr.s_addr = myAddr4;
        }
    }
    else if(addr->qsa_family == AF_INET6)
    {
        static const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
        if(!memcmp(&((struct sockaddr_in6*)addr)->sin6_addr, &in6addr_any,
               sizeof(in6addr_any)))
        {
            memcpy(&((struct sockaddr_in6*)addr)->sin6_addr, &myAddrv6,
                sizeof(((struct sockaddr_in6*)addr)->sin6_addr));
        }
    }

    return 0;
}

//=============================================================================

int UDP_GetNameFromAddr(struct qsockaddr* addr, char* name)
{
    if(addr->qsa_family == AF_INET)
    {
        struct hostent* hostentry;

        hostentry = gethostbyaddr((char*)&((struct sockaddr_in*)addr)->sin_addr,
            sizeof(struct in_addr), AF_INET);
        if(hostentry)
        {
            strncpy(name, (char*)hostentry->h_name, NET_NAMELEN - 1);
            return 0;
        }
    }
    else if(addr->qsa_family == AF_INET6)
    {
        // meh, don't bother, its unreliable anyway.
    }

    strcpy(name, UDP_AddrToString(addr, false));
    return 0;
}

//=============================================================================

int UDP4_GetAddrFromName(const char* name, struct qsockaddr* addr)
{
    struct hostent* hostentry;
    const char* colon;
    unsigned short port = net_hostport;

    if(name[0] >= '0' && name[0] <= '9')
    {
        return PartialIPAddress(name, addr);
    }

    colon = std::strrchr(name, ':');
    if(colon)
    {
        char dupe[MAXHOSTNAMELEN];
        if(colon - name + 1 > MAXHOSTNAMELEN)
        {
            return -1;
        }
        memcpy(dupe, name, colon - name);
        dupe[colon - name] = 0;
        hostentry = gethostbyname(dupe);
        port = strtoul(colon + 1, nullptr, 10);
    }
    else
    {
        hostentry = gethostbyname(name);
    }
    if(!hostentry || hostentry->h_addrtype != AF_INET)
    {
        return -1;
    }

    addr->qsa_family = AF_INET;
    ((struct sockaddr_in*)addr)->sin_port = htons(port);
    ((struct sockaddr_in*)addr)->sin_addr.s_addr =
        *(in_addr_t*)hostentry->h_addr_list[0];

    return 0;
}

//=============================================================================

int UDP_AddrCompare(struct qsockaddr* addr1, struct qsockaddr* addr2)
{
    if(addr1->qsa_family != addr2->qsa_family)
    {
        return -1;
    }

    if(addr1->qsa_family == AF_INET)
    {
        if(((struct sockaddr_in*)addr1)->sin_addr.s_addr !=
            ((struct sockaddr_in*)addr2)->sin_addr.s_addr)
        {
            return -1;
        }

        if(((struct sockaddr_in*)addr1)->sin_port !=
            ((struct sockaddr_in*)addr2)->sin_port)
        {
            return 1;
        }

        return 0;
    }
    else if(addr1->qsa_family == AF_INET6)
    {
        if(memcmp(&((struct sockaddr_in6*)addr1)->sin6_addr,
               &((struct sockaddr_in6*)addr2)->sin6_addr,
               sizeof(((struct sockaddr_in6*)addr1)->sin6_addr)))
        {
            return -1;
        }

        if(((struct sockaddr_in6*)addr1)->sin6_port !=
            ((struct sockaddr_in6*)addr2)->sin6_port)
        {
            return 1;
        }

        if(((struct sockaddr_in6*)addr1)->sin6_scope_id &&
            ((struct sockaddr_in6*)addr2)->sin6_scope_id &&
            ((struct sockaddr_in6*)addr1)->sin6_scope_id !=
                ((struct sockaddr_in6*)addr2)->sin6_scope_id)
        { // the ipv6 scope id is for use with
            // link-local addresses, to identify the
            // specific interface.
            return 1;
        }

        return 0;
    }
    else
    {
        return -1;
    }
}

//=============================================================================

int UDP_GetSocketPort(struct qsockaddr* addr)
{
    if(addr->qsa_family == AF_INET)
    {
        return ntohs(((struct sockaddr_in*)addr)->sin_port);
    }
    else if(addr->qsa_family == AF_INET6)
    {
        return ntohs(((struct sockaddr_in6*)addr)->sin6_port);
    }
    else
    {
        return -1;
    }
}


int UDP_SetSocketPort(struct qsockaddr* addr, int port)
{
    if(addr->qsa_family == AF_INET)
    {
        ((struct sockaddr_in*)addr)->sin_port = htons((unsigned short)port);
    }
    else if(addr->qsa_family == AF_INET6)
    {
        ((struct sockaddr_in6*)addr)->sin6_port = htons((unsigned short)port);
    }
    else
    {
        return -1;
    }
    return 0;
}

//=============================================================================



sys_socket_t UDP6_Init()
{
    char* colon;
    struct qsockaddr addr;

    if(COM_CheckParm("-noudp") || COM_CheckParm("-noudp6"))
    {
        return INVALID_SOCKET;
    }

    // TODO: determine my name & address

    if((net_controlsocket6 = UDP6_OpenSocket(0)) == INVALID_SOCKET)
    {
        Con_SafePrintf(
            "UDP6_Init: Unable to open control socket, UDPv6 disabled\n");
        return INVALID_SOCKET;
    }

    UDP_GetSocketAddr(net_controlsocket6, &addr);
    strcpy(my_ipv6_address, UDP_AddrToString(&addr, false));
    colon = strrchr(my_ipv6_address, ':');
    if(colon)
    {
        *colon = 0;
    }

    Con_SafePrintf("UDPv6 Initialized\n");
    ipv6Available = true;

    return net_controlsocket6;
}

//=============================================================================

void UDP6_Shutdown()
{
    UDP6_Listen(false);
    UDP_CloseSocket(net_controlsocket6);
}

//=============================================================================

sys_socket_t UDP6_Listen(bool state)
{
    if(state)
    {
        // enable listening
        if(net_acceptsocket6 == INVALID_SOCKET)
        {
            if((net_acceptsocket6 = UDP6_OpenSocket(net_hostport)) ==
                INVALID_SOCKET)
            {
                Sys_Error("UDP6_Listen: Unable to open accept socket");
            }
        }
    }
    else
    {
        // disable listening
        if(net_acceptsocket6 != INVALID_SOCKET)
        {
            UDP_CloseSocket(net_acceptsocket6);
            net_acceptsocket6 = INVALID_SOCKET;
        }
    }
    return net_acceptsocket6;
}

//=============================================================================

sys_socket_t UDP6_OpenSocket(int port)
{
    sys_socket_t newsocket;
    struct sockaddr_in6 address;
    int _true = 1;
    int err;

    if((newsocket = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) ==
        INVALID_SOCKET)
    {
        err = SOCKETERRNO;
        Con_SafePrintf("UDP6_OpenSocket: %s\n", socketerror(err));
        return INVALID_SOCKET;
    }

    setsockopt(
        newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&_true, sizeof(_true));

    if(ioctlsocket(newsocket, FIONBIO, &_true) == SOCKET_ERROR)
        goto ErrorReturn;

    memset(&address, 0, sizeof(struct sockaddr_in6));
    address.sin6_family = AF_INET6;
    memset(&address.sin6_addr, 0, sizeof(address.sin6_addr));
    address.sin6_port = htons((unsigned short)port);
    if(bind(newsocket, (struct sockaddr*)&address, sizeof(address)) == 0)
    {
        // we don't know if we're the server or not. oh well.
        struct ipv6_mreq req;
        memset(&req, 0, sizeof(req));
        req.ipv6mr_multiaddr.s6_addr[0] = 0xff;
        req.ipv6mr_multiaddr.s6_addr[1] = 0x03;
        req.ipv6mr_multiaddr.s6_addr[15] = 0x01;
        req.ipv6mr_interface = 0;
        setsockopt(
            newsocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&req, sizeof(req));

        return newsocket;
    }

ErrorReturn:
    err = SOCKETERRNO;
    Con_SafePrintf("UDP6_OpenSocket: %s\n", socketerror(err));
    UDP_CloseSocket(newsocket);
    return INVALID_SOCKET;
}

//=============================================================================

sys_socket_t UDP6_CheckNewConnections()
{
    int available;
    struct sockaddr_in from;
    socklen_t fromlen;
    char buff[1];

    if(net_acceptsocket6 == INVALID_SOCKET)
    {
        return INVALID_SOCKET;
    }

    if(ioctl(net_acceptsocket6, FIONREAD, &available) == -1)
    {
        int err = SOCKETERRNO;
        Sys_Error("UDP6: ioctlsocket (FIONREAD) failed (%s)", socketerror(err));
    }
    if(available)
    {
        return net_acceptsocket6;
    }
    // quietly absorb empty packets
    fromlen = sizeof(from);
    recvfrom(net_acceptsocket6, buff, 0, 0, (struct sockaddr*)&from, &fromlen);
    return INVALID_SOCKET;
}

//=============================================================================

int UDP6_Broadcast(sys_socket_t socketid, byte* buf, int len)
{
    struct sockaddr_in6 address;

    memset(&address, 0, sizeof(struct sockaddr_in6));
    address.sin6_family = AF_INET6;
    memset(&address.sin6_addr, 0, sizeof(address.sin6_addr));
    address.sin6_addr.s6_addr[0] = 0xff;
    address.sin6_addr.s6_addr[1] = 0x03;
    address.sin6_addr.s6_addr[15] = 0x1;
    address.sin6_port = htons((unsigned short)net_hostport);

    return UDP_Write(socketid, buf, len, (struct qsockaddr*)&address);
}

//=============================================================================

int UDP6_StringToAddr(const char* string, struct qsockaddr* addr)
{ // This is never actually called...
    Con_SafePrintf("UDP6_StringToAddr: %s\n", string);
    return UDP6_GetAddrFromName(string, addr);
}

//=============================================================================

int UDP6_GetAddrFromName(const char* name, struct qsockaddr* addr)
{
    struct addrinfo* addrinfo = nullptr;
    struct addrinfo* pos;
    struct addrinfo udp6hint;
    int error;
    const char* port;
    char dupbase[256];
    size_t len;
    bool success = false;

    memset(&udp6hint, 0, sizeof(udp6hint));
    udp6hint.ai_family = 0; // Any... we check for AF_INET6 or 4
    udp6hint.ai_socktype = SOCK_DGRAM;
    udp6hint.ai_protocol = IPPROTO_UDP;

    if(*name == '[')
    {
        port = std::strstr(name, "]");
        if(!port)
        {
            error = EAI_NONAME;
        }
        else
        {
            len = port - (name + 1);
            if(len >= sizeof(dupbase))
            {
                len = sizeof(dupbase) - 1;
            }
            strncpy(dupbase, name + 1, len);
            dupbase[len] = '\0';
            error = getaddrinfo(dupbase, (port[1] == ':') ? port + 2 : nullptr,
                &udp6hint, &addrinfo);
        }
    }
    else
    {
        port = std::strrchr(name, ':');

        if(port)
        {
            len = port - name;
            if(len >= sizeof(dupbase))
            {
                len = sizeof(dupbase) - 1;
            }
            strncpy(dupbase, name, len);
            dupbase[len] = '\0';
            error = getaddrinfo(dupbase, port + 1, &udp6hint, &addrinfo);
        }
        else
        {
            error = EAI_NONAME;
        }
        if(error)
        { // failed, try string with no port.
            error = getaddrinfo(name, nullptr, &udp6hint,
                &addrinfo); // remember, this func will return any address
        }
        // family that could be using the udp protocol...
        // (ip4 or ip6)
    }

    if(!error)
    {
        ((struct sockaddr*)addr)->sa_family = 0;
        for(pos = addrinfo; pos; pos = pos->ai_next)
        {
            if(0) // pos->ai_family == AF_INET)
            {
                memcpy(addr, pos->ai_addr, pos->ai_addrlen);
                success = true;
                break;
            }
            if(pos->ai_family == AF_INET6 && !success)
            {
                memcpy(addr, pos->ai_addr, pos->ai_addrlen);
                success = true;
            }
        }
        freeaddrinfo(addrinfo);
    }

    if(success)
    {
        if(((struct sockaddr*)addr)->sa_family == AF_INET)
        {
            if(!((struct sockaddr_in*)addr)->sin_port)
            {
                ((struct sockaddr_in*)addr)->sin_port = htons(net_hostport);
            }
        }
        else if(((struct sockaddr*)addr)->sa_family == AF_INET6)
        {
            if(!((struct sockaddr_in6*)addr)->sin6_port)
            {
                ((struct sockaddr_in6*)addr)->sin6_port = htons(net_hostport);
            }
        }
        return 0;
    }
    return -1;
}

//=============================================================================

#ifdef __linux__ // sadly there is no posix standard for querying all ipv4+ipv6
                 // addresses.
#include <ifaddrs.h>
static struct ifaddrs* iflist;
double iftime; // requery sometimes.
static int UDP_GetAddresses(qhostaddr_t* addresses, int maxaddresses, int fam)
{
    struct ifaddrs* ifa;
    int result = 0;
    double time = Sys_DoubleTime();
    if(time - iftime > 1 && iflist)
    {
        freeifaddrs(iflist);
        iflist = nullptr;
    }
    if(!iflist)
    {
        iftime = time;
        getifaddrs(&iflist);
    }

    for(ifa = iflist; ifa && result < maxaddresses; ifa = ifa->ifa_next)
    {
        // can happen if the interface is not bound.
        if(ifa->ifa_addr == nullptr) continue;
        if(fam == ifa->ifa_addr->sa_family)
            q_strlcpy(addresses[result++],
                UDP_AddrToString((struct qsockaddr*)ifa->ifa_addr, false),
                sizeof(addresses[0]));
    }
    return result;
}
#else
// for other systems, like macs, where we don't know how to query this stuff
// properly.
// FIXME: there is a posix standard for ipv4 at least.
static int UDP_GetAddresses(qhostaddr_t* addresses, int maxaddresses, int fam)
{
    return 0;
}
#endif
int UDP4_GetAddresses(qhostaddr_t* addresses, int maxaddresses)
{
    return UDP_GetAddresses(addresses, maxaddresses, AF_INET);
}
int UDP6_GetAddresses(qhostaddr_t* addresses, int maxaddresses)
{
    return UDP_GetAddresses(addresses, maxaddresses, AF_INET6);
}
// ---
