summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2014-11-05 01:20:39 +0300
committerAndrey Nazarov <skuller@skuller.net>2014-12-01 23:04:15 +0300
commit3798d858a5f64c17dd31bf34b28b3cc3f868e4b9 (patch)
tree3914e99166adda02e1624a071b8d2dfc21801aeb
parent6ed835d943063ea4a81071e7b7d8dae50ff700ba (diff)
Add experimental IPv6 support.
Enabled by default when resolving addresses, but IPv4 is prioritized over IPv6. Listening for incoming IPv6 connections disabled by default. No IPv6 multicast support yet, overcomplicated for no good reason.
-rw-r--r--doc/client.txt34
-rw-r--r--doc/server.txt28
-rw-r--r--inc/common/net/net.h7
-rw-r--r--src/client/main.c2
-rw-r--r--src/client/ui/servers.c1
-rw-r--r--src/common/net/net.c851
-rw-r--r--src/common/net/unix.h120
-rw-r--r--src/common/net/win.h28
8 files changed, 716 insertions, 355 deletions
diff --git a/doc/client.txt b/doc/client.txt
index f1f13cd..d9f9dba 100644
--- a/doc/client.txt
+++ b/doc/client.txt
@@ -114,15 +114,29 @@ cl_updaterate::
Network
~~~~~~~
+net_enable_ipv6::
+ Enables IPv6 support. Default value is 1 on systems that support IPv6 and 0
+ otherwise.
+ - 0 — disable IPv6, use IPv4 only
+ - 1 — enable IPv6, but prefer IPv4 over IPv6 when resolving host names
+ with multiple addresses
+ - 2 — enable IPv6, use normal address resolver priority configured by OS
+
net_ip::
- Specifies network interface address client should use for outgoing
- connections. Default value is empty, which means that default network
- interface is used.
+ Specifies network interface address client should use for outgoing UDP
+ connections using IPv4. Default value is empty, which means that default
+ network interface is used.
+
+net_ip6::
+ Specifies network interface address client should use for outgoing UDP
+ connections using IPv6. Default value is empty, which means that default
+ network interface is used. Has no effect unless ‘net_enable_ipv6’ is set to
+ non-zero value.
net_clientport::
- Specifies UDP port number client should use for outgoing connections.
- Default value is -1, which means that random port number is chosen at
- socket creation time.
+ Specifies UDP port number client should use for outgoing connections (using
+ IPv4 or IPv6). Default value is -1, which means that random port number is
+ chosen at socket creation time.
net_maxmsglen::
Specifies maximum server to client packet size client will request from
@@ -130,10 +144,10 @@ net_maxmsglen::
It is nice to have this variable as close to your network link MTU as
possible (accounting for headers). Thus for normal Ethernet MTU of 1500
bytes 1462 can be specified (10 bytes quake header, 8 bytes UDP header, 20
- bytes IP header). Higher values may cause IP fragmentation which is better
- to avoid. Servers will cap this variable to their own maximum values.
- Please don't change this variable unless you know exactly what you are
- doing.
+ bytes IPv4 header). Higher values may cause IP fragmentation which is
+ better to avoid. Servers will cap this variable to their own maximum
+ values. Please don't change this variable unless you know exactly what you
+ are doing.
net_chantype::
Specifies if enhanced Q2PRO network channel implementation is enabled when
diff --git a/doc/server.txt b/doc/server.txt
index 4a3290e..f8b1796 100644
--- a/doc/server.txt
+++ b/doc/server.txt
@@ -16,11 +16,20 @@ Variables
Network
~~~~~~~
+net_enable_ipv6::
+ Enables IPv6 support. Default value is 1 on systems that support IPv6 and 0
+ otherwise.
+ - 0 — disable IPv6, use IPv4 only
+ - 1 — enable IPv6, but do not listen for incoming IPv6 connections and
+ prefer IPv4 over IPv6 when resolving host names with multiple addresses
+ - 2 — enable IPv6, listen for incoming IPv6 connections and use normal
+ address resolver priority configured by OS
+
net_ip::
Specifies network interface address server should listen on for UDP and TCP
- connections. The same interface is also used for outgoing TCP connections
- (these include MVD/GTV and anticheat connections). Default value is empty,
- which means listening on all interfaces.
+ connections using IPv4. The same interface is also used for outgoing TCP
+ connections (these include MVD/GTV and anticheat connections). Default
+ value is empty, which means listening on all interfaces.
NOTE: There is a limitation preventing anticheat to work correctly on servers
accessible from multiple IP addresses. If you are running the server on multi-ip
@@ -28,9 +37,16 @@ system and plan to use anticheat, you need to explicitly bind the server to
one of your network interfaces using the ‘net_ip’ cvar, otherwise expect any
kinds of problems.
+net_ip6::
+ Specifies network interface address server should listen on for UDP and TCP
+ connections using IPv6. The same interface is also used for outgoing TCP
+ connections (these include MVD/GTV connections). Default value is empty,
+ which means listening on all interfaces. Has no effect unless
+ ‘net_enable_ipv6’ is set to non-zero value.
+
net_port::
- Specifies port number server should listen on for UDP and TCP connections.
- Default value is 27910.
+ Specifies port number server should listen on for UDP and TCP connections
+ (using IPv4 or IPv6). Default value is 27910.
net_ignore_icmp::
On Win32 and Linux, server is able to receive ICMP
@@ -44,7 +60,7 @@ net_maxmsglen::
server. 0 means no hard limit. Default value is conservative 1390 bytes. It
is nice to have this variable as close to your network link MTU as possible
(accounting for headers). Thus for normal Ethernet MTU of 1500 bytes 1462
- can be specified (10 bytes quake header, 8 bytes UDP header, 20 bytes IP
+ can be specified (10 bytes quake header, 8 bytes UDP header, 20 bytes IPv4
header). Higher values may cause IP fragmentation for misconfigured clients
which is better to avoid. Please don't change this variable unless you know
exactly what you are doing.
diff --git a/inc/common/net/net.h b/inc/common/net/net.h
index d687120..589f8c6 100644
--- a/inc/common/net/net.h
+++ b/inc/common/net/net.h
@@ -62,7 +62,7 @@ typedef struct {
} ioentry_t;
typedef enum {
- NA_BAD,
+ NA_UNSPECIFIED,
NA_LOOPBACK,
NA_BROADCAST,
NA_IP,
@@ -92,6 +92,7 @@ typedef struct netadr_s {
netadrtype_t type;
netadrip_t ip;
uint16_t port;
+ uint32_t scope_id; // IPv6 crap
} netadr_t;
typedef enum netstate_e {
@@ -240,6 +241,10 @@ qboolean NET_SendPacket(netsrc_t sock, const void *data,
char *NET_AdrToString(const netadr_t *a);
qboolean NET_StringToAdr(const char *s, netadr_t *a, int default_port);
+qboolean NET_StringPairToAdr(const char *host, const char *port, netadr_t *a);
+
+char *NET_BaseAdrToString(const netadr_t *a);
+#define NET_StringToBaseAdr(s, a) NET_StringPairToAdr(s, NULL, a)
const char *NET_ErrorString(void);
diff --git a/src/client/main.c b/src/client/main.c
index 1190130..e4e3d3d 100644
--- a/src/client/main.c
+++ b/src/client/main.c
@@ -1130,7 +1130,7 @@ static void CL_Reconnect_f(void)
}
// issued manually at console
- if (cls.serverAddress.type == NA_BAD) {
+ if (cls.serverAddress.type == NA_UNSPECIFIED) {
Com_Printf("No server to reconnect to.\n");
return;
}
diff --git a/src/client/ui/servers.c b/src/client/ui/servers.c
index 1ff97e0..793f6c1 100644
--- a/src/client/ui/servers.c
+++ b/src/client/ui/servers.c
@@ -508,6 +508,7 @@ static void ParseBinary(void *data, size_t len, size_t chunk)
if (!data)
return;
+ memset(&address, 0, sizeof(address));
address.type = NA_IP;
ptr = data;
diff --git a/src/common/net/net.c b/src/common/net/net.c
index 4d14736..19fe13f 100644
--- a/src/common/net/net.c
+++ b/src/common/net/net.c
@@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
+#include <ws2tcpip.h>
#else // _WIN32
#include <unistd.h>
#include <sys/types.h>
@@ -48,13 +49,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sys/param.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
+#include <errno.h>
#ifdef __linux__
#include <linux/types.h>
#if USE_ICMP
#include <linux/errqueue.h>
+#else
+#undef IP_RECVERR
+#undef IPV6_RECVERR
#endif
#endif // __linux__
-#include <errno.h>
#endif // !_WIN32
// prevents infinite retry loops caused by broken TCP/IP stacks
@@ -80,6 +84,7 @@ static loopback_t loopbacks[NS_COUNT];
#endif // USE_CLIENT
cvar_t *net_ip;
+cvar_t *net_ip6;
cvar_t *net_port;
netadr_t net_from;
@@ -95,6 +100,8 @@ static cvar_t *net_log_name;
static cvar_t *net_log_flush;
#endif
+static cvar_t *net_enable_ipv6;
+
#if USE_ICMP
static cvar_t *net_ignore_icmp;
#endif
@@ -105,6 +112,9 @@ static int net_error;
static qsocket_t udp_sockets[NS_COUNT] = { -1, -1 };
static qsocket_t tcp_socket = -1;
+static qsocket_t udp6_sockets[NS_COUNT] = { -1, -1 };
+static qsocket_t tcp6_socket = -1;
+
#ifdef _DEBUG
static qhandle_t net_logFile;
#endif
@@ -132,82 +142,100 @@ static uint64_t net_packets_sent;
//=============================================================================
-static void NET_NetadrToSockadr(const netadr_t *a, struct sockaddr_in *s)
+static size_t NET_NetadrToSockadr(const netadr_t *a, struct sockaddr_storage *s)
{
+ struct sockaddr_in *s4 = (struct sockaddr_in *)s;
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)s;
+
memset(s, 0, sizeof(*s));
switch (a->type) {
case NA_BROADCAST:
- s->sin_family = AF_INET;
- s->sin_addr.s_addr = INADDR_BROADCAST;
- s->sin_port = a->port;
- break;
+ s4->sin_family = AF_INET;
+ s4->sin_addr.s_addr = INADDR_BROADCAST;
+ s4->sin_port = a->port;
+ return sizeof(*s4);
case NA_IP:
- s->sin_family = AF_INET;
- s->sin_addr.s_addr = a->ip.u32[0];
- s->sin_port = a->port;
- break;
+ s4->sin_family = AF_INET;
+ memcpy(&s4->sin_addr, &a->ip, 4);
+ s4->sin_port = a->port;
+ return sizeof(*s4);
+ case NA_IP6:
+ s6->sin6_family = AF_INET6;
+ memcpy(&s6->sin6_addr, &a->ip, 16);
+ s6->sin6_port = a->port;
+ s6->sin6_scope_id = a->scope_id;
+ return sizeof(*s6);
default:
- Com_Error(ERR_FATAL, "%s: bad address type", __func__);
break;
}
+
+ return 0;
}
-static void NET_SockadrToNetadr(const struct sockaddr_in *s, netadr_t *a)
+static void NET_SockadrToNetadr(const struct sockaddr_storage *s, netadr_t *a)
{
- memset(a, 0, sizeof(*a));
+ const struct sockaddr_in *s4 = (const struct sockaddr_in *)s;
+ const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *)s;
- a->type = NA_IP;
- a->ip.u32[0] = s->sin_addr.s_addr;
- a->port = s->sin_port;
-}
+ memset(a, 0, sizeof(*a));
-static qboolean NET_StringToSockaddr(const char *s, struct sockaddr_in *sadr)
-{
- uint32_t addr;
- struct hostent *h;
- const char *p;
- int dots;
-
- memset(sadr, 0, sizeof(*sadr));
- sadr->sin_family = AF_INET;
-
- for (p = s, dots = 0; *p; p++) {
- if (*p == '.') {
- dots++;
- } else if (!Q_isdigit(*p)) {
- break;
+ switch (s->ss_family) {
+ case AF_INET:
+ a->type = NA_IP;
+ memcpy(&a->ip, &s4->sin_addr, 4);
+ a->port = s4->sin_port;
+ break;
+ case AF_INET6:
+ if (IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr)) {
+ a->type = NA_IP;
+ memcpy(&a->ip, &s6->sin6_addr.s6_addr[12], 4);
+ } else {
+ a->type = NA_IP6;
+ memcpy(&a->ip, &s6->sin6_addr, 16);
+ a->scope_id = s6->sin6_scope_id;
}
+ a->port = s6->sin6_port;
+ break;
+ default:
+ break;
}
- if (*p == 0 && dots <= 3) {
- if ((addr = inet_addr(s)) == INADDR_NONE)
- return qfalse;
- sadr->sin_addr.s_addr = addr;
- } else {
- if (!(h = gethostbyname(s)))
- return qfalse;
- sadr->sin_addr.s_addr = *(uint32_t *)h->h_addr_list[0];
- }
-
- return qtrue;
}
-static qboolean NET_StringToSockaddr2(const char *s, int port, struct sockaddr_in *sadr)
+char *NET_BaseAdrToString(const netadr_t *a)
{
- if (*s) {
- if (!NET_StringToSockaddr(s, sadr))
- return qfalse;
- } else {
- // empty string binds to all interfaces
- memset(sadr, 0, sizeof(*sadr));
- sadr->sin_family = AF_INET;
- sadr->sin_addr.s_addr = INADDR_ANY;
- }
+ static char s[MAX_QPATH];
- if (port != PORT_ANY)
- sadr->sin_port = htons((u_short)port);
+ switch (a->type) {
+ case NA_UNSPECIFIED:
+ return strcpy(s, "<unspecified>");
+ case NA_LOOPBACK:
+ return strcpy(s, "loopback");
+ case NA_IP:
+ case NA_BROADCAST:
+ if (inet_ntop(AF_INET, &a->ip, s, sizeof(s)))
+ return s;
+ else
+ return strcpy(s, "<invalid>");
+ case NA_IP6:
+ if (a->scope_id) {
+ struct sockaddr_storage addr;
+ size_t addrlen;
+
+ addrlen = NET_NetadrToSockadr(a, &addr);
+ if (getnameinfo((struct sockaddr *)&addr, addrlen,
+ s, sizeof(s), NULL, 0, NI_NUMERICHOST) == 0)
+ return s;
+ }
+ if (inet_ntop(AF_INET6, &a->ip, s, sizeof(s)))
+ return s;
+ else
+ return strcpy(s, "<invalid>");
+ default:
+ Com_Error(ERR_FATAL, "%s: bad address type", __func__);
+ }
- return qtrue;
+ return NULL;
}
/*
@@ -220,24 +248,65 @@ char *NET_AdrToString(const netadr_t *a)
static char s[MAX_QPATH];
switch (a->type) {
+ case NA_UNSPECIFIED:
+ return strcpy(s, "<unspecified>");
case NA_LOOPBACK:
- strcpy(s, "loopback");
- return s;
- case NA_IP:
- case NA_BROADCAST:
- Q_snprintf(s, sizeof(s), "%u.%u.%u.%u:%u",
- a->ip.u8[0], a->ip.u8[1],
- a->ip.u8[2], a->ip.u8[3],
- BigShort(a->port));
- return s;
+ return strcpy(s, "loopback");
default:
- Com_Error(ERR_FATAL, "%s: bad address type", __func__);
- break;
+ Q_snprintf(s, sizeof(s), (a->type == NA_IP6) ? "[%s]:%u" : "%s:%u",
+ NET_BaseAdrToString(a), BigShort(a->port));
+ }
+ return s;
+}
+
+static struct addrinfo *NET_SearchAdrrInfo(struct addrinfo *rp, int family)
+{
+ while (rp) {
+ if (rp->ai_family == family)
+ return rp;
+ rp = rp->ai_next;
}
return NULL;
}
+qboolean NET_StringPairToAdr(const char *host, const char *port, netadr_t *a)
+{
+ byte buf[128];
+ struct addrinfo hints, *res, *rp;
+ int err;
+
+ memset(&hints, 0, sizeof(hints));
+
+ if (net_enable_ipv6->integer < 1)
+ hints.ai_family = AF_INET;
+
+ if (inet_pton(AF_INET, host, buf) == 1 ||
+ inet_pton(AF_INET6, host, buf) == 1)
+ hints.ai_flags |= AI_NUMERICHOST;
+
+#ifdef AI_NUMERICSERV
+ if (port && COM_IsUint(port))
+ hints.ai_flags |= AI_NUMERICSERV;
+#endif
+
+ err = getaddrinfo(host, port, &hints, &res);
+ if (err)
+ return qfalse;
+
+ rp = res;
+ if (net_enable_ipv6->integer < 2) {
+ rp = NET_SearchAdrrInfo(res, AF_INET);
+ if (!rp)
+ rp = res;
+ }
+
+ NET_SockadrToNetadr((struct sockaddr_storage *)rp->ai_addr, a);
+
+ freeaddrinfo(res);
+ return qtrue;
+}
+
/*
=============
NET_StringToAdr
@@ -251,26 +320,31 @@ idnewt:28000
*/
qboolean NET_StringToAdr(const char *s, netadr_t *a, int default_port)
{
- struct sockaddr_in sadr;
- char copy[MAX_STRING_CHARS], *p;
+ char copy[MAX_STRING_CHARS], *h, *p;
size_t len;
len = Q_strlcpy(copy, s, sizeof(copy));
if (len >= sizeof(copy))
return qfalse;
+ // parse IPv6 address in square brackets
+ h = p = copy;
+ if (*h == '[') {
+ h++;
+ p = strchr(h, ']');
+ if (!p)
+ return qfalse;
+ *p++ = 0;
+ }
+
// strip off a trailing :port if present
- p = strchr(copy, ':');
+ p = strchr(p, ':');
if (p)
- *p = 0;
+ *p++ = 0;
- if (!NET_StringToSockaddr(copy, &sadr))
+ if (!NET_StringPairToAdr(h, p, a))
return qfalse;
- NET_SockadrToNetadr(&sadr, a);
-
- if (p)
- a->port = BigShort(atoi(p + 1));
if (!a->port)
a->port = BigShort(default_port);
@@ -527,26 +601,31 @@ static qboolean NET_SendLoopPacket(netsrc_t sock, const void *data,
static const char *os_error_string(int err);
-static void NET_ErrorEvent(netsrc_t sock, netadr_t *from,
+static void NET_ErrorEvent(qsocket_t sock, netadr_t *from,
int ee_errno, int ee_info)
{
if (net_ignore_icmp->integer > 0) {
return;
}
+ if (from->type == NA_UNSPECIFIED) {
+ return;
+ }
+
Com_DPrintf("%s: %s from %s\n", __func__,
os_error_string(ee_errno), NET_AdrToString(from));
net_icmp_errors++;
- switch (sock) {
- case NS_SERVER:
+ if (sock == udp_sockets[NS_SERVER] ||
+ sock == udp6_sockets[NS_SERVER]) {
SV_ErrorEvent(from, ee_errno, ee_info);
- break;
- case NS_CLIENT:
+ return;
+ }
+
+ if (sock == udp_sockets[NS_CLIENT] ||
+ sock == udp6_sockets[NS_CLIENT]) {
CL_ErrorEvent(from);
- break;
- default:
- break;
+ return;
}
}
@@ -752,15 +831,15 @@ int NET_Sleepv(int msec, ...)
//=============================================================================
-static void NET_GetUdpPackets(netsrc_t sock, void (*packet_cb)(void))
+static void NET_GetUdpPackets(qsocket_t sock, void (*packet_cb)(void))
{
ioentry_t *e;
ssize_t ret;
- if (udp_sockets[sock] == -1)
+ if (sock == -1)
return;
- e = os_get_io(udp_sockets[sock]);
+ e = os_get_io(sock);
if (!e->canread)
return;
@@ -813,7 +892,10 @@ void NET_GetPackets(netsrc_t sock, void (*packet_cb)(void))
#endif
// process UDP packets
- NET_GetUdpPackets(sock, packet_cb);
+ NET_GetUdpPackets(udp_sockets[sock], packet_cb);
+
+ // process UDP6 packets
+ NET_GetUdpPackets(udp6_sockets[sock], packet_cb);
}
/*
@@ -826,6 +908,7 @@ qboolean NET_SendPacket(netsrc_t sock, const void *data,
size_t len, const netadr_t *to)
{
ssize_t ret;
+ qsocket_t s;
if (len == 0)
return qfalse;
@@ -836,15 +919,28 @@ qboolean NET_SendPacket(netsrc_t sock, const void *data,
return qfalse;
}
+ switch (to->type) {
+ case NA_UNSPECIFIED:
+ return qfalse;
#if USE_CLIENT
- if (to->type == NA_LOOPBACK)
+ case NA_LOOPBACK:
return NET_SendLoopPacket(sock, data, len, to);
#endif
+ case NA_IP:
+ case NA_BROADCAST:
+ s = udp_sockets[sock];
+ break;
+ case NA_IP6:
+ s = udp6_sockets[sock];
+ break;
+ default:
+ Com_Error(ERR_FATAL, "%s: bad address type", __func__);
+ }
- if (udp_sockets[sock] == -1)
+ if (s == -1)
return qfalse;
- ret = os_udp_send(sock, data, len, to);
+ ret = os_udp_send(s, data, len, to);
if (ret == NET_AGAIN)
return qfalse;
@@ -873,140 +969,216 @@ qboolean NET_SendPacket(netsrc_t sock, const void *data,
//=============================================================================
-static qsocket_t UDP_OpenSocket(const char *iface, int port)
+static qsocket_t UDP_OpenSocket(const char *iface, int port, int family)
{
- struct sockaddr_in sadr;
- int s;
+ qsocket_t s, newsocket;
+ struct addrinfo hints, *res, *rp;
+ char buf[MAX_QPATH];
+ const char *node, *service;
+ int err;
- Com_DPrintf("Opening UDP socket: %s:%d\n", iface, port);
+ Com_DPrintf("Opening UDP%s socket: %s:%d\n",
+ (family == AF_INET6) ? "6" : "", iface, port);
- // resolve iface addr
- if (!NET_StringToSockaddr2(iface, port, &sadr)) {
- Com_EPrintf("%s: %s:%d: bad interface address\n",
- __func__, iface, port);
- return -1;
- }
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
- s = os_socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (s == -1) {
- Com_EPrintf("%s: %s:%d: can't create socket: %s\n",
- __func__, iface, port, NET_ErrorString());
- return -1;
+ // empty string binds to all interfaces
+ if (!*iface) {
+ node = NULL;
+ } else {
+ node = iface;
}
- // make it non-blocking
- if (os_make_nonblock(s, 1)) {
- Com_EPrintf("%s: %s:%d: can't make socket non-blocking: %s\n",
- __func__, iface, port, NET_ErrorString());
- goto fail;
+ if (port == PORT_ANY) {
+ service = "0";
+ } else {
+ Q_snprintf(buf, sizeof(buf), "%d", port);
+ service = buf;
}
- // make it broadcast capable
- if (os_setsockopt(s, SOL_SOCKET, SO_BROADCAST, 1)) {
- Com_WPrintf("%s: %s:%d: can't make socket broadcast capable: %s\n",
- __func__, iface, port, NET_ErrorString());
- }
+#ifdef AI_NUMERICSERV
+ hints.ai_flags |= AI_NUMERICSERV;
+#endif
-#ifdef __linux__
- // udp(7) says: "By default, Linux UDP does path MTU discovery". This means
- // kernel will set "don't fragment" bit on outgoing packets. This is not
- // what most Quake 2 server operators expect. Firewalled ICMP traffic may
- // result in clients getting stuck on connect. Thus we enable IP
- // fragmentation by default.
+ // resolve iface addr
+ err = getaddrinfo(node, service, &hints, &res);
+ if (err) {
+ Com_EPrintf("%s: %s:%d: bad interface address: %s\n",
+ __func__, iface, port, gai_strerror(err));
+ return -1;
+ }
- int disc = IP_PMTUDISC_DONT;
+ newsocket = -1;
+ for (rp = res; rp; rp = rp->ai_next) {
+ s = os_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (s == -1) {
+ Com_EPrintf("%s: %s:%d: can't create socket: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ continue;
+ }
-#if USE_ICMP
- // enable ICMP error queue
- if (net_ignore_icmp->integer <= 0) {
- if (os_setsockopt(s, IPPROTO_IP, IP_RECVERR, 1)) {
- Com_WPrintf("%s: %s:%d: can't enable ICMP error queue: %s\n",
+ // make it non-blocking
+ if (os_make_nonblock(s, 1)) {
+ Com_EPrintf("%s: %s:%d: can't make socket non-blocking: %s\n",
__func__, iface, port, NET_ErrorString());
- Cvar_Set("net_ignore_icmp", "1");
+ os_closesocket(s);
+ continue;
}
-#if USE_PMTUDISC
- // overload negative values to enable path MTU discovery
- switch (net_ignore_icmp->integer) {
- case -1: disc = IP_PMTUDISC_WANT; break;
- case -2: disc = IP_PMTUDISC_DO; break;
-#ifdef IP_PMTUDISC_PROBE
- case -3: disc = IP_PMTUDISC_PROBE; break;
+ if (rp->ai_family == AF_INET) {
+ // make it broadcast capable
+ if (os_setsockopt(s, SOL_SOCKET, SO_BROADCAST, 1)) {
+ Com_WPrintf("%s: %s:%d: can't make socket broadcast capable: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
+
+#ifdef IP_RECVERR
+ // enable ICMP error queue
+ if (os_setsockopt(s, IPPROTO_IP, IP_RECVERR, 1)) {
+ Com_WPrintf("%s: %s:%d: can't enable ICMP error queue: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
+#endif
+
+#ifdef IP_MTU_DISCOVER
+ // allow IP fragmentation by disabling path MTU discovery
+ if (os_setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT)) {
+ Com_WPrintf("%s: %s:%d: can't disable path MTU discovery: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
#endif
}
-#endif // USE_PMTUDISC
- }
-#endif // USE_ICMP
- // set path MTU discovery option
- if (os_setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, disc)) {
- Com_WPrintf("%s: %s:%d: can't %sable path MTU discovery: %s\n",
- __func__, iface, port,
- disc == IP_PMTUDISC_DONT ? "dis" : "en",
- NET_ErrorString());
- }
-#endif // __linux__
+ if (rp->ai_family == AF_INET6) {
+#ifdef IPV6_RECVERR
+ // enable ICMP6 error queue
+ if (os_setsockopt(s, IPPROTO_IPV6, IPV6_RECVERR, 1)) {
+ Com_WPrintf("%s: %s:%d: can't enable ICMP6 error queue: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
+#endif
- if (os_bind(s, (struct sockaddr *)&sadr, sizeof(sadr))) {
- Com_EPrintf("%s: %s:%d: can't bind socket: %s\n",
- __func__, iface, port, NET_ErrorString());
- goto fail;
+#ifdef IPV6_V6ONLY
+ // disable IPv4-mapped addresses
+ if (os_setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, 1)) {
+ Com_WPrintf("%s: %s:%d: can't make socket IPv6-only: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
+#endif
+ }
+
+ if (os_bind(s, rp->ai_addr, rp->ai_addrlen)) {
+ Com_EPrintf("%s: %s:%d: can't bind socket: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ os_closesocket(s);
+ continue;
+ }
+
+ newsocket = s;
+ break;
}
- return s;
+ freeaddrinfo(res);
-fail:
- os_closesocket(s);
- return -1;
+ return newsocket;
}
-static qsocket_t TCP_OpenSocket(const char *iface, int port, netsrc_t who)
+static qsocket_t TCP_OpenSocket(const char *iface, int port, int family, netsrc_t who)
{
- struct sockaddr_in sadr;
- int s;
+ qsocket_t s, newsocket;
+ struct addrinfo hints, *res, *rp;
+ char buf[MAX_QPATH];
+ const char *node, *service;
+ int err;
- Com_DPrintf("Opening TCP socket: %s:%d\n", iface, port);
+ Com_DPrintf("Opening TCP%s socket: %s:%d\n",
+ (family == AF_INET6) ? "6" : "", iface, port);
- // resolve iface addr
- if (!NET_StringToSockaddr2(iface, port, &sadr)) {
- Com_EPrintf("%s: %s:%d: bad interface address\n",
- __func__, iface, port);
- return -1;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ // empty string binds to all interfaces
+ if (!*iface) {
+ node = NULL;
+ } else {
+ node = iface;
}
- s = os_socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (s == -1) {
- Com_EPrintf("%s: %s:%d: can't create socket: %s\n",
- __func__, iface, port, NET_ErrorString());
- return -1;
+ if (port == PORT_ANY) {
+ service = "0";
+ } else {
+ Q_snprintf(buf, sizeof(buf), "%d", port);
+ service = buf;
}
- // make it non-blocking
- if (os_make_nonblock(s, 1)) {
- Com_EPrintf("%s: %s:%d: can't make socket non-blocking: %s\n",
- __func__, iface, port, NET_ErrorString());
- goto fail;
+#ifdef AI_NUMERICSERV
+ hints.ai_flags |= AI_NUMERICSERV;
+#endif
+
+ // resolve iface addr
+ err = getaddrinfo(node, service, &hints, &res);
+ if (err) {
+ Com_EPrintf("%s: %s:%d: bad interface address: %s\n",
+ __func__, iface, port, gai_strerror(err));
+ return -1;
}
- if (who == NS_SERVER) {
- // give it a chance to reuse previous port
- if (os_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 1)) {
- Com_WPrintf("%s: %s:%d: can't force socket to reuse address: %s\n",
+ newsocket = -1;
+ for (rp = res; rp; rp = rp->ai_next) {
+ s = os_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (s == -1) {
+ Com_EPrintf("%s: %s:%d: can't create socket: %s\n",
__func__, iface, port, NET_ErrorString());
+ continue;
}
- }
- if (os_bind(s, (struct sockaddr *)&sadr, sizeof(sadr))) {
- Com_EPrintf("%s: %s:%d: can't bind socket: %s\n",
- __func__, iface, port, NET_ErrorString());
- goto fail;
+ // make it non-blocking
+ if (os_make_nonblock(s, 1)) {
+ Com_EPrintf("%s: %s:%d: can't make socket non-blocking: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ os_closesocket(s);
+ continue;
+ }
+
+ if (who == NS_SERVER) {
+ // give it a chance to reuse previous port
+ if (os_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 1)) {
+ Com_WPrintf("%s: %s:%d: can't force socket to reuse address: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
+ }
+
+ if (rp->ai_family == AF_INET6) {
+#ifdef IPV6_V6ONLY
+ // disable IPv4-mapped addresses
+ if (os_setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, 1)) {
+ Com_WPrintf("%s: %s:%d: can't make socket IPv6-only: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ }
+#endif
+ }
+
+ if (os_bind(s, rp->ai_addr, rp->ai_addrlen)) {
+ Com_EPrintf("%s: %s:%d: can't bind socket: %s\n",
+ __func__, iface, port, NET_ErrorString());
+ os_closesocket(s);
+ continue;
+ }
+
+ newsocket = s;
+ break;
}
- return s;
+ freeaddrinfo(res);
-fail:
- os_closesocket(s);
- return -1;
+ return newsocket;
}
static void NET_OpenServer(void)
@@ -1015,7 +1187,10 @@ static void NET_OpenServer(void)
ioentry_t *e;
qsocket_t s;
- s = UDP_OpenSocket(net_ip->string, net_port->integer);
+ if (udp_sockets[NS_SERVER] != -1)
+ return;
+
+ s = UDP_OpenSocket(net_ip->string, net_port->integer, AF_INET);
if (s != -1) {
saved_port = net_port->integer;
udp_sockets[NS_SERVER] = s;
@@ -1041,6 +1216,26 @@ static void NET_OpenServer(void)
Com_Error(ERR_FATAL, "Couldn't open dedicated server UDP port");
}
+static void NET_OpenServer6(void)
+{
+ ioentry_t *e;
+ qsocket_t s;
+
+ if (net_enable_ipv6->integer < 2)
+ return;
+
+ if (udp6_sockets[NS_SERVER] != -1)
+ return;
+
+ s = UDP_OpenSocket(net_ip6->string, net_port->integer, AF_INET6);
+ if (s == -1)
+ return;
+
+ udp6_sockets[NS_SERVER] = s;
+ e = NET_AddFd(s);
+ e->wantread = qtrue;
+}
+
#if USE_CLIENT
static void NET_OpenClient(void)
{
@@ -1048,11 +1243,14 @@ static void NET_OpenClient(void)
qsocket_t s;
netadr_t adr;
- s = UDP_OpenSocket(net_ip->string, net_clientport->integer);
+ if (udp_sockets[NS_CLIENT] != -1)
+ return;
+
+ s = UDP_OpenSocket(net_ip->string, net_clientport->integer, AF_INET);
if (s == -1) {
// now try with random port
if (net_clientport->integer != PORT_ANY)
- s = UDP_OpenSocket(net_ip->string, PORT_ANY);
+ s = UDP_OpenSocket(net_ip->string, PORT_ANY, AF_INET);
if (s == -1) {
Com_WPrintf("Couldn't open client UDP port.\n");
@@ -1073,6 +1271,26 @@ static void NET_OpenClient(void)
e = NET_AddFd(s);
e->wantread = qtrue;
}
+
+static void NET_OpenClient6(void)
+{
+ ioentry_t *e;
+ qsocket_t s;
+
+ if (net_enable_ipv6->integer < 1)
+ return;
+
+ if (udp6_sockets[NS_CLIENT] != -1)
+ return;
+
+ s = UDP_OpenSocket(net_ip6->string, net_clientport->integer, AF_INET6);
+ if (s == -1)
+ return;
+
+ udp6_sockets[NS_CLIENT] = s;
+ e = NET_AddFd(s);
+ e->wantread = qtrue;
+}
#endif
/*
@@ -1096,19 +1314,26 @@ void NET_Config(netflag_t flag)
os_closesocket(udp_sockets[sock]);
udp_sockets[sock] = -1;
}
+ if (udp6_sockets[sock] != -1) {
+ NET_RemoveFd(udp6_sockets[sock]);
+ os_closesocket(udp6_sockets[sock]);
+ udp6_sockets[sock] = -1;
+ }
}
net_active = NET_NONE;
return;
}
#if USE_CLIENT
- if ((flag & NET_CLIENT) && udp_sockets[NS_CLIENT] == -1) {
+ if (flag & NET_CLIENT) {
NET_OpenClient();
+ NET_OpenClient6();
}
#endif
- if ((flag & NET_SERVER) && udp_sockets[NS_SERVER] == -1) {
+ if (flag & NET_SERVER) {
NET_OpenServer();
+ NET_OpenServer6();
}
net_active |= flag;
@@ -1144,7 +1369,7 @@ void NET_CloseStream(netstream_t *s)
s->state = NS_DISCONNECTED;
}
-neterr_t NET_Listen(qboolean arg)
+static neterr_t NET_Listen4(qboolean arg)
{
qsocket_t s;
ioentry_t *e;
@@ -1163,12 +1388,12 @@ neterr_t NET_Listen(qboolean arg)
return NET_AGAIN;
}
- s = TCP_OpenSocket(net_ip->string, net_port->integer, NS_SERVER);
+ s = TCP_OpenSocket(net_ip->string, net_port->integer, AF_INET, NS_SERVER);
if (s == -1) {
return NET_ERROR;
}
- ret = os_listen(s, 128);
+ ret = os_listen(s, 16);
if (ret) {
os_closesocket(s);
return ret;
@@ -1183,23 +1408,82 @@ neterr_t NET_Listen(qboolean arg)
return NET_OK;
}
-// net_from variable receives source address
-neterr_t NET_Accept(netstream_t *s)
+static neterr_t NET_Listen6(qboolean arg)
+{
+ qsocket_t s;
+ ioentry_t *e;
+ neterr_t ret;
+
+ if (!arg) {
+ if (tcp6_socket != -1) {
+ NET_RemoveFd(tcp6_socket);
+ os_closesocket(tcp6_socket);
+ tcp6_socket = -1;
+ }
+ return NET_OK;
+ }
+
+
+ if (tcp6_socket != -1) {
+ return NET_AGAIN;
+ }
+
+ if (net_enable_ipv6->integer < 2) {
+ return NET_AGAIN;
+ }
+
+ s = TCP_OpenSocket(net_ip6->string, net_port->integer, AF_INET6, NS_SERVER);
+ if (s == -1) {
+ return NET_ERROR;
+ }
+
+ ret = os_listen(s, 16);
+ if (ret) {
+ os_closesocket(s);
+ return ret;
+ }
+
+ tcp6_socket = s;
+
+ // initialize io entry
+ e = NET_AddFd(s);
+ e->wantread = qtrue;
+
+ return NET_OK;
+}
+
+neterr_t NET_Listen(qboolean arg)
+{
+ neterr_t ret4, ret6;
+
+ ret4 = NET_Listen4(arg);
+ ret6 = NET_Listen6(arg);
+
+ if (ret4 == NET_OK || ret6 == NET_OK)
+ return NET_OK;
+
+ if (ret4 == NET_ERROR || ret6 == NET_ERROR)
+ return NET_ERROR;
+
+ return NET_AGAIN;
+}
+
+static neterr_t NET_AcceptSocket(netstream_t *s, qsocket_t sock)
{
ioentry_t *e;
qsocket_t newsocket;
neterr_t ret;
- if (tcp_socket == -1) {
+ if (sock == -1) {
return NET_AGAIN;
}
- e = os_get_io(tcp_socket);
+ e = os_get_io(sock);
if (!e->canread) {
return NET_AGAIN;
}
- ret = os_accept(tcp_socket, &newsocket, &net_from);
+ ret = os_accept(sock, &newsocket, &net_from);
if (ret) {
e->canread = qfalse;
return ret;
@@ -1226,6 +1510,18 @@ neterr_t NET_Accept(netstream_t *s)
return NET_OK;
}
+// net_from variable receives source address
+neterr_t NET_Accept(netstream_t *s)
+{
+ neterr_t ret;
+
+ ret = NET_AcceptSocket(s, tcp_socket);
+ if (ret == NET_AGAIN)
+ ret = NET_AcceptSocket(s, tcp6_socket);
+
+ return ret;
+}
+
neterr_t NET_Connect(const netadr_t *peer, netstream_t *s)
{
qsocket_t socket;
@@ -1234,7 +1530,17 @@ neterr_t NET_Connect(const netadr_t *peer, netstream_t *s)
// always bind to `net_ip' for outgoing TCP connections
// to avoid problems with AC or MVD/GTV auth on a multi IP system
- socket = TCP_OpenSocket(net_ip->string, PORT_ANY, NS_CLIENT);
+ switch (peer->type) {
+ case NA_IP:
+ socket = TCP_OpenSocket(net_ip->string, PORT_ANY, AF_INET, NS_CLIENT);
+ break;
+ case NA_IP6:
+ socket = TCP_OpenSocket(net_ip6->string, PORT_ANY, AF_INET6, NS_CLIENT);
+ break;
+ default:
+ return NET_ERROR;
+ }
+
if (socket == -1) {
return NET_ERROR;
}
@@ -1427,23 +1733,18 @@ error:
//===================================================================
-static void dump_hostent(struct hostent *h)
+static void dump_addrinfo(struct addrinfo *ai)
{
- byte **list;
- int i;
-
- Com_Printf("Hostname: %s\n", h->h_name);
-
- list = (byte **)h->h_aliases;
- for (i = 0; list[i]; i++) {
- Com_Printf("Alias : %s\n", list[i]);
- }
-
- list = (byte **)h->h_addr_list;
- for (i = 0; list[i]; i++) {
- Com_Printf("IP : %u.%u.%u.%u\n",
- list[i][0], list[i][1], list[i][2], list[i][3]);
- }
+ char buf1[MAX_QPATH], buf2[MAX_STRING_CHARS];
+ char *fa = (ai->ai_addr->sa_family == AF_INET6) ? "6" : "";
+
+ getnameinfo(ai->ai_addr, ai->ai_addrlen,
+ buf1, sizeof(buf1), NULL, 0, NI_NUMERICHOST);
+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
+ buf2, sizeof(buf2), NULL, 0, NI_NAMEREQD) == 0)
+ Com_Printf("IP%1s : %s (%s)\n", fa, buf1, buf2);
+ else
+ Com_Printf("IP%1s : %s\n", fa, buf1);
}
static void dump_socket(qsocket_t s, const char *s1, const char *s2)
@@ -1470,25 +1771,45 @@ NET_ShowIP_f
*/
static void NET_ShowIP_f(void)
{
- char buffer[256];
- struct hostent *h;
+ char buffer[MAX_STRING_CHARS];
+ struct addrinfo hints, *res, *rp;
+ int err;
if (gethostname(buffer, sizeof(buffer)) == -1) {
Com_EPrintf("Couldn't get system host name\n");
return;
}
- if (!(h = gethostbyname(buffer))) {
- Com_EPrintf("Couldn't resolve %s\n", buffer);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (net_enable_ipv6->integer < 1)
+ hints.ai_family = AF_INET;
+
+ err = getaddrinfo(buffer, NULL, &hints, &res);
+ if (err) {
+ Com_Printf("Couldn't resolve %s: %s\n", buffer, gai_strerror(err));
return;
}
- dump_hostent(h);
+ if (res->ai_canonname)
+ Com_Printf("Hostname: %s\n", res->ai_canonname);
+
+ for (rp = res; rp; rp = rp->ai_next)
+ dump_addrinfo(rp);
- // dump listening sockets
+ freeaddrinfo(res);
+
+ // dump listening IP sockets
dump_socket(udp_sockets[NS_CLIENT], "Client", "UDP");
dump_socket(udp_sockets[NS_SERVER], "Server", "UDP");
dump_socket(tcp_socket, "Server", "TCP");
+
+ // dump listening IPv6 sockets
+ dump_socket(udp6_sockets[NS_CLIENT], "Client", "UDP6");
+ dump_socket(udp6_sockets[NS_SERVER], "Server", "UDP6");
+ dump_socket(tcp6_socket, "Server", "TCP6");
}
/*
@@ -1498,10 +1819,9 @@ NET_Dns_f
*/
static void NET_Dns_f(void)
{
- char buffer[MAX_QPATH];
- char *p;
- struct hostent *h;
- u_long address;
+ char buffer[MAX_STRING_CHARS], *h, *p;
+ struct addrinfo hints, *res, *rp;
+ int err;
if (Cmd_Argc() != 2) {
Com_Printf("Usage: %s <address>\n", Cmd_Argv(0));
@@ -1510,22 +1830,43 @@ static void NET_Dns_f(void)
Cmd_ArgvBuffer(1, buffer, sizeof(buffer));
- if ((p = strchr(buffer, ':')) != NULL) {
- *p = 0;
+ // parse IPv6 address square brackets
+ h = p = buffer;
+ if (*h == '[') {
+ h++;
+ p = strchr(h, ']');
+ if (!p) {
+ Com_Printf("Bad IPv6 address\n");
+ return;
+ }
+ *p++ = 0;
}
- if ((address = inet_addr(buffer)) != INADDR_NONE) {
- h = gethostbyaddr((const char *)&address, sizeof(address), AF_INET);
- } else {
- h = gethostbyname(buffer);
- }
+ // strip off a trailing :port if present
+ p = strchr(p, ':');
+ if (p)
+ *p++ = 0;
- if (!h) {
- Com_Printf("Couldn't resolve %s\n", buffer);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (net_enable_ipv6->integer < 1)
+ hints.ai_family = AF_INET;
+
+ err = getaddrinfo(h, NULL, &hints, &res);
+ if (err) {
+ Com_Printf("Couldn't resolve %s: %s\n", h, gai_strerror(err));
return;
}
- dump_hostent(h);
+ if (res->ai_canonname)
+ Com_Printf("Hostname: %s\n", res->ai_canonname);
+
+ for (rp = res; rp; rp = rp->ai_next)
+ dump_addrinfo(rp);
+
+ freeaddrinfo(res);
}
/*
@@ -1536,18 +1877,21 @@ NET_Restart_f
static void NET_Restart_f(void)
{
netflag_t flag = net_active;
- qboolean listen = tcp_socket != -1;
+ qboolean listen4 = (tcp_socket != -1);
+ qboolean listen6 = (tcp6_socket != -1);
Com_DPrintf("%s\n", __func__);
- if (listen) {
- NET_Listen(qfalse);
- }
+ NET_Listen4(qfalse);
+ NET_Listen6(qfalse);
NET_Config(NET_NONE);
+
+ listen6 |= listen4;
+ listen6 &= net_enable_ipv6->integer > 1;
+
NET_Config(flag);
- if (listen) {
- NET_Listen(qtrue);
- }
+ NET_Listen4(listen4);
+ NET_Listen6(listen6);
#if USE_SYSCON
SV_SetConsoleTitle();
@@ -1559,6 +1903,17 @@ static void net_udp_param_changed(cvar_t *self)
NET_Restart_f();
}
+static const char *NET_EnableIP6(void)
+{
+ qsocket_t s = os_socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+
+ if (s == -1)
+ return "0";
+
+ os_closesocket(s);
+ return "1";
+}
+
/*
====================
NET_Init
@@ -1570,13 +1925,17 @@ void NET_Init(void)
net_ip = Cvar_Get("net_ip", "", 0);
net_ip->changed = net_udp_param_changed;
+ net_ip6 = Cvar_Get("net_ip6", "", 0);
+ net_ip6->changed = net_udp_param_changed;
net_port = Cvar_Get("net_port", STRINGIFY(PORT_SERVER), 0);
net_port->changed = net_udp_param_changed;
+
#if USE_CLIENT
net_clientport = Cvar_Get("net_clientport", STRINGIFY(PORT_ANY), 0);
net_clientport->changed = net_udp_param_changed;
net_dropsim = Cvar_Get("net_dropsim", "0", 0);
#endif
+
#if _DEBUG
net_log_enable = Cvar_Get("net_log_enable", "0", 0);
net_log_enable->changed = net_log_enable_changed;
@@ -1585,6 +1944,10 @@ void NET_Init(void)
net_log_flush = Cvar_Get("net_log_flush", "0", 0);
net_log_flush->changed = net_log_param_changed;
#endif
+
+ net_enable_ipv6 = Cvar_Get("net_enable_ipv6", NET_EnableIP6(), 0);
+ net_enable_ipv6->changed = net_udp_param_changed;
+
#if USE_ICMP
net_ignore_icmp = Cvar_Get("net_ignore_icmp", "0", 0);
#endif
diff --git a/src/common/net/unix.h b/src/common/net/unix.h
index 920ca87..a5ad510 100644
--- a/src/common/net/unix.h
+++ b/src/common/net/unix.h
@@ -25,36 +25,12 @@ static const char *os_error_string(int err)
return strerror(err);
}
-// Receiving ICMP errors on unconnected UDP sockets is tricky...
-// Linux 2.2 and higher supports this via IP_RECVERR option, see ip(7).
-// What about BSD?
-
-#if USE_ICMP && (defined __linux__)
-
-static qboolean check_offender(const struct sockaddr_in *from,
- const struct sockaddr_in *to)
-{
- if (!to)
- return qfalse;
-
- if (from->sin_addr.s_addr != to->sin_addr.s_addr)
- return qfalse;
-
- if (from->sin_port && from->sin_port != to->sin_port)
- return qfalse;
-
- Com_DPrintf("%s: found offending address %s:%d\n", "process_error_queue",
- inet_ntoa(from->sin_addr), ntohs(from->sin_port));
-
- return qtrue;
-}
-
-// Returns true if failed socket operation should be retried.
-// May get called from NET_SendPacket() path, avoid interfering with net_from.
-static qboolean process_error_queue(netsrc_t sock, const struct sockaddr_in *to_addr)
+// returns true if failed socket operation should be retried.
+static qboolean process_error_queue(qsocket_t sock, const netadr_t *to)
{
+#ifdef IP_RECVERR
byte buffer[1024];
- struct sockaddr_in from_addr;
+ struct sockaddr_storage from_addr;
struct msghdr msg;
struct cmsghdr *cmsg;
struct sock_extended_err *ee;
@@ -71,7 +47,7 @@ static qboolean process_error_queue(netsrc_t sock, const struct sockaddr_in *to_
msg.msg_control = buffer;
msg.msg_controllen = sizeof(buffer);
- if (recvmsg(udp_sockets[sock], &msg, MSG_ERRQUEUE) == -1) {
+ if (recvmsg(sock, &msg, MSG_ERRQUEUE) == -1) {
if (errno != EWOULDBLOCK)
Com_DPrintf("%s: %s\n", __func__, strerror(errno));
break;
@@ -86,14 +62,17 @@ static qboolean process_error_queue(netsrc_t sock, const struct sockaddr_in *to_
for (cmsg = CMSG_FIRSTHDR(&msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
- if (cmsg->cmsg_level != IPPROTO_IP) {
+ if (cmsg->cmsg_level != IPPROTO_IP &&
+ cmsg->cmsg_level != IPPROTO_IPV6) {
continue;
}
- if (cmsg->cmsg_type != IP_RECVERR) {
+ if (cmsg->cmsg_type != IP_RECVERR &&
+ cmsg->cmsg_type != IPV6_RECVERR) {
continue;
}
ee = (struct sock_extended_err *)CMSG_DATA(cmsg);
- if (ee->ee_origin == SO_EE_ORIGIN_ICMP) {
+ if (ee->ee_origin == SO_EE_ORIGIN_ICMP ||
+ ee->ee_origin == SO_EE_ORIGIN_ICMP6) {
break;
}
}
@@ -103,33 +82,38 @@ static qboolean process_error_queue(netsrc_t sock, const struct sockaddr_in *to_
break;
}
- found |= check_offender(&from_addr, to_addr);
-
NET_SockadrToNetadr(&from_addr, &from);
+ // check for offender address being current packet destination
+ if (to != NULL && NET_IsEqualBaseAdr(&from, to) &&
+ (from.port == 0 || from.port == to->port)) {
+ Com_DPrintf("%s: found offending address: %s\n", __func__,
+ NET_AdrToString(&from));
+ found = qtrue;
+ }
+
// handle ICMP error
NET_ErrorEvent(sock, &from, ee->ee_errno, ee->ee_info);
}
return !!tries && !found;
+#else
+ return qfalse;
+#endif
}
-#endif // !__linux__
-
-static ssize_t os_udp_recv(netsrc_t sock, void *data,
+static ssize_t os_udp_recv(qsocket_t sock, void *data,
size_t len, netadr_t *from)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
socklen_t addrlen;
ssize_t ret;
-
-#if USE_ICMP && (defined __linux__)
int tries;
+
for (tries = 0; tries < MAX_ERROR_RETRIES; tries++) {
-#endif
memset(&addr, 0, sizeof(addr));
addrlen = sizeof(addr);
- ret = recvfrom(udp_sockets[sock], data, len, 0,
+ ret = recvfrom(sock, data, len, 0,
(struct sockaddr *)&addr, &addrlen);
NET_SockadrToNetadr(&addr, from);
@@ -143,32 +127,26 @@ static ssize_t os_udp_recv(netsrc_t sock, void *data,
if (net_error == EWOULDBLOCK)
return NET_AGAIN;
-#if USE_ICMP && (defined __linux__)
- // recvfrom() fails on Linux if there is an ICMP originated pending
- // error on socket. Suck up error queue and retry...
-
if (!process_error_queue(sock, NULL))
break;
}
-#endif
return NET_ERROR;
}
-static ssize_t os_udp_send(netsrc_t sock, const void *data,
+static ssize_t os_udp_send(qsocket_t sock, const void *data,
size_t len, const netadr_t *to)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
ssize_t ret;
+ int tries;
- NET_NetadrToSockadr(to, &addr);
+ addrlen = NET_NetadrToSockadr(to, &addr);
-#if USE_ICMP && (defined __linux__)
- int tries;
for (tries = 0; tries < MAX_ERROR_RETRIES; tries++) {
-#endif
- ret = sendto(udp_sockets[sock], data, len, 0,
- (struct sockaddr *)&addr, sizeof(addr));
+ ret = sendto(sock, data, len, 0,
+ (struct sockaddr *)&addr, addrlen);
if (ret >= 0)
return ret;
@@ -178,28 +156,9 @@ static ssize_t os_udp_send(netsrc_t sock, const void *data,
if (net_error == EWOULDBLOCK)
return NET_AGAIN;
-#if USE_ICMP && (defined __linux__)
- // sendto() fails on Linux if there is an ICMP originated pending error
- // on socket. Suck up error queue and retry...
- //
- // But how do I distingiush between a failure caused by completely
- // unrelated ICMP error sitting in the queue and an error directly
- // related to this sendto() call?
- //
- // On one hand, I don't want to drop packets to legitimate clients, and
- // have to retry sendto() after processing error queue. On other
- // hand, infinite loop should be avoided if sendto() call regenerates
- // the message in error queue.
- //
- // This mess is worked around by passing destination address to
- // process_error_queue() and checking if this address/port pair is
- // found in the queue. If it is found, or the queue is empty, do not
- // retry.
-
- if (!process_error_queue(sock, &addr))
+ if (!process_error_queue(sock, to))
break;
}
-#endif
return NET_ERROR;
}
@@ -245,7 +204,7 @@ static neterr_t os_listen(qsocket_t sock, int backlog)
static neterr_t os_accept(qsocket_t sock, qsocket_t *newsock, netadr_t *from)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
socklen_t addrlen;
int s;
@@ -266,11 +225,12 @@ static neterr_t os_accept(qsocket_t sock, qsocket_t *newsock, netadr_t *from)
static neterr_t os_connect(qsocket_t sock, const netadr_t *to)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
- NET_NetadrToSockadr(to, &addr);
+ addrlen = NET_NetadrToSockadr(to, &addr);
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ if (connect(sock, (struct sockaddr *)&addr, addrlen) == -1) {
net_error = errno;
if (net_error == EINPROGRESS)
return NET_OK;
@@ -325,7 +285,7 @@ static neterr_t os_bind(qsocket_t sock, const struct sockaddr *addr, size_t addr
static neterr_t os_getsockname(qsocket_t sock, netadr_t *name)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
socklen_t addrlen;
memset(&addr, 0, sizeof(addr));
diff --git a/src/common/net/win.h b/src/common/net/win.h
index 74b451b..fbb1e1c 100644
--- a/src/common/net/win.h
+++ b/src/common/net/win.h
@@ -102,10 +102,10 @@ static const char *os_error_string(int err)
return wsa_error_table[i].msg;
}
-static ssize_t os_udp_recv(netsrc_t sock, void *data,
+static ssize_t os_udp_recv(qsocket_t sock, void *data,
size_t len, netadr_t *from)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
int addrlen;
int ret;
int tries;
@@ -113,7 +113,7 @@ static ssize_t os_udp_recv(netsrc_t sock, void *data,
for (tries = 0; tries < MAX_ERROR_RETRIES; tries++) {
memset(&addr, 0, sizeof(addr));
addrlen = sizeof(addr);
- ret = recvfrom(udp_sockets[sock], data, len, 0,
+ ret = recvfrom(sock, data, len, 0,
(struct sockaddr *)&addr, &addrlen);
NET_SockadrToNetadr(&addr, from);
@@ -142,16 +142,17 @@ static ssize_t os_udp_recv(netsrc_t sock, void *data,
return NET_ERROR;
}
-static ssize_t os_udp_send(netsrc_t sock, const void *data,
+static ssize_t os_udp_send(qsocket_t sock, const void *data,
size_t len, const netadr_t *to)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
+ int addrlen;
int ret;
- NET_NetadrToSockadr(to, &addr);
+ addrlen = NET_NetadrToSockadr(to, &addr);
- ret = sendto(udp_sockets[sock], data, len, 0,
- (struct sockaddr *)&addr, sizeof(addr));
+ ret = sendto(sock, data, len, 0,
+ (struct sockaddr *)&addr, addrlen);
if (ret != SOCKET_ERROR)
return ret;
@@ -210,7 +211,7 @@ static neterr_t os_listen(qsocket_t sock, int backlog)
static neterr_t os_accept(qsocket_t sock, qsocket_t *newsock, netadr_t *from)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
int addrlen;
SOCKET s;
@@ -231,11 +232,12 @@ static neterr_t os_accept(qsocket_t sock, qsocket_t *newsock, netadr_t *from)
static neterr_t os_connect(qsocket_t sock, const netadr_t *to)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
+ int addrlen;
- NET_NetadrToSockadr(to, &addr);
+ addrlen = NET_NetadrToSockadr(to, &addr);
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) {
+ if (connect(sock, (struct sockaddr *)&addr, addrlen) == SOCKET_ERROR) {
net_error = WSAGetLastError();
if (net_error == WSAEWOULDBLOCK)
return NET_OK;
@@ -296,7 +298,7 @@ static neterr_t os_bind(qsocket_t sock, const struct sockaddr *addr, size_t addr
static neterr_t os_getsockname(qsocket_t sock, netadr_t *name)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
int addrlen;
memset(&addr, 0, sizeof(addr));