HTTP DDoS protection

maybe we can also make some efficient code to protect against DDOS attacks.

will it be faster if the ip-adres is asserted as number instead of atom, to measure the time between http-requests of 1 specific ip adres.

If nowadays you watch the logs of web-servers there are many automised http requests, in many cases they issue more than 10 requests in 1 second.

so if more than X requests between Y time, then permanently block that ip-adres, and that before the session cookie is created

In SWI-Prolog ip addresses are ip(A,B,C,D) or ip(A, B, C, D, E, F, G, H) (IPv6) terms. The fun thing is that I used ip(A,B,C,D) because in very old days SWI-Prolog integers were (I think) 28 bits, so they could not hold an IP address :slight_smile:

I use something similar to DDoS protection in SWISH code for establishing web sockets. This sometimes fails, causing many retry requests. The idea is to add a penalty on every request and have a decay function if no request came in for some time. So, for each IP address you need a score (number) and last time stamp.

You can implement this in the HTTP server main thread. This thread merely calls tcp_accept/3 and then passes the accepted socket to a pool. You can also implement it in the dispatching code. Next, I think you roughly have these options
(suggests more if I missed some):

  • Use a Prolog term, e.g., library(assoc). You can use that when implemented in the server main thread or you could run it inside an engine.
  • Use a SWI-Prolog dict as above. Probably faster and cheaper if there are up to about 1,000 distinct IP addresses in your pool.
  • Use a SWI-Prolog trie.
  • Use the dynamic database.

It is a little hard to say how these three would compare. My intuition says that the trie is the most likely winner, both time and space wise.

that is very extensive information thankyou. i didnt know about the Ipv6 format , i will use it. my brains can handle at most / max the dynamic predicates stuff. indeed a score is needed and a time stamp. I made a very simple code here.

:- dynamic ip_behaviour/7.

judge_interval( Time_difference , do_block_ip ) :- Time_difference < 0.3 , ! .
judge_interval( _Time_difference , do_allow_ip ) :-  ! .

allow_ip_request( ip( Nu1, Nu2, Nu3, Nu4 ) , do_block_ip ):-
% it was already asserted as to block it 
 ip_behaviour( Nu1, Nu2, Nu3, Nu4 , _Last_time , _Time_difference , Judgement ) , 
 Judgement == do_block_ip ,  ! .

allow_ip_request( ip( Nu1, Nu2, Nu3, Nu4 ) , Judgement ):-
 retract( ip_behaviour( Nu1, Nu2, Nu3, Nu4 , Previous_time , _Previous_interval , _Allow_or_block ) ) ,
 get_time( Now ),  Xinterval is Now - Previous_time , 
 judge_interval( Time_difference , Judgement , ) , 
 assert( ip_behaviour( Nu1, Nu2, Nu3, Nu4 , Now , Xinterval , Judgement ) ) .

allow_ip_request( ip( Nu1, Nu2, Nu3, Nu4 ) , do_allow_ip ):-  !,
 % the ip is new / has never been encountered yet , so assert it
 get_time( Now ), 
 assert( ip_behaviour( Nu1, Nu2, Nu3, Nu4 , Now , 5 , do_allow_ip ) ) .

Out of curiosity, were the remaining four bits “type bits”?

SWI-Prolog always had 3 type bits. So, maybe it was 29 … The initial versions had no GC. GC uses another 2 bits. Not sure when sockets were added. I do remember that using an integer was no option because they could not represent the 32 bits IP address.

Maybe the git repo goes back far enough :slight_smile:

1 Like