[Include] [WIP] Ban system
#1

Hey,

I've been working on this ban system on and off for a while now, and I think it's reached a stable testing state.

Worth mentioning is IPs are dealt with as integers, not strings. All IPs are tagged with IP:.

Another thing that's probably worth mentioning is it doesn't require any configuration what so ever, and it uses SQLite.

Functions
GetDotDecimalIP(IP:ip) Returns a string containing the IP in human-readable format (i.e. 127.0.0.1).
IP:GetIntegerIP(ip[]) Convert a dot-decimal IP string to an integer IP.
IP:GetPlayerIP Returns the integer IP for a player.
IsIPInRange(IP:ip, range)
IsIPInRange(ip[], range)
Returns true if the IP is within the specified IP range. The 1st argument can be an integer IP or a string.
GetDurationFromString(duration[]) Convert a human-readable duration into seconds. Examples of valid input:
  • "2 weeks 5 days"
  • "1 month and 3 weeks"
  • "1 year -2 months" (gives 10 months)
  • "1y2m"
  • "1hr20min"
GetStringFromDuration(duration) Opposite of above (i.e. 9000 => 2 hours 30 minutes).
GetUnixTimestamp() Returns the current UNIX timestamp.
BanIP(IP:ip, duration = DURATION_PERMANENT)
BanIP(ip[], duration = DURATION_PERMANENT)
Ban an IP and, optionally, set the duration of that ban. The duration is in seconds.
See GetDurationFromString for an easy way to generate a duration.
Returns the ban index.
BanIPRange(IPRange:range, duration = DURATION_PERMANENT)
Same as above, but for IP ranges.
Returns the ban index.
BanPlayer(iPlayer, iDuration = DURATION_PERMANENT)
Ban a player by their IP.
Returns the ban index.
LiftBan(ban_index) Remove a ban.
GetIPBanIndex(IP:ip)
GetIPBanIndex(ip[])
Returns the ban index for the first active ban found on that IP, 0 if no bans.
GetPlayerBanIndex(playerid) Same as above, but for player ID.
GetBanType(ban_index) Returns the ban type for a ban. Possible return values:
  • BAN_TYPE_NONE - Probably invalid ban index
  • BAN_TYPE_SINGLE - IP is banned
  • BAN_TYPE_RANGE - IP is inside banned range
GetIPRange(ip[], ip[])
GetIPRange(IP:ip, IP:ip)
GetIPRange(ip_range[])
Returns an IP range either between argument 1 and 2, or generated by wildcards (*) in argument 1.
Example: GetIPRange("192.168.0.0", "192.168.255.255") is the same as GetIPRange("192.168.*.*").
IsIPInRange(IP:ip, IPRange:range)
IsIPInRange(ip[], IPRange:range)
Returns true if the IP (1st argument) is inside the IP range (2nd argument).
The IP passed to the function can be either a string or a cell.
See GetIPRange or the IP_RANGE macro to get IP ranges.
SetBanInfoInt(ban_index, key[], value)
SetBanInfoFloat(ban_index, key[], Float:value)
SetBanInfoString(ban_index, key[], value[])
Set values associated with ban indexes (for example, reason, user id, etc.).
GetBanInfoInt(ban_index, key[])
GetBanInfoFloat(ban_index, key[])
GetBanInfoString(ban_index, key[])
GetBanInfoStringCopy(ban_index, key[], destination[])
Read values stored by SetBanInfo*. Note that the max string size for GetBanInfoString is 256. For larger strings, use GetBanInfoStringCopy.
GetIPBanIndices(ip[], destination[])
GetIPBanIndices(IP:ip, destination[])
Fill the destination array with all active ban indices (indexes) and return the number of indices found.
GetPlayerBanIndices(playerid, destination[])
Same as above, but for player ID.
Macros
IP(x.x.x.x)
Gives an integer IP.
Example: IP(127.0.0.1)
IP_RANGE(x.x.x.x - x.x.x.x)
Gives an IP range.
Example: IP_RANGE(192.168.0.0 - 192.168.0.255)
IP_OCTET(ip, position)
Gives the value of a single octet in an IP.
Example: IP_OCTET(IP(127.0.0.1), 1) will give 127.
IPRange<name> Used when declating IP range variables (IP ranges are enums).
DURATION(x)
Like GetDurationFromString, but during compile-time. Has to follow the syntax below.
Example: DURATION(1 hour, 20 minutes) gives 3620.
LOOP_IP_BANS(ban_index : IP:ip)
LOOP_IP_BANS(ban_index : ip[])
LOOP_IP_BANS(new ban_index : IP:ip)
LOOP_IP_BANS(new ban_index : ip[])
Loop all bans for a specific IP. I designed it to resemble foreach, for consistency.
Example: LOOP_IP_BANS(new ban : "127.0.0.1") printf("Ban index: %d", ban);
LOOP_PLAYER_BANS(ban_index : playerid)
LOOP_PLAYER_BANS(new_ban_index_:_playerid)
Loop all bans for a specific IP. I designed it to resemble foreach, for consistency.
Example: LOOP_PLAYER_BANS(new ban : playerid) printf("Ban index: %d", ban);
Examples
Ban a player
pawn Код:
new ban = BanPlayer(playerid, DURATION(2 hours, 30 minutes));

SetBanInfoString(ban, "reason", "Auto-ban for doing whatever you're not supposed to..");
Check if a player is banned
pawn Код:
public OnPlayerConnect(playerid) {
    new ban = GetPlayerBanIndex(playerid);
   
    if (ban) {
        new message[128];
       
        if (GetBanType(ban) == BAN_TYPE_RANGE)
            format(message, 128, "* Your IP is inside a range-ban, reason: %s", GetBanInfoString(ban, "reason"));
        else
            format(message, 128, "* You are banned, reason: %s", GetBanInfoString(ban, "reason"));
       
        SendClientMessage(playerid, 0xCC0000FF, message);
       
        // This will output something like: "* You may come back in 1 day and 13 hours"
        format(message, 128, "* You may come back in %s.", GetStringFromDuration(GetBanInfoInt(ban, "time_left")));
        SendClientMessage(playerid, 0xCC0000FF, message);
       
        Kick(playerid);
       
        return 0;
    }
   
    return 1;
}
Changes
2011-11-25
  • Fixed a bug causing BanIP to fail.
2011-11-18
  • Minor bug fixes.
  • BanIPRange now accepts both strings IP ranges (IPRange<>).
  • New functions:
    • GetBanIPRange
    • GetIPString
    • GetIPRangeString
    • ClearBanDB
  • To facilitate DB upgrades, tables will now be created according to gs_Tables, and missing fields (if any) will be searched for & created from gs_Columns.
2011-11-08
  • Added automatic table creation.
  • The database will have VACUUM done on initialization.
  • GetStringFromDuration now puts commas and, if applicable, it puts "and" before the last part.
  • Packed a bunch of print and db_query statements to save space.
  • Rewrote EscapeSQLiteString to be approx. 160% faster.
  • Added Get(IP/Player)BanIndices.
  • Added LOOP_IP_BANS and LOOP_PLAYER_BANS.
  • Fixed a few bugs related to improper buffer usage.
Download

http://pastebin.com/R12ZpSjc
Reply
#2

Good luck with it, I am sure many people would like it
Reply
#3

Good Job!
Reply
#4

This really helps to catch ban evaders.

Thanks slice.
Reply
#5

good job i will try this
Reply
#6

Advanced system. Nice Job
Reply
#7

Thanks everyone. I've made an update to the system, it will not automatically create the required tables if they don't exist.
Reply
#8

Excellent work Slice! I was going to write my own that works similarly, but never got around to it. It will be implemented in littlewhitey's new gamemode.

Loving the use of IP as integer and function overloads. Very creative as to how you got them working, I wish Pawn implemented overloads natively
Reply
#9

Thank you. We could probably collaborate a bit with integrating it into littlewhitey's. Currently I have a few questionmarks:
  • How should rangebans be managed? Currently they blend together with the normal bans, but perhaps a bit too much. Whitelist functionality perhaps?
  • Which approach would be best for DNS look-ups: plugin or external HTTP requests?
  • If a player has several bans on his range/IP, how should this be handled? Currently GetBanIndex retrieves the 1st ban index, and LiftBan removes whichever ban index was passed to it, and only that. Maybe something simple like sort of a JustMakeItSoThisPoorPlayerCanJoinTheServerIDontCar eHow(ip).
Also, I'll probably make a dialog system for managing bans. Either a filterscript or a part of the include, I've not decided which just yet.
Reply
#10

Slice you need to make a work around so I can ban evade
Reply
#11

Quote:
Originally Posted by Slice
Посмотреть сообщение
How should rangebans be managed? Currently they blend together with the normal bans, but perhaps a bit too much. Whitelist functionality perhaps?
Ban exceptions would be amazing. It's something that should really be a part of SA-MP. You could clone your bans table structure and rename it to exceptions, if an IP exists in the exceptions then don't bother looking it up in bans.

Quote:
Originally Posted by Slice
Посмотреть сообщение
Which approach would be best for DNS look-ups: plugin or external HTTP requests?
I firstly go with what makes it easier for you, the developer, as both have advantages. I believe the HTTP would be the easiest way to go, but obviously would leave you with a delay. A plugin would give you more of an ability to remove that delay, either by using a constant list or having an dynamically increasing cache. However, use of a plugin adds an extra dependency which can be annoying.

Quote:
Originally Posted by Slice
Посмотреть сообщение
If a player has several bans on his range/IP, how should this be handled? Currently GetBanIndex retrieves the 1st ban index, and LiftBan removes whichever ban index was passed to it, and only that. Maybe something simple like sort of a JustMakeItSoThisPoorPlayerCanJoinTheServerIDontCar eHow(ip).
You could have a GetBanIndices(IP:ip, dest[], len = sizeof(dest)) which populates the dest array with all the ban indices that the ip is included in. The function would return the number of ban indices that that IP is a part of (if it's not excepted)
Reply
#12

Alright, GetBanIndices it is!

Update
  • GetStringFromDuration now puts commas and, if applicable, it puts "and" before the last part.
  • Packed a bunch of print and db_query statements to save space.
  • Rewrote EscapeSQLiteString to be approx. 160% faster.
  • Added Get(IP/Player)BanIndices.
  • Added LOOP_IP_BANS and LOOP_PLAYER_BANS.
  • Fixed a few bugs related to improper buffer usage.
LOOP_IP_BANS example:
pawn Код:
new ban;

ban = BanIP(IP(127.0.0.1), DURATION(10 seconds));
SetBanInfoString(ban, "test", "lorem");

ban = BanIP(IP(127.0.0.1), DURATION(10 seconds));
SetBanInfoString(ban, "test", "ipsum");

ban = BanIP(IP(127.0.0.1), DURATION(10 seconds));
SetBanInfoString(ban, "test", "dolor");

ban = BanIP(IP(127.0.0.1), DURATION(10 seconds));
SetBanInfoString(ban, "test", "sit");

ban = BanIP(IP(127.0.0.1), DURATION(10 seconds));
SetBanInfoString(ban, "test", "amet");

LOOP_IP_BANS(ban : IP(127.0.0.1)) {
    printf("Ban: %d (test: %s)", ban, GetBanInfoString(ban, "test"));
}
Outputs something like this:
Код:
Ban: 105 (test: lorem)
Ban: 106 (test: ipsum)
Ban: 107 (test: dolor)
Ban: 108 (test: sit)
Ban: 109 (test: amet)
Reply
#13

Shit Excellent Slice! Keep it up!
Reply
#14

Is anyone using this, and how's that working out?
Reply
#15

I'm not using it, but I'm fascinated by your work and would like to implement your method of range banning for my own server. My project currently has 4 fields which store the IP exploded by a period. But looking at how your scripts always have top notch features and are at the same time optimized to the max, I assume that the way you store IPs is better than using 4 separate fields for it. If so, can you confirm and also post a few tips on how to implement similar functionality to a MySQL based script?
Reply
#16

Good job !
Reply
#17

Quote:
Originally Posted by AndreT
Посмотреть сообщение
I'm not using it, but I'm fascinated by your work and would like to implement your method of range banning for my own server. My project currently has 4 fields which store the IP exploded by a period. But looking at how your scripts always have top notch features and are at the same time optimized to the max, I assume that the way you store IPs is better than using 4 separate fields for it. If so, can you confirm and also post a few tips on how to implement similar functionality to a MySQL based script?
Well, basically, I'm storing IPs in one single variable, where each number represents an IP (for example, 2130706433 = 0x7F000001 = 127.0.0.1).
To do this conversion in a MySQL query, you can use the function INET_ATON(ip).

For example:
Код:
INSERT INTO x (a, b, ip) VALUES ('a', 'b', INET_ATON('127.0.0.1'));
To later retrieve that value, you can do:
Код:
SELECT a, b, INET_NTOA(ip) FROM x WHERE y;
So you could put ip_start and ip_end fields in your table, and use the [url=http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#operator_between
BETWEEN ... AND ...[/url] operator to find a matching range (that's how I do it).

For example:
Код:
SELET * FROM bans WHERE INET_ATON('127.0.0.1') BETWEEN ip_start AND ip_end;
You could (and may) take code from my script and modify it a little bit to get this working.

If you don't understand how a single number can fit an IP, here's a quick explanation on that:
Variables in PAWN scripting for SA-MP have the size 4 bytes (32 bits). One byte can have 255 unique values - either -127 to 127, or 0 to 255.
Therefore, by isolating each byte, we can store 4 values ranging from 0 to 255 in one variable!

However, because format is buggy, you can't put negative numbers or certain very high numbers without getting strange results. You could have a look at the way I solved it, or you could simply use the queries above and put IPs in dot-notation in your queries (x.x.x.x).
Reply
#18

You know i love you Slice.
Reply
#19

Thanks a lot for the explanation, Slice. I wasn't aware of such functions in MySQL and had never before looked into how IP addresses can be integers. For anyone interested, here's an article with some benchmarking on how storing IP addresses can be made more efficient.

This change will make 4 string fields into 2 unsigned integer fields. And the change to the ban checking query which is ran every time a player connects, will improve things even more!

I'll sure look into your code even more and I hope you haven't forgotten your awesome topic about optimizations and enhancements. I'd love to see something new in there!
Reply
#20

Quote:
Originally Posted by TheArcher
Посмотреть сообщение
You know i love you Slice.
Yay!

Quote:
Originally Posted by AndreT
Посмотреть сообщение
Thanks a lot for the explanation, Slice. I wasn't aware of such functions in MySQL and had never before looked into how IP addresses can be integers. For anyone interested, here's an article with some benchmarking on how storing IP addresses can be made more efficient.

This change will make 4 string fields into 2 unsigned integer fields. And the change to the ban checking query which is ran every time a player connects, will improve things even more!

I'll sure look into your code even more and I hope you haven't forgotten your awesome topic about optimizations and enhancements. I'd love to see something new in there!
Well not only that, but you will easily be able to compare IPs in the server (for example, find players with the same IP/subnet).
Are you familiar with bitwise operations such as AND, OR, XOR, shifting? If not, you should read about that a little bit and you'll be able to do and understand so much more.
Reply


Forum Jump:


Users browsing this thread: 7 Guest(s)