From: molivier Date: Thu, 14 May 2009 05:40:38 +0000 (+0000) Subject: Fixed IPv6 support, including the addition of the master server messages "getserversE... X-Git-Tag: xonotic-v0.1.0preview~1662 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=commitdiff_plain;h=3601d63f7b0038c6e2009ecf0857ad9d1a8d5252 Fixed IPv6 support, including the addition of the master server messages "getserversExt" and "getserversExtResponse" git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8956 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/lhnet.c b/lhnet.c index 37d9c620..9026aca2 100644 --- a/lhnet.c +++ b/lhnet.c @@ -21,6 +21,7 @@ #include #include #include +#include #endif #ifdef __MORPHOS__ @@ -126,39 +127,122 @@ int LHNETADDRESS_FromPort(lhnetaddress_t *vaddress, lhnetaddresstype_t addressty return 0; } +int LHNETADDRESS_Resolve(lhnetaddressnative_t *address, const char *name, int port) +{ + char port_buff [16]; + struct addrinfo hints; + struct addrinfo* addrinf; + int err; + + dpsnprintf (port_buff, sizeof (port_buff), "%d", port); + port_buff[sizeof (port_buff) - 1] = '\0'; + + memset(&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + //hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo(name, port_buff, &hints, &addrinf); + if (err != 0 || addrinf == NULL) + return 0; + if (addrinf->ai_addr->sa_family != AF_INET6 && addrinf->ai_addr->sa_family != AF_INET) + return 0; + + // great it worked + if (addrinf->ai_addr->sa_family == AF_INET6) + { + address->addresstype = LHNETADDRESSTYPE_INET6; + memcpy(&address->addr.in6, addrinf->ai_addr, sizeof(address->addr.in6)); + } + else + { + address->addresstype = LHNETADDRESSTYPE_INET4; + memcpy(&address->addr.in, addrinf->ai_addr, sizeof(address->addr.in)); + } + address->port = port; + + freeaddrinfo (addrinf); + return 1; +} + int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; - int i, port, namelen, d1, d2, d3, d4; - struct hostent *hostentry; + int i, port, d1, d2, d3, d4, resolved; + size_t namelen; unsigned char *a; - const char *colon; char name[128]; #ifdef STANDALONETEST char string2[128]; #endif + const char* addr_start; + const char* addr_end = NULL; + const char* port_name = NULL; + int addr_family = AF_UNSPEC; + if (!address || !string || !*string) return 0; memset(address, 0, sizeof(*address)); address->addresstype = LHNETADDRESSTYPE_NONE; port = 0; - colon = strrchr(string, ':'); - if (colon) - port = atoi(colon + 1); + + // If it's a bracketed IPv6 address + if (string[0] == '[') + { + const char* end_bracket = strchr(string, ']'); + + if (end_bracket == NULL) + return 0; + + if (end_bracket[1] == ':') + port_name = end_bracket + 2; + else if (end_bracket[1] != '\0') + return 0; + + addr_family = AF_INET6; + addr_start = &string[1]; + addr_end = end_bracket; + } else - colon = string + strlen(string); - if (port == 0) - port = defaultport; - namelen = colon - string; - if (namelen > 127) - namelen = 127; - if (string[0] == '[' && namelen > 0 && string[namelen-1] == ']') // ipv6 { - string++; - namelen -= 2; + const char* first_colon; + + addr_start = string; + + // If it's a numeric non-bracket IPv6 address (-> no port), + // or it's a numeric IPv4 address, or a name, with a port + first_colon = strchr(string, ':'); + if (first_colon != NULL) + { + const char* last_colon = strrchr(first_colon + 1, ':'); + + // If it's an numeric IPv4 address, or a name, with a port + if (last_colon == NULL) + { + addr_end = first_colon; + port_name = first_colon + 1; + } + else + addr_family = AF_INET6; + } } - memcpy(name, string, namelen); + + if (addr_end != NULL) + namelen = addr_end - addr_start; + else + namelen = strlen (addr_start); + + if (namelen >= sizeof(name)) + namelen = sizeof(name) - 1; + memcpy (name, addr_start, namelen); name[namelen] = 0; + + if (port_name) + port = atoi(port_name); + + if (port == 0) + port = defaultport; + // handle loopback if (!strcmp(name, "local")) { @@ -172,7 +256,8 @@ int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int de #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif - if (sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) + if (addr_family != AF_INET6 && + sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) { // parsed a valid ipv4 address address->addresstype = LHNETADDRESSTYPE_INET4; @@ -213,67 +298,48 @@ int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int de } return 0; } - // try gethostbyname (handles dns and other ip formats) - hostentry = gethostbyname(name); - if (hostentry) - { - if (hostentry->h_addrtype == AF_INET6) - { - // great it worked - address->addresstype = LHNETADDRESSTYPE_INET6; - address->port = port; - address->addr.in6.sin6_family = hostentry->h_addrtype; - address->addr.in6.sin6_port = htons((unsigned short)port); - memcpy(&address->addr.in6.sin6_addr, hostentry->h_addr_list[0], sizeof(address->addr.in6.sin6_addr)); - for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) - namecache[namecacheposition].name[i] = name[i]; - namecache[namecacheposition].name[i] = 0; + + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; #ifndef STANDALONETEST - namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours #endif - namecache[namecacheposition].address = *address; - namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; + + // try resolving the address (handles dns and other ip formats) + resolved = LHNETADDRESS_Resolve(address, name, port); + if (resolved) + { #ifdef STANDALONETEST - LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); - printf("gethostbyname(\"%s\") returned ipv6 address %s\n", string, string2); -#endif - return 1; - } - else if (hostentry->h_addrtype == AF_INET) + const char *protoname; + + switch (address->addresstype) { - // great it worked - address->addresstype = LHNETADDRESSTYPE_INET4; - address->port = port; - address->addr.in.sin_family = hostentry->h_addrtype; - address->addr.in.sin_port = htons((unsigned short)port); - memcpy(&address->addr.in.sin_addr, hostentry->h_addr_list[0], sizeof(address->addr.in.sin_addr)); - for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) - namecache[namecacheposition].name[i] = name[i]; - namecache[namecacheposition].name[i] = 0; -#ifndef STANDALONETEST - namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours -#endif - namecache[namecacheposition].address = *address; - namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; -#ifdef STANDALONETEST - LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); - printf("gethostbyname(\"%s\") returned ipv4 address %s\n", string, string2); -#endif - return 1; + case LHNETADDRESSTYPE_INET6: + protoname = "ipv6"; + break; + case LHNETADDRESSTYPE_INET4: + protoname = "ipv4"; + break; + default: + protoname = "UNKNOWN"; + break; } + LHNETADDRESS_ToString(vaddress, string2, sizeof(string2), 1); + Con_Printf("LHNETADDRESS_Resolve(\"%s\") returned %s address %s\n", string, protoname, string2); +#endif + namecache[namecacheposition].address = *address; } + else + { #ifdef STANDALONETEST - printf("gethostbyname failed on address \"%s\"\n", name); + printf("name resolution failed on address \"%s\"\n", name); #endif - for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) - namecache[namecacheposition].name[i] = name[i]; - namecache[namecacheposition].name[i] = 0; -#ifndef STANDALONETEST - namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours -#endif - namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; + namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; + } + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; - return 0; + return resolved; } int LHNETADDRESS_ToString(const lhnetaddress_t *vaddress, char *string, int stringbuffersize, int includeport) @@ -355,6 +421,35 @@ int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address) return LHNETADDRESSTYPE_NONE; } +const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *vaddress) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + + if (address && address->addresstype == LHNETADDRESSTYPE_INET6) + { +#ifndef _WIN32 + + static char ifname [IF_NAMESIZE]; + + if (if_indextoname(address->addr.in6.sin6_scope_id, ifname) == ifname) + return ifname; + +#else + + // The Win32 API doesn't have if_indextoname() until Windows Vista, + // but luckily it just uses the interface ID as the interface name + + static char ifname [16]; + + if (dpsnprintf(ifname, sizeof(ifname), "%lu", address->addr.in6.sin6_scope_id) > 0) + return ifname; + +#endif + } + + return NULL; +} + int LHNETADDRESS_GetPort(const lhnetaddress_t *address) { if (!address) @@ -618,40 +713,61 @@ lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address) #endif if (ioctlsocket(lhnetsocket->inetsocket, FIONBIO, &_true) != -1) { - lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; - SOCKLEN_T namelen; - int bindresult; - if (address->addresstype == LHNETADDRESSTYPE_INET6) - { - namelen = sizeof(localaddress->addr.in6); - bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); - if (bindresult != -1) - getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); - } - else +#ifdef IPV6_V6ONLY + // We need to set this flag to tell the OS that we only listen on IPv6. If we don't + // most OSes will create a dual-protocol socket that also listens on IPv4. In this case + // if an IPv4 socket is already bound to the port we want, our bind() call will fail. + int ipv6_only = 1; + if (address->addresstype != LHNETADDRESSTYPE_INET6 + || setsockopt (lhnetsocket->inetsocket, IPPROTO_IPV6, IPV6_V6ONLY, + (const void *)&ipv6_only, sizeof(ipv6_only)) == 0 +#ifdef WIN32 + // The Win32 API only supports IPV6_V6ONLY since Windows Vista, but fortunately + // the default value is what we want on Win32 anyway (IPV6_V6ONLY = true) + || SOCKETERRNO == WSAENOPROTOOPT +#endif + ) +#endif { - namelen = sizeof(localaddress->addr.in); - bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; + SOCKLEN_T namelen; + int bindresult; + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { + namelen = sizeof(localaddress->addr.in6); + bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + if (bindresult != -1) + getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); + } + else + { + namelen = sizeof(localaddress->addr.in); + bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + if (bindresult != -1) + getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); + } if (bindresult != -1) - getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); - } - if (bindresult != -1) - { - int i = 1; - // enable broadcast on this socket - setsockopt(lhnetsocket->inetsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)); - lhnetsocket->next = &lhnet_socketlist; - lhnetsocket->prev = lhnetsocket->next->prev; - lhnetsocket->next->prev = lhnetsocket; - lhnetsocket->prev->next = lhnetsocket; + { + int i = 1; + // enable broadcast on this socket + setsockopt(lhnetsocket->inetsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)); + lhnetsocket->next = &lhnet_socketlist; + lhnetsocket->prev = lhnetsocket->next->prev; + lhnetsocket->next->prev = lhnetsocket; + lhnetsocket->prev->next = lhnetsocket; #ifdef WIN32 - if (ioctlsocket(lhnetsocket->inetsocket, SIO_UDP_CONNRESET, &_false) == -1) - Con_DPrintf("LHNET_OpenSocket_Connectionless: ioctlsocket SIO_UDP_CONNRESET returned error: %s\n", LHNETPRIVATE_StrError()); + if (ioctlsocket(lhnetsocket->inetsocket, SIO_UDP_CONNRESET, &_false) == -1) + Con_DPrintf("LHNET_OpenSocket_Connectionless: ioctlsocket SIO_UDP_CONNRESET returned error: %s\n", LHNETPRIVATE_StrError()); #endif - return lhnetsocket; + return lhnetsocket; + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: bind returned error: %s\n", LHNETPRIVATE_StrError()); } +#ifdef IPV6_V6ONLY else - Con_Printf("LHNET_OpenSocket_Connectionless: bind returned error: %s\n", LHNETPRIVATE_StrError()); + Con_Printf("LHNET_OpenSocket_Connectionless: setsockopt(IPV6_V6ONLY) returned error: %s\n", LHNETPRIVATE_StrError()); +#endif } else Con_Printf("LHNET_OpenSocket_Connectionless: ioctlsocket returned error: %s\n", LHNETPRIVATE_StrError()); diff --git a/lhnet.h b/lhnet.h index 08850fb3..701045e3 100644 --- a/lhnet.h +++ b/lhnet.h @@ -25,6 +25,7 @@ int LHNETADDRESS_FromPort(lhnetaddress_t *address, lhnetaddresstype_t addresstyp int LHNETADDRESS_FromString(lhnetaddress_t *address, const char *string, int defaultport); int LHNETADDRESS_ToString(const lhnetaddress_t *address, char *string, int stringbuffersize, int includeport); int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address); +const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *address); int LHNETADDRESS_GetPort(const lhnetaddress_t *address); int LHNETADDRESS_SetPort(lhnetaddress_t *address, int port); int LHNETADDRESS_Compare(const lhnetaddress_t *address1, const lhnetaddress_t *address2); diff --git a/netconn.c b/netconn.c index 99b84e81..b124b432 100755 --- a/netconn.c +++ b/netconn.c @@ -127,7 +127,7 @@ mempool_t *netconn_mempool = NULL; cvar_t cl_netport = {0, "cl_port", "0", "forces client to use chosen port number if not 0"}; cvar_t sv_netport = {0, "port", "26000", "server port for players to connect to"}; cvar_t net_address = {0, "net_address", "0.0.0.0", "network address to open ports on"}; -//cvar_t net_netaddress_ipv6 = {0, "net_address_ipv6", "[0:0:0:0:0:0:0:0]", "network address to open ipv6 ports on"}; +cvar_t net_address_ipv6 = {0, "net_address_ipv6", "[0:0:0:0:0:0:0:0]", "network address to open ipv6 ports on"}; char net_extresponse[NET_EXTRESPONSE_MAX][1400]; int net_extresponse_count = 0; @@ -863,7 +863,7 @@ void NetConn_OpenClientPorts(void) Con_Printf("Client using port %i\n", port); NetConn_OpenClientPort("local:2", 0); NetConn_OpenClientPort(net_address.string, port); - //NetConn_OpenClientPort(net_address_ipv6.string, port); + NetConn_OpenClientPort(net_address_ipv6.string, port); } void NetConn_CloseServerPorts(void) @@ -922,7 +922,7 @@ void NetConn_OpenServerPorts(int opennetports) if (opennetports) { NetConn_OpenServerPort(net_address.string, port); - //NetConn_OpenServerPort(net_address_ipv6.string, port); + NetConn_OpenServerPort(net_address_ipv6.string, port); } if (sv_numsockets == 0) Host_Error("NetConn_OpenServerPorts: unable to open any ports!"); @@ -1412,6 +1412,79 @@ static qboolean NetConn_ClientParsePacket_ServerList_PrepareQuery( int protocol, return true; } +static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *senderaddress, const unsigned char *data, int length, qboolean isextended) +{ + masterreplycount++; + if (serverlist_consoleoutput) + Con_Printf("received DarkPlaces %sserver list...\n", isextended ? "extended " : ""); + while (length >= 7) + { + char ipstring [128]; + + // IPv4 address + if (data[0] == '\\') + { + unsigned short port = data[5] * 256 + data[6]; + + if (port != 0 && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF)) + dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%hu", data[1], data[2], data[3], data[4], port); + + // move on to next address in packet + data += 7; + length -= 7; + } + // IPv6 address + else if (data[0] == '/' && isextended && length >= 19) + { + unsigned short port = data[17] * 256 + data[18]; + + if (port != 0) + { + const char *ifname; + + // TODO: make some basic checks of the IP address (broadcast, ...) + + ifname = LHNETADDRESS_GetInterfaceName(senderaddress); + if (ifname != NULL) + { + dpsnprintf (ipstring, sizeof (ipstring), "[%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x%%%s]:%hu", + data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], + data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], + ifname, port); + } + else + { + dpsnprintf (ipstring, sizeof (ipstring), "[%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x]:%hu", + data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], + data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], + port); + } + } + + // move on to next address in packet + data += 19; + length -= 19; + } + else + { + Con_Print("Error while parsing the server list\n"); + break; + } + + if (serverlist_consoleoutput && developer_networking.integer) + Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring); + + if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, ipstring, false ) ) { + break; + } + + } + + // begin or resume serverlist queries + serverlist_querysleep = false; + serverlist_querywaittime = realtime + 3; +} + static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) { qboolean fromserver; @@ -1572,26 +1645,15 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat // Extract the IP addresses data += 18; length -= 18; - masterreplycount++; - if (serverlist_consoleoutput) - Con_Print("received DarkPlaces server list...\n"); - while (length >= 7 && data[0] == '\\' && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF) && data[5] * 256 + data[6] != 0) - { - dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%u", data[1], data[2], data[3], data[4], data[5] * 256 + data[6]); - if (serverlist_consoleoutput && developer_networking.integer) - Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring); - - if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, ipstring, false ) ) { - break; - } - - // move on to next address in packet - data += 7; - length -= 7; - } - // begin or resume serverlist queries - serverlist_querysleep = false; - serverlist_querywaittime = realtime + 3; + NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, false); + return true; + } + if (!strncmp(string, "getserversExtResponse", 21) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 21; + length -= 21; + NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, true); return true; } if (!memcmp(string, "d\n", 2) && serverlist_cachecount < SERVERLIST_TOTALSIZE) @@ -2813,6 +2875,7 @@ void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) { if (cl_sockets[i]) { + const char *cmdname, *extraoptions; int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) @@ -2833,7 +2896,17 @@ void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) } // build the getservers message to send to the dpmaster master servers - dpsnprintf(request, sizeof(request), "\377\377\377\377getservers %s %u empty full\x0A", gamename, NET_PROTOCOL_VERSION); + if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == LHNETADDRESSTYPE_INET6) + { + cmdname = "getserversExt"; + extraoptions = " ipv4 ipv6"; // ask for IPv4 and IPv6 servers + } + else + { + cmdname = "getservers"; + extraoptions = ""; + } + dpsnprintf(request, sizeof(request), "\377\377\377\377%s %s %u empty full%s", cmdname, gamename, NET_PROTOCOL_VERSION, extraoptions); // search internet for (masternum = 0;sv_masters[masternum].name;masternum++) @@ -3046,7 +3119,7 @@ void NetConn_Init(void) Cvar_RegisterVariable(&cl_netport); Cvar_RegisterVariable(&sv_netport); Cvar_RegisterVariable(&net_address); - //Cvar_RegisterVariable(&net_address_ipv6); + Cvar_RegisterVariable(&net_address_ipv6); Cvar_RegisterVariable(&sv_public); Cvar_RegisterVariable(&sv_heartbeatperiod); for (i = 0;sv_masters[i].name;i++) diff --git a/netconn.h b/netconn.h index 74b59a0b..aacbcd7e 100755 --- a/netconn.h +++ b/netconn.h @@ -371,7 +371,7 @@ extern cvar_t cl_netlocalping; extern cvar_t cl_netport; extern cvar_t sv_netport; extern cvar_t net_address; -//extern cvar_t net_netaddress_ipv6; +extern cvar_t net_address_ipv6; qboolean NetConn_CanSend(netconn_t *conn); int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, qboolean quakesignon_suppressreliables);