Add a cvar net_sourceaddresscheck (default 1) to ignore connect replies from the...
authordivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Mon, 27 Jun 2016 12:49:37 +0000 (12:49 +0000)
committerdivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Mon, 27 Jun 2016 12:49:37 +0000 (12:49 +0000)
This fixes some existing easy connect takeover exploits (which can,
without this check, even attack local matches).

From: Rudolf Polzer <divVerent@xonotic.org>

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12258 d7cf8633-e32d-0410-b094-e92efae38249

client.h
crypto.c
host_cmd.c
netconn.c

index d9facec..4b6e566 100644 (file)
--- a/client.h
+++ b/client.h
@@ -834,6 +834,7 @@ typedef struct client_static_s
        double connect_nextsendtime;
        lhnetsocket_t *connect_mysocket;
        lhnetaddress_t connect_address;
+       lhnetaddress_t rcon_address;
        // protocol version of the server we're connected to
        // (kept outside client_state_t because it's used between levels)
        protocolversion_t protocol;
index 7948953..00bcf0a 100644 (file)
--- a/crypto.c
+++ b/crypto.c
@@ -7,16 +7,21 @@
 #include "libcurl.h"
 
 cvar_t crypto_developer = {CVAR_SAVE, "crypto_developer", "0", "print extra info about crypto handshake"};
+cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"};
+
 cvar_t crypto_servercpupercent = {CVAR_SAVE, "crypto_servercpupercent", "10", "allowed crypto CPU load in percent for server operation (0 = no limit, faster)"};
 cvar_t crypto_servercpumaxtime = {CVAR_SAVE, "crypto_servercpumaxtime", "0.01", "maximum allowed crypto CPU time per frame (0 = no limit)"};
 cvar_t crypto_servercpudebug = {CVAR_SAVE, "crypto_servercpudebug", "0", "print statistics about time usage by crypto"};
 static double crypto_servercpu_accumulator = 0;
 static double crypto_servercpu_lastrealtime = 0;
-cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"};
+
+extern cvar_t net_sourceaddresscheck;
+
 int crypto_keyfp_recommended_length;
 static const char *crypto_idstring = NULL;
 static char crypto_idstring_buf[512];
 
+
 #define PROTOCOL_D0_BLIND_ID FOURCC_D0PK
 #define PROTOCOL_VLEN (('v' << 0) | ('l' << 8) | ('e' << 16) | ('n' << 24))
 
@@ -1727,7 +1732,7 @@ static int Crypto_ServerParsePacket_Internal(const char *data_in, size_t len_in,
                                        break;
                // if the challenge is not recognized, drop the packet
                if (i == MAX_CHALLENGES) // challenge mismatch is silent
-                       return CRYPTO_DISCARD; // pre-challenge: rather be silent
+                       return Crypto_SoftServerError(data_out, len_out, "missing challenge in connect");
 
                crypto = Crypto_ServerFindInstance(peeraddress, false);
                if(!crypto || !crypto->authenticated)
@@ -1742,23 +1747,23 @@ static int Crypto_ServerParsePacket_Internal(const char *data_in, size_t len_in,
                id = (cnt ? atoi(cnt) : -1);
                cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue));
                if(!cnt)
-                       return CRYPTO_DISCARD; // pre-challenge: rather be silent
+                       return Crypto_SoftServerError(data_out, len_out, "missing cnt in d0pk");
                GetUntilNul(&data_in, &len_in);
                if(!data_in)
-                       return CRYPTO_DISCARD; // pre-challenge: rather be silent
+                       return Crypto_SoftServerError(data_out, len_out, "missing appended data in d0pk");
                if(!strcmp(cnt, "0"))
                {
                        int i;
                        if (!(s = InfoString_GetValue(string + 4, "challenge", infostringvalue, sizeof(infostringvalue))))
-                               return CRYPTO_DISCARD; // pre-challenge: rather be silent
+                               return Crypto_SoftServerError(data_out, len_out, "missing challenge in d0pk\\0");
                        // validate the challenge
                        for (i = 0;i < MAX_CHALLENGES;i++)
                                if(challenge[i].time > 0)
                                        if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s))
                                                break;
                        // if the challenge is not recognized, drop the packet
-                       if (i == MAX_CHALLENGES) // challenge mismatch is silent
-                               return CRYPTO_DISCARD; // pre-challenge: rather be silent
+                       if (i == MAX_CHALLENGES)
+                               return Crypto_SoftServerError(data_out, len_out, "invalid challenge in d0pk\\0");
 
                        if (!(s = InfoString_GetValue(string + 4, "aeslevel", infostringvalue, sizeof(infostringvalue))))
                                aeslevel = 0; // not supported
@@ -2075,7 +2080,7 @@ static int Crypto_ClientError(char *data_out, size_t *len_out, const char *msg)
 static int Crypto_SoftClientError(char *data_out, size_t *len_out, const char *msg)
 {
        *len_out = 0;
-       Con_Printf("%s\n", msg);
+       Con_DPrintf("%s\n", msg);
        return CRYPTO_DISCARD;
 }
 
@@ -2185,6 +2190,10 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                int wantserver_aeslevel = 0;
                qboolean wantserver_issigned = false;
 
+               // Must check the source IP here, if we want to prevent other servers' replies from falsely advancing the crypto state, preventing successful connect to the real server.
+               if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
+                       return Crypto_SoftClientError(data_out, len_out, "challenge message from wrong server");
+
                // if we have a stored host key for the server, assume serverid to already be selected!
                // (the loop will refuse to overwrite this one then)
                wantserver_idfp[0] = 0;
@@ -2201,7 +2210,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                GetUntilNul(&data_in, &len_in);
                if(!data_in)
                        return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present") :
-                               (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) :
+                               (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") :
                                CRYPTO_NOMATCH;
 
                // FTEQW extension protocol
@@ -2237,7 +2246,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
 
                if(!vlen_blind_id_ptr)
                        return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though authentication is required") :
-                               (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) :
+                               (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") :
                                CRYPTO_NOMATCH;
 
                data_in = vlen_blind_id_ptr;
@@ -2302,7 +2311,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                                default: // dummy, never happens, but to make gcc happy...
                                case 0:
                                        if(wantserver_aeslevel >= 3)
-                                               return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL);
+                                               return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)");
                                        CDATA->wantserver_aes = false;
                                        break;
                                case 1:
@@ -2313,7 +2322,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                                        break;
                                case 3:
                                        if(wantserver_aeslevel <= 0)
-                                               return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL);
+                                               return Crypto_ClientError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)");
                                        CDATA->wantserver_aes = true;
                                        break;
                        }
@@ -2373,7 +2382,6 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                                data_out_p += *len_out;
                                *len_out = data_out_p - data_out;
                        }
-
                        return CRYPTO_DISCARD;
                }
                else
@@ -2381,7 +2389,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                        if(wantserver_idfp[0]) // if we know a host key, honor its encryption setting
                        if(wantserver_aeslevel >= 3)
                                return Crypto_ClientError(data_out, len_out, "Server insists on encryption, but neither can authenticate to the other");
-                       return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) :
+                       return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") :
                                CRYPTO_NOMATCH;
                }
        }
@@ -2389,6 +2397,11 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
        {
                const char *cnt;
                int id;
+
+               // Must check the source IP here, if we want to prevent other servers' replies from falsely advancing the crypto state, preventing successful connect to the real server.
+               if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
+                       return Crypto_SoftClientError(data_out, len_out, "d0pk\\ message from wrong server");
+
                cnt = InfoString_GetValue(string + 4, "id", infostringvalue, sizeof(infostringvalue));
                id = (cnt ? atoi(cnt) : -1);
                cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue));
@@ -2402,7 +2415,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
                {
                        if(id >= 0)
                                if(CDATA->cdata_id != id)
-                                       return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
+                                       return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
                        if(CDATA->next_step != 1)
                                return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step));
 
@@ -2451,7 +2464,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
 
                        if(id >= 0)
                                if(CDATA->cdata_id != id)
-                                       return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
+                                       return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
                        if(CDATA->next_step != 3)
                                return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step));
 
@@ -2533,7 +2546,7 @@ int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out,
 
                        if(id >= 0)
                                if(CDATA->cdata_id != id)
-                                       return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
+                                       return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
                        if(CDATA->next_step != 5)
                                return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step));
 
index 10012cc..1f4048f 100644 (file)
@@ -2552,9 +2552,7 @@ static void Host_PQRcon_f (void)
 {
        int n;
        const char *e;
-       lhnetaddress_t to;
        lhnetsocket_t *mysocket;
-       char peer_address[64];
 
        if (Cmd_Argc() == 1)
        {
@@ -2572,9 +2570,7 @@ static void Host_PQRcon_f (void)
        n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
 
        if (cls.netcon)
-       {
-               InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
-       }
+               cls.rcon_address = cls.netcon->peeraddress;
        else
        {
                if (!rcon_address.string[0])
@@ -2582,10 +2578,9 @@ static void Host_PQRcon_f (void)
                        Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
                        return;
                }
-               strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
+               LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
        }
-       LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
-       mysocket = NetConn_ChooseClientSocketForAddress(&to);
+       mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
        if (mysocket)
        {
                sizebuf_t buf;
@@ -2598,7 +2593,7 @@ static void Host_PQRcon_f (void)
                MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
                MSG_WriteString(&buf, Cmd_Args());
                StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
-               NetConn_Write(mysocket, buf.data, buf.cursize, &to);
+               NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
                SZ_Clear(&buf);
        }
 }
@@ -2619,7 +2614,6 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld
 {
        int i, n;
        const char *e;
-       lhnetaddress_t to;
        lhnetsocket_t *mysocket;
        char vabuf[1024];
 
@@ -2639,7 +2633,7 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld
        n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
 
        if (cls.netcon)
-               to = cls.netcon->peeraddress;
+               cls.rcon_address = cls.netcon->peeraddress;
        else
        {
                if (!rcon_address.string[0])
@@ -2647,9 +2641,9 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld
                        Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
                        return;
                }
-               LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
+               LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
        }
-       mysocket = NetConn_ChooseClientSocketForAddress(&to);
+       mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
        if (mysocket && Cmd_Args()[0])
        {
                // simply put together the rcon packet and send it
@@ -2665,13 +2659,13 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld
                        }
                        for (i = 0;i < MAX_RCONS;i++)
                                if(cls.rcon_commands[i][0])
-                                       if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
+                                       if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
                                                break;
                        ++cls.rcon_trying;
                        if(i >= MAX_RCONS)
-                               NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
+                               NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
                        strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
-                       cls.rcon_addresses[cls.rcon_ringpos] = to;
+                       cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
                        cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
                        cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
                }
@@ -2685,12 +2679,12 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld
                        {
                                buf[40] = ' ';
                                strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
-                               NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &to);
+                               NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
                        }
                }
                else
                {
-                       NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
+                       NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &cls.rcon_address);
                }
        }
 }
index a01c174..0d9f69e 100755 (executable)
--- a/netconn.c
+++ b/netconn.c
@@ -82,6 +82,7 @@ cvar_t net_connecttimeout = {0, "net_connecttimeout","15", "after requesting a c
 cvar_t net_connectfloodblockingtimeout = {0, "net_connectfloodblockingtimeout", "5", "when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods). Note that this does not include retries from the same IP; these are handled earlier and let in."};
 cvar_t net_challengefloodblockingtimeout = {0, "net_challengefloodblockingtimeout", "0.5", "when a challenge packet is received, it will block all future challenge packets from that IP address for this many seconds (cuts down on challenge floods). DarkPlaces clients retry once per second, so this should be <= 1. Failure here may lead to connect attempts failing."};
 cvar_t net_getstatusfloodblockingtimeout = {0, "net_getstatusfloodblockingtimeout", "1", "when a getstatus packet is received, it will block all future getstatus packets from that IP address for this many seconds (cuts down on getstatus floods). DarkPlaces retries every 4 seconds, and qstat retries once per second, so this should be <= 1. Failure here may lead to server not showing up in the server list."};
+cvar_t net_sourceaddresscheck = {0, "net_sourceaddresscheck", "1", "compare the source IP address for replies (more secure, may break some bad multihoming setups"};
 cvar_t hostname = {CVAR_SAVE, "hostname", "UNNAMED", "server message to show in server browser"};
 cvar_t developer_networking = {0, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"};
 
@@ -1904,8 +1905,12 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                {
                        // darkplaces or quake3
                        char protocolnames[1400];
-                       Protocol_Names(protocolnames, sizeof(protocolnames));
                        Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2);
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+                               Con_DPrintf("challenge message from wrong server %s\n", addressstring2);
+                               return true;
+                       }
+                       Protocol_Names(protocolnames, sizeof(protocolnames));
 #ifdef CONFIG_MENU
                        M_Update_Return_Reason("Got challenge response");
 #endif
@@ -1918,6 +1923,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying)
                {
                        // darkplaces or quake3
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+                               Con_DPrintf("accept message from wrong server %s\n", addressstring2);
+                               return true;
+                       }
 #ifdef CONFIG_MENU
                        M_Update_Return_Reason("Accepted");
 #endif
@@ -1927,6 +1936,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying)
                {
                        char rejectreason[128];
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+                               Con_DPrintf("reject message from wrong server %s\n", addressstring2);
+                               return true;
+                       }
                        cls.connect_trying = false;
                        string += 7;
                        length = min(length - 7, (int)sizeof(rejectreason) - 1);
@@ -2097,6 +2110,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying)
                {
                        // challenge message
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+                               Con_DPrintf("c message from wrong server %s\n", addressstring2);
+                               return true;
+                       }
                        Con_Printf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2);
 #ifdef CONFIG_MENU
                        M_Update_Return_Reason("Got QuakeWorld challenge response");
@@ -2110,6 +2127,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                if (length >= 1 && string[0] == 'j' && cls.connect_trying)
                {
                        // accept message
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+                               Con_DPrintf("j message from wrong server %s\n", addressstring2);
+                               return true;
+                       }
 #ifdef CONFIG_MENU
                        M_Update_Return_Reason("QuakeWorld Accepted");
 #endif
@@ -2167,7 +2188,11 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                }
                if (string[0] == 'n')
                {
-                       // qw print command
+                       // qw print command, used by rcon replies too
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address) && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) {
+                               Con_DPrintf("n message from wrong server %s\n", addressstring2);
+                               return true;
+                       }
                        Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1);
                }
                // we may not have liked the packet, but it was a command packet, so
@@ -2203,6 +2228,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                        if (cls.connect_trying)
                        {
                                lhnetaddress_t clientportaddress;
+                               if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+                                       Con_DPrintf("CCREP_ACCEPT message from wrong server %s\n", addressstring2);
+                                       break;
+                               }
                                clientportaddress = *peeraddress;
                                LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong(&cl_message));
                                // extra ProQuake stuff
@@ -2229,8 +2258,12 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
                        }
                        break;
                case CCREP_REJECT:
-                       if (developer_extra.integer)
-                               Con_DPrintf("Datagram_ParseConnectionless: received CCREP_REJECT from %s.\n", addressstring2);
+                       if (developer_extra.integer) {
+                               Con_DPrintf("CCREP_REJECT message from wrong server %s\n", addressstring2);
+                               break;
+                       }
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
+                               break;
                        cls.connect_trying = false;
 #ifdef CONFIG_MENU
                        M_Update_Return_Reason((char *)MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
@@ -2261,6 +2294,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
 #endif
                        break;
                case CCREP_RCON: // RocketGuy: ProQuake rcon support
+                       if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) {
+                               Con_DPrintf("CCREP_RCON message from wrong server %s\n", addressstring2);
+                               break;
+                       }
                        if (developer_extra.integer)
                                Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2);
 
@@ -3835,6 +3872,7 @@ void NetConn_Init(void)
        Cvar_RegisterVariable(&net_connectfloodblockingtimeout);
        Cvar_RegisterVariable(&net_challengefloodblockingtimeout);
        Cvar_RegisterVariable(&net_getstatusfloodblockingtimeout);
+       Cvar_RegisterVariable(&net_sourceaddresscheck);
        Cvar_RegisterVariable(&cl_netlocalping);
        Cvar_RegisterVariable(&cl_netpacketloss_send);
        Cvar_RegisterVariable(&cl_netpacketloss_receive);