PDA

View Full Version : [Q3 FIX] Q3 fake clients fix



RobsoN
14th January 2014, 18:24
Hello, I want to share my Q3fill fix.
Script will kick player which tries to connect to the server when other player on server has same IP address.
The code is not diffucult but it can help to prevent annoying server haters ^^.

NOTICE: Requires libcod!



notifyConnecting()
{
self endon("disconnect");

waittillframeend;

if(isDefined(self))
{
level notify("connecting",self);
setQ3Fix(); //Add this line
}


}
setQ3Fix()
{
if(!isDefined(level.q3_fix))
level.q3_fix = [];

my_ip = getIP(self);

if(isDefined(level.q3_fix[my_ip]) && !self.data["bot"])
{
self setClientCvar("com_errorTitle", "Error"); //Maybe somehow system will fail (it should'nt but we should always assume that can happens) and a real client can be kicked.
self setClientCvar("com_errorMessage", "IP duplicate detected"); //So let him know, what went wrong.
self.kicked = true;
println("Warning: Kicking client with IP duplicate: "+my_ip);
kick(self getEntityNumber());
return true;
}

if(!self.data["bot"])
level.q3_fix[my_ip] = true;

return false;
}
getIP(client)
{
return closer(430, client getEntityNumber());
}
println(msg)
{
closer(200, msg + "\n");
}
Callback_PlayerDisconnect()
{
if(!isDefined(self.kicked))
level.q3_fix[getIP(self)] = undefined;
[...]
}

Results:



Going from CS_FREE to CS_CONNECTED for (num 0 guid 0)
Sending heartbeat to cod2master.activision.com
Client 3 connecting with 50 challenge ping from SOMEIP:SOMEPORT
Going from CS_FREE to CS_CONNECTED for (num 1 guid 0)
Warning: Kicking client with IP duplicate: SOMEIP
WARNING: Non-localized Game Message string is not allowed to have letters in it. Must be changed over to a localized string: "^4*^7 t0nj3WO345oa ^7Ha^9s ^7Disc^9onnected ^7Fr^9om ^4*^7eXtremE^2~^9ZOMBIES^7"
1: EXE_PLAYERKICKED


You can see self.data["bot"] which is boolean in my case. You should define your own variable to know if connecting entity is a bot (to prevent testclient kicking).
Also you can add a line which adds kicked IP into a logfile. Then you can easily add it manually into firewall (or do whatever you need).

If you got any questions or objections to my code please let me know below :)

Edit#1: I forget to add array undefining when a real player disconnects

Mitch
14th January 2014, 18:48
Maybe add a 10 or 15 seconde delay and add endon on 'begin'. When you receive 'begin' then it isn't a fake client.

Edit: is self defined when it is a fake client?

RobsoN
14th January 2014, 19:08
Maybe add a 10 or 15 seconde delay and add endon on 'begin'. When you receive 'begin' then it isn't a fake client.
I didn't tested anything with "begin" notify but I will soon.


Edit: is self defined when it is a fake client?
Yeah, self is defined.

Mitch
11th February 2014, 18:59
This is my fix. It requires my libcod version with lastmsg.



kickFakeClients()
{
self endon("begin");

wait 15;

lastmsg = std\player::getLastMsg();

if(lastmsg > 50000)
{
ip = std\player::getIP();
iprintln(self.name + "^7 connection timeout.");
std\io::println("[CONNECTION TIMEOUT]: " + self.name + " (" + ip + ")");
kick(self getEntityNumber());
}
}


std\player


getLastMsg() { return closer(432, self getEntityNumber()); }


libcod: http://znation.nl/libcod/
https://github.com/M-itch/libcod/commit/46e5db5f63b0ca3283907769f192938c9b93ec4e

Mitch
12th February 2014, 20:21
I improved my code a bit


main()
{
level.connectingip = [];
}



kickFakeClients(ip)
{
self endon("begin");
self endon("disconnect");

delay = 5;
max = 5000;

if(!isDefined(level.connectingip[ip])) // less strict for first connection
{
level.connectingip[ip] = 1;
max = 50000;
delay = 30;
}

wait delay;

lastmsg = std\player::getLastMsg();

if(lastmsg > max)
{
if(delay == 30 && isDefined(level.connectingip[ip]))
level.connectingip[ip] = undefined; // prevent strict rules for crashed clients

iprintln(self.name + "^7 connection timeout.");
std\io::println("[CONNECTION TIMEOUT]: " + self.name + " (" + ip + ")");
kick(self getEntityNumber());
}
}



ip = std\player::getIP();
self thread kickFakeClients(ip);
self.statusicon = "hud_status_connecting";
self waittill("begin");
self.statusicon = "";

if(isDefined(level.connectingip[ip]))
level.connectingip[ip] = undefined;

default
18th February 2014, 22:16
how to add :P

Mitch
18th February 2014, 23:06
how to add :P

I have it like this:


Callback_PlayerConnect()
{
thread dummy();

ip = std\player::getIP();
self thread kickFakeClients(ip);
self PlayerConnected();

But you can also add it to CodeCallback_PlayerConnect() when you use multiple gametypes.

IzNoGoD
23rd February 2014, 15:15
Dont set max on 50k if your wait is 30sec, lastmsg is number of ms that a client hasnt responded.

Set it to 29k if you wait 30s, same goes for 5 sec and 4.9k

Mitch
26th February 2014, 14:05
I found a better way to detect fake clients. It also uses last msg but also the last connect time.
The fake clients only connect to the server and nothing else. So after they connect the lastmsg time is the same as the last connect time.

This is part of the last function they run:


Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name );

newcl->state = CS_CONNECTED;
newcl->nextSnapshotTime = svs.time;
newcl->lastPacketTime = svs.time;
newcl->lastConnectTime = svs.time;


Proof


Fake clients:
[ip] connect: 1050/1050 (4GtXwfgRl3)
[ip] connect: 1050/1050 (2S)
[ip] connect: 1050/1050 (5II)
[ip] connect: 6050/6050 (Z4sjLDJe4)
[ip] connect: 6050/6050 (4GtXwfgRl3)
[ip] connect: 6050/6050 (2S)
[ip] connect: 6050/6050 (5II)

Me:
[ip] connect: 1050/1000 (Mitch||OwnZ. ^7[^456^7]) (after 1 s)
[ip] connect: 6050/4000 (Mitch||OwnZ. ^7[^456^7]) (after 6 s)




lastconnect = std\player::getLastConnectTime();
lastmsg = std\player::getLastMsg();
std\io::println("[" + ip + "] connect: " + lastconnect + "/" + lastmsg + " (" + self.name + ")");




getLastConnectTime() { return closer(433, self getEntityNumber()); }

https://github.com/M-itch/libcod/commit/cb44c3594f9d20380c67bad9528d28c88ac75c45
http://znation.nl/libcod



kickFakeClients(ip)
{
self endon("begin");
self endon("disconnect");

if(self getGuid() != 0)
return; // cd key can only be used once

wait 5;

lastconnect = std\player::getLastConnectTime();
lastmsg = std\player::getLastMsg();

if(lastmsg == lastconnect)
{
ip = std\player::getIP();
std\io::println("[" + ip + "] connect: " + lastconnect + "/" + lastmsg + " (" + self.name + ")");
iprintln(self.name + "^7 connection timeout.");
kick(self getEntityNumber());
}
}

RobsoN
26th February 2014, 14:23
Well done, thanks for sharing this Mitch!

IzNoGoD
26th February 2014, 14:31
Another thing to consider is that players that have not spawned have classname "noclass".

Might be useful too, instead of using the waittill begin.

Mitch
26th February 2014, 14:50
Well done, thanks for sharing this Mitch!

Thanks. I just edit my code so people with a valid guid are always accepted.

I am now using iptables to limit the number of connect each second. (same rules as limiting getstatus)



iptables -N CQUERY-BLOCK
iptables -A CQUERY-BLOCK -m recent --set --name cblocked-hosts -j DROP
iptables -N CQUERY-CHECK
iptables -A CQUERY-CHECK -p udp -m string ! --string "connect" --algo bm --from 32 --to 39 -j RETURN
iptables -A CQUERY-CHECK -m recent --update --name cblocked-hosts --seconds 30 --hitcount 1 -j DROP
iptables -A CQUERY-CHECK -m hashlimit --hashlimit-mode srcip --hashlimit-name cod2connect --hashlimit-above 2/second -j CQUERY-BLOCK
iptables -A INPUT -p udp --dport 27960:29000 -j CQUERY-CHECK

Mitch
14th August 2014, 14:03
This is my latest version with the new libcod functions.



kickFakeClients()
{
self endon("begin");
self endon("disconnect");

if(self getGuid() != 0)
return; // cd key can only be used once

wait 5;

lastconnect = self getLastConnectTime();
lastmsg = self getLastMSG();

if(lastmsg == lastconnect)
{
ip = self getIP();

if(isDefined(level.lastconnectedplayerip) && level.lastconnectedplayerip == ip)
self kickFakeClient(ip, lastconnect, lastmsg);
else // more time when IP isn't the same as previous ip
{
level.lastconnectedplayerip = ip;
wait 5;

lastconnect = self getLastConnectTime();
lastmsg = self getLastMSG();

if(lastmsg == lastconnect)
self kickFakeClient(ip, lastconnect, lastmsg);
}
}
}

kickFakeClient(ip, lastconnect, lastmsg)
{
printfline("[" + ip + "] connect: " + lastconnect + "/" + lastmsg + " (" + self.name + ")");
iprintln(self.name + "^7 connection timeout.");
kick(self getEntityNumber());
}




self thread kickFakeClients();

self.statusicon = "hud_status_connecting";
self waittill("begin");
self.statusicon = "";

Mitch
25th November 2014, 10:15
With the commit https://github.com/M-itch/libcod/commit/f1cdb2c25e25adc05f96df84055541c3be6c1969 you can request the state of a player with getClientState. The state of a fake player is always CS_CONNECTED.



kickFakeClients()
{
self endon("begin");
self endon("disconnect");

if(self getGuid() != 0)
return; // cd key can only be used once

wait 0.05;

if(self getClientState() >= 3) // CS_PRIMED (3) or CS_ACTIVE (4)
return; // a fake player's state is always CS_CONNECTED (2)

wait 5;

if(self getClientState() == 2)
{
ip = self getIP();
lastconnect = self getLastConnectTime();
lastmsg = self getLastMSG();
self kickFakeClient(ip, lastconnect, lastmsg);
}
}

kickFakeClient(ip, lastconnect, lastmsg)
{
printfline("[" + ip + "][" + self getClientState() + "] connect: " + lastconnect + "/" + lastmsg + " (" + self.name + ")");
iprintln(self.name + "^7 connection timeout.");
kick(self getEntityNumber());
}

guiismiti
31st January 2015, 17:32
In Brazil a considerable number of people still play CoD in cyber cafes. If two friends decide to play in a protected server, they won't be able to.

But it's just a small consequence anyway.

Ni3ls
8th June 2015, 13:14
Its not working for me. All my servers are being filled with bots. I added the script. THey do get kicked, but they connect faster than they get kicked. So server is still being filled. Any ideas to just prevent them even from connecting? The IP: 94.255.70.95

Mitch
8th June 2015, 13:23
Its not working for me. All my servers are being filled with bots. I added the script. THey do get kicked, but they connect faster than they get kicked. So server is still being filled. Any ideas to just prevent them even from connecting? The IP: 94.255.70.95

You could try blocking the connect packets with the iptables from this post:
http://killtube.org/showthread.php?1767-Q3-FIX-Q3-fake-clients-fix&p=9259&viewfull=1#post9259

Or the new challenge rate limiter from this commit:
https://github.com/M-itch/libcod/commit/f89b31fdbc5575a0579fe1c3dc908ae4ef702581

Ni3ls
8th June 2015, 13:31
How can I use/add that commit?

Mitch
8th June 2015, 13:57
How can I use/add that commit?

You could try my builds for Ubuntu (they should also work under Debian):
https://znation.nl/libcod/ubuntu/

Or checkout my github.

git checkout https://github.com/M-itch/libcod.git libcod
cd libcod
./doit.sh base
./doit.sh cod2_1_0

Ni3ls
8th June 2015, 14:28
I added your build + the last script with getState. Still got 10 bots in the server.

http://service.killtube.org/?ip=46.4.55.66&port=28970&resource=http://tracker.killtube.org/iframe-styles/frontpage

I dont have root access so I cant add the iptables. He is also using different IP's now

Mitch
8th June 2015, 15:11
I added your build + the last script with getState. Still got 10 bots in the server.

http://service.killtube.org/?ip=46.4.55.66&port=28970&resource=http://tracker.killtube.org/iframe-styles/frontpage

I dont have root access so I cant add the iptables. He is also using different IP's now

Then i will need to add a rate limiter for connect too.

Edit: it is harder to make than i thought. I expect that the current code will also block other players from joining as well.

Ni3ls
8th June 2015, 15:25
A lot of servers in COD2 1.0 are filled now... Some russian kid is doing this. Max 5 bots are added now, so its already a big improvement :)

Mitch
8th June 2015, 15:58
A lot of servers in COD2 1.0 are filled now... Some russian kid is doing this. Max 5 bots are added now, so its already a big improvement :)

I guess that there is no need for connect rate limiter.

This might limit the fake clients a little better. It will allow ~5 challenge packets per second.


int hook_SV_GetChallenge(netadr_t from)
{
// Prevent using getchallenge as an amplifier
if ( SVC_RateLimitAddress( from, 5, 1000 ) ) {


The limiter stores the burst of the packets. The challenge limiter is also affected by other packets like getinfo, getstatus, rcon.
For example, 2x getinfo, 2x getstatus, 1x rcon and 1x getchallenge in one second. With the above code the challenge will be dropped, because it is packet #6.

Ni3ls
8th June 2015, 16:28
And how to use / add this? :P

Mitch
8th June 2015, 17:12
And how to use / add this? :P

Maybe for now it might be easier to tweak the fake bot kick script.

Now the server will wait 5 seconds for the first fake client. But after kicking this bot, the server will kick the clients from that IP after 1.5 seconds.


kickFakeClients()
{
self endon("begin");
self endon("disconnect");

if(self getGuid() != 0)
return; // cd key can only be used once

wait 0.05;

if(self getClientState() >= 3) // CS_PRIMED (3) or CS_ACTIVE (4)
return; // a fake player's state is always CS_CONNECTED (2)

ip = self getIP();
if(isDefined(level.lastfakeplayerip) && level.lastfakeplayerip == ip)
wait 1.5;
else // more time when IP isn't the same as previous ip
wait 5;

if(self getClientState() == 2)
{
lastconnect = self getLastConnectTime();
lastmsg = self getLastMSG();
if(lastconnect == lastmsg)
{
level.lastfakeplayerip = ip; // store ip from fake client
self kickFakeClient(ip, lastconnect, lastmsg);
}
}
}

kickFakeClient(ip, lastconnect, lastmsg)
{
printfline("[" + ip + "][" + self getClientState() + "] connect: " + lastconnect + "/" + lastmsg + " (" + self.name + ")");
iprintln(self.name + "^7 connection timeout.");
kick(self getEntityNumber());
}


How to use:


Callback_PlayerConnect()
{
thread dummy();

self thread kickFakeClients();
self PlayerConnected();

You can add it to CodeCallback_PlayerConnect() when you are use multiple gametypes.

Ni3ls
8th June 2015, 17:20
But the bots connect every 0.05 seconds. Thats the smallest delay

Mitch
8th June 2015, 19:49
But the bots connect every 0.05 seconds. Thats the smallest delay

If the rate limiter limits the connecting bots to 5 each second then you could try changing the wait to 0.15.

if(isDefined(level.lastfakeplayerip) && level.lastfakeplayerip == ip)
wait 0.15;

Ni3ls
13th August 2015, 11:37
Again a filler.
Mitch's script works perfectly!
IP of filler: http://www.ip-tracker.org/locator/ip-lookup.php?ip=80.25.150.100

New IP of the filler:
109.26.225.58