Jump to content

Rate Limiting in PHP Websockets


Monkuar

Recommended Posts


function wsProcessClientMessage($clientID, $opcode, &$data, $dataLength) {
global $wsClients;
 
// check opcodes
if ($opcode == WS_OPCODE_PING) {
// received ping message
return wsSendClientMessage($clientID, WS_OPCODE_PONG, $data);
}
elseif ($opcode == WS_OPCODE_PONG) {
// received pong message (it's valid if the server did not send a ping request for this pong message)
if ($wsClients[$clientID][4] !== false) {
$wsClients[$clientID][4] = false;
}
}
elseif ($opcode == WS_OPCODE_CLOSE) {
// received close message
if (substr($data, 1, 1) !== false) {
$array = unpack('na', substr($data, 0, 2));
$status = $array['a'];
}
else {
$status = false;
}
 
if ($wsClients[$clientID][2] == WS_READY_STATE_CLOSING) {
// the server already sent a close frame to the client, this is the client's close frame reply
// (no need to send another close frame to the client)
$wsClients[$clientID][2] = WS_READY_STATE_CLOSED;
}
else {
// the server has not already sent a close frame to the client, send one now
wsSendClientClose($clientID, WS_STATUS_NORMAL_CLOSE);
}
 
wsRemoveClient($clientID);
}
elseif ($opcode == WS_OPCODE_TEXT || $opcode == WS_OPCODE_BINARY) {
// received text or binary message
if (function_exists('wsOnMessage')) wsOnMessage($clientID, $data, $dataLength, $opcode == WS_OPCODE_BINARY);
}
else {
// unknown opcode
return false;
}
 
return true;
}
 
 
 

 

Okay, this is the code that processes the client messages.   It's extracted from here: http://code.google.com/p/phpwebsocket/ and I use it.

 

My problem is. I call a mysql select on a function via my websockets using this:

 

 

 


cb.socket.send('LOOTITEM 2213123');

 

 

 

 

(The CB class just connects it to the socket and sends it off)

 

But I have a problem.  I can crash my own server, just by going into Google Chromes console tab and spamming this:

 

 


cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');cb.socket.send('LOOTITEM 2213123');

 

Then I press enter, and look at my console. And a shit ton of queries are instantly checked and then it pops and says:

 

33996f94672e498f0fd461dfa63989cb.png

 

So, this is way bad. Because people can just do this and crash the server. I need to limit the requests to atleast 1 second. Any idea?

 

Link to comment
https://forums.phpfreaks.com/topic/293144-rate-limiting-in-php-websockets/
Share on other sites

Sounds like you have a bug in your wsBuildClientFrame function which is causing it to go into a recursion loop. You should address that issue before adding rate limiting.

 

To add rate limiting, you count the # of requests along with keeping track of a timestamp. On each request compare the current # of requests to the # at the last timestamp and see if it's above your limit. If so, deny the request.

 

Something like this

//$lastTimestamp = timestamp of last check
//$lastCount = whatever $count was at last check
$now = time();
$count++; 
$timeDiff = $now - $lastTimestamp;
if ($timeDiff > 1){ //Ensure timestamps have changed
   $countDiff = $count - $lastCount;
   $lastTimestamp = $now;
   $lastCount = $count;

   if ($countDiff/$timeDiff > 5){ //if more than 5 requests per second
      denyRequest();
   }
}

Sounds like you have a bug in your wsBuildClientFrame function which is causing it to go into a recursion loop. You should address that issue before adding rate limiting.

 

To add rate limiting, you count the # of requests along with keeping track of a timestamp. On each request compare the current # of requests to the # at the last timestamp and see if it's above your limit. If so, deny the request.

 

Something like this

//$lastTimestamp = timestamp of last check
//$lastCount = whatever $count was at last check
$now = time();
$count++; 
$timeDiff = $now - $lastTimestamp;
if ($timeDiff > 1){ //Ensure timestamps have changed
   $countDiff = $count - $lastCount;
   $lastTimestamp = $now;
   $lastCount = $count;

   if ($countDiff/$timeDiff > 5){ //if more than 5 requests per second
      denyRequest();
   }
}

 I see.

 

And for the denyRequest function just simply return or exit out the user?

 

And this issue wouldn't be prevalent if I were using a more modern approach, like socket.io/node.js correct?

Sounds like you have a bug in your wsBuildClientFrame function which is causing it to go into a recursion loop. You should address that issue before adding rate limiting.

 

To add rate limiting, you count the # of requests along with keeping track of a timestamp. On each request compare the current # of requests to the # at the last timestamp and see if it's above your limit. If so, deny the request.

 

Something like this

//$lastTimestamp = timestamp of last check
//$lastCount = whatever $count was at last check
$now = time();
$count++; 
$timeDiff = $now - $lastTimestamp;
if ($timeDiff > 1){ //Ensure timestamps have changed
   $countDiff = $count - $lastCount;
   $lastTimestamp = $now;
   $lastCount = $count;

   if ($countDiff/$timeDiff > 5){ //if more than 5 requests per second
      denyRequest();
   }
}

 

 

Sorry to bump this thread, but I got a working version:

function wsOnMessage($clientID, $message, $messageLength, $binary) {
global $servername, $dbuser, $dbpassword, $dbname, $users, $db;
 
// anti flood protection
if($_SESSION['last_session_request'] > time() - 1){
// users will be redirected to this page if it makes requests faster than 2 seconds
echo "Limit Reached... Simmer down!";
return false;
}else{
 
echo $_SESSION['last_session_request'];
}

I got a working version working here:

 

My question is: That return false works but it will stop the whole script? Which if other users are using will stop them as well right? How do I only target the specific client and user instead?

 

Also, should I use microtime instead? 

 

You know, I can use "wsClose($clientID);" to kick them off. But what's stopping them from just refreshing?

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.