diff options
Diffstat (limited to 'src/common/net/net.c')
-rw-r--r-- | src/common/net/net.c | 851 |
1 files changed, 607 insertions, 244 deletions
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 |