diff options
author | Andrey Nazarov <skuller@skuller.net> | 2008-10-30 17:12:38 +0000 |
---|---|---|
committer | Andrey Nazarov <skuller@skuller.net> | 2008-10-30 17:12:38 +0000 |
commit | d65e02496cd9873f5bb0297661b19762b44ddfec (patch) | |
tree | 3efd54fd5e823ed2f56b374c5ee3f2ecd65592e5 /source/net_common.c | |
parent | d95e79eb44efe2cd2e021527abbbf31933432375 (diff) |
Added ICMP error queue support on Linux.
Client no longer drops connection immediately after an ICMP error
is received, but waits some time for valid packets from server.
Fixed FreeBSD build.
Diffstat (limited to 'source/net_common.c')
-rw-r--r-- | source/net_common.c | 134 |
1 files changed, 130 insertions, 4 deletions
diff --git a/source/net_common.c b/source/net_common.c index 3ac70f6..249c02b 100644 --- a/source/net_common.c +++ b/source/net_common.c @@ -49,6 +49,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include <sys/uio.h> #include <errno.h> #include <arpa/inet.h> +#ifdef __linux__ +#include <linux/types.h> +#include <linux/errqueue.h> +#endif #define SOCKET int #define INVALID_SOCKET -1 #define closesocket close @@ -406,6 +410,96 @@ qboolean NET_GetLoopPacket( netsrc_t sock ) { #endif +#ifdef __unix__ + +static neterr_t get_icmp_error( int s ) { +#ifdef __linux__ + byte buffer[1024]; + struct sockaddr_in from; + struct msghdr msg; + struct cmsghdr *cmsg; + struct sock_extended_err *ee; + //struct sockaddr_in *off; + + memset( &from, 0, sizeof( from ) ); + + memset( &msg, 0, sizeof( msg ) ); + msg.msg_name = &from; + msg.msg_namelen = sizeof( from ); + msg.msg_control = buffer; + msg.msg_controllen = sizeof( buffer ); + + if( recvmsg( s, &msg, MSG_ERRQUEUE ) == -1 ) { + NET_GET_ERROR(); + + switch( net_error ) { + case EWOULDBLOCK: + // wouldblock is silent + break; + default: + Com_EPrintf( "%s: %s\n", __func__, NET_ErrorString() ); + } + + return NET_AGAIN; + } + + if( !( msg.msg_flags & MSG_ERRQUEUE ) ) { + Com_DPrintf( "%s: no extended error received\n", __func__ ); + return NET_AGAIN; + } + + // find an ICMP error message + for( cmsg = CMSG_FIRSTHDR( &msg ); + cmsg != NULL; + cmsg = CMSG_NXTHDR( &msg, cmsg ) ) + { + if( cmsg->cmsg_level != IPPROTO_IP ) { + continue; + } + if( cmsg->cmsg_type != IP_RECVERR ) { + continue; + } + ee = ( struct sock_extended_err * )CMSG_DATA( cmsg ); + if( ee->ee_origin == SO_EE_ORIGIN_ICMP ) { + break; + } + } + + if( !cmsg ) { + Com_DPrintf( "%s: no ICMP error found\n", __func__ ); + return NET_AGAIN; + } + + /* + off = ( struct sockaddr_in * )SO_EE_OFFENDER( err ); + if( off->sin_family == AF_INET ) { + } + */ + + NET_SockadrToNetadr( &from, &net_from ); + + // handle most common ICMP errors (defined in linux/net/ipv4/icmp.c) + net_error = ee->ee_errno; + switch( net_error ) { + case ENETUNREACH: + case EHOSTUNREACH: + case EHOSTDOWN: + case ECONNREFUSED: + case ENONET: + Com_DPrintf( "%s: %s from %s\n", __func__, + NET_ErrorString(), NET_AdrToString( &net_from ) ); + return NET_ERROR; + default: + Com_EPrintf( "%s: %s from %s\n", __func__, + NET_ErrorString(), NET_AdrToString( &net_from ) ); + } +#endif + + return NET_AGAIN; +} + +#endif // __unix__ + /* ============= NET_GetPacket @@ -426,6 +520,8 @@ neterr_t NET_GetPacket( netsrc_t sock ) { NET_UpdateStats(); + memset( &from, 0, sizeof( from ) ); + fromlen = sizeof( from ); ret = recvfrom( udp_sockets[sock], msg_read_buffer, MAX_PACKETLEN, 0, ( struct sockaddr * )&from, &fromlen ); @@ -465,11 +561,15 @@ neterr_t NET_GetPacket( netsrc_t sock ) { case EWOULDBLOCK: // wouldblock is silent break; + case ENETUNREACH: + case EHOSTUNREACH: + case EHOSTDOWN: case ECONNREFUSED: - Com_DPrintf( "%s: %s from %s\n", __func__, - NET_ErrorString(), NET_AdrToString( &net_from ) ); + case ENONET: + //Com_DPrintf( "%s: %s from %s\n", __func__, + // NET_ErrorString(), NET_AdrToString( &net_from ) ); if( !net_ignore_icmp->integer ) { - return NET_ERROR; + return get_icmp_error( udp_sockets[sock] ); } break; default: @@ -598,10 +698,12 @@ neterr_t NET_SendPacket( netsrc_t sock, const netadr_t *to, size_t length, const case EWOULDBLOCK: // wouldblock is silent break; + case ECONNREFUSED: case ECONNRESET: case EHOSTUNREACH: case ENETUNREACH: case ENETDOWN: + case EPERM: // not ICMP, but local firewall Com_DPrintf( "%s: %s to %s\n", __func__, NET_ErrorString(), NET_AdrToString( to ) ); if( !net_ignore_icmp->integer ) { @@ -685,6 +787,18 @@ static SOCKET UDP_OpenSocket( const char *interface, int port ) { goto fail; } +#ifdef __linux__ + // enable ICMP error queue + if( !net_ignore_icmp->integer ) { + _true = 1; + if( setsockopt( newsocket, IPPROTO_IP, IP_RECVERR, + ( char * )&_true, sizeof( _true ) ) == -1 ) + { + goto fail; + } + } +#endif + if( !NET_StringToIface( interface, &address ) ) { Com_Printf( "Bad interface address: %s\n", interface ); goto fail; @@ -729,7 +843,7 @@ static SOCKET TCP_OpenSocket( const char *interface, int port, netsrc_t who ) { if( who == NS_SERVER ) { _true = 1; if( setsockopt( newsocket, SOL_SOCKET, SO_REUSEADDR, - ( char * )&_true, sizeof( _true ) ) == -1 ) + ( char * )&_true, sizeof( _true ) ) == -1 ) { goto fail; } @@ -756,8 +870,18 @@ fail: } static void NET_OpenServer( void ) { + static int saved_port; + udp_sockets[NS_SERVER] = UDP_OpenSocket( net_ip->string, net_port->integer ); if( udp_sockets[NS_SERVER] != INVALID_SOCKET ) { + saved_port = net_port->integer; + return; + } + + if( saved_port && saved_port != net_port->integer ) { + // revert to the last valid port + Com_Printf( "Reverting to the last valid port %d...\n", saved_port ); + Cbuf_AddText( va( "set net_port %d\n", saved_port ) ); return; } @@ -1236,6 +1360,8 @@ static void NET_Restart_f( void ) { netflag_t flag = net_active; SOCKET sock = tcp_socket; + Com_DPrintf( "%s\n", __func__ ); + if( sock != INVALID_SOCKET ) { NET_Listen( qfalse ); } |