Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ Legend:
? = Other stuff
```

(26/10/26): Version 2.2.5-srcdslab (**Run updater required**)
-----------------------
```
01. ! Players who joined before the database connected, hit a failed
ban query, or were online when sbpp_main late-loaded were never
checked for bans; the retry timer now re-queries
02. ! Steam Networking (SDR / FakeIP) players: the shared 169.254.x.x
placeholder is no longer stored or matched as their IP; sm_banip
refuses them, SourceSleuth skips them, and updater migration
816 cleans placeholders stored by older plugins
03. ! SourceSleuth no longer re-bans a player on their own ban (with
sm_sleuth_actions 3 the length doubled on every reconnect)
```

(26/10/26): Version 2.2.4-srcdslab
-----------------------
```
Expand Down
24 changes: 24 additions & 0 deletions docs/src/content/docs/setup/plugin-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,30 @@ Drop the plugins you want into `addons/sourcemod/plugins/` and remove
the ones you don't (SourceMod loads everything it finds, so an unused
plugin still consumes a server slot).

### Steam Networking (SDR / FakeIP) servers

Servers using `-enablefakeip` or `sv_use_steam_networking` (Source SDK
2013 games) see every Steam Networking client under a placeholder
`169.254.x.x` address instead of the player's public IP. That address
is shared by every such player and identifies nobody, so the plugins
never store or match it:

- **Bans and comms** record no IP for these players (neither the
target's nor admins). Bans are applied by SteamID.
- **`sm_banip`** refuses to ban a player who has no usable IP; use
`sm_ban` instead.
- **`sbpp_checker.smx` and `sbpp_main.smx`** only look up these players
via SteamID when they connect.
- **`sbpp_sleuth.smx`** skips them entirely. Its IP-based alt-account
detection can't work without a public IP address, and matching the
shared placeholder IP would flag innocent players.

Players on the same server who connect normally, without SDR, are
checked exactly as before. The panel updater (migration 816) removes
placeholder addresses already stored by older plugins: SDR IP bans that
have a valid SteamID are converted to SteamID bans, and placeholder
IPs are cleared.

### Discord notifications

For a Discord-channel forwarder, install the separate
Expand Down
67 changes: 67 additions & 0 deletions game/addons/sourcemod/scripting/include/sbpp_server_ip.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// *************************************************************************
// This file is part of SourceBans++.
//
// Copyright (C) 2014-2026 SourceBans++ Dev Team <https://github.com/sbpp>
//
// SourceBans++ is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, per version 3 of the License.
// *************************************************************************

#if defined _sbpp_server_ip_included
#endinput
#endif
#define _sbpp_server_ip_included

/**
* SDR / Steam Networking ("FakeIP") clients are not reachable by a real address.
* The engine hands the server a placeholder instead. From the Source SDK 2013
* Steamworks headers (steamnetworkingtypes.h):
*
* FakeIP addresses are designed to (hopefully) pass through existing code as
* transparently as possible, while conflicting with "real" addresses that might
* be in use on networks (both the Internet and LANs) in the same code as little
* as possible. At the time this comment is being written, they come from the
* 169.254.0.0/16 range, and the port number will always be >1024. HOWEVER,
* this is subject to change! Do not make assumptions about these addresses,
* or your code might break in the future. In particular, you should use
* functions such as ISteamNetworkingUtils::IsFakeIP to determine if an IP
* address is a "fake" one used by this system.
*
* https://github.com/ValveSoftware/source-sdk-2013/blob/be57e3d01ac7d7aa6a2cacbfef187c0b46616db0/src/public/steam/isteamnetworkingsockets.h#L812-L820
*
*
* ISteamNetworkingUtils::IsFakeIP is a C++ interface that SourceMod does not
* expose to SourcePawn, so SourceBans++ falls back to the 169.254.0.0/16 range
* described above. If a native for IsFakeIP ever becomes available, this is
* the single place to switch to it.
*
* @param ip Client IP address string (without port).
* @return True if the address is a Steam Networking FakeIP.
*/
stock bool SBPP_IsSteamNetworkingIP(const char[] ip)
{
return strncmp(ip, "169.254.", 8) == 0;
}

/**
* GetClientIP wrapper that never returns a Steam Networking FakeIP, so callers
* can't store or match it by accident. A FakeIP is shared by every SDR client
* and says nothing about the player.
*
* @param client Client index.
* @param buffer Buffer for the IP (port removed). Emptied when unavailable.
* @param maxlen Size of buffer.
* @return True if buffer holds a usable IP, false if it is empty
* (lookup failed or the address is a FakeIP).
*/
stock bool SBPP_GetClientPublicIP(int client, char[] buffer, int maxlen)
{
if (!GetClientIP(client, buffer, maxlen, true) || SBPP_IsSteamNetworkingIP(buffer))
{
buffer[0] = '\0';
return false;
}

return true;
}
23 changes: 16 additions & 7 deletions game/addons/sourcemod/scripting/sbpp_checker.sp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <sourcemod>
#include <sbpp_version>
#include <sbpp_server_ip>

#define VERSION SB_VERSION
#define LISTBANS_USAGE "sm_listbans <#userid|name> - Lists a user's prior bans from Sourcebans"
Expand Down Expand Up @@ -201,18 +202,23 @@ public void OnClientAuthorized(int client, const char[] auth)
if (auth[0] == 'B' || auth[9] == 'L')
return;

char query[512], ip[30];
GetClientIP(client, ip, sizeof(ip));
// No usable IP (e.g. SDR client): never match IP bans, an empty address
// would match any type=1 row with a blank IP.
char query[512], ip[30], ipCondition[64];
if (SBPP_GetClientPublicIP(client, ip, sizeof(ip)))
FormatEx(ipCondition, sizeof(ipCondition), "ip = '%s'", ip);
else
strcopy(ipCondition, sizeof(ipCondition), "1 = 0");
FormatEx(query, sizeof(query),
"SELECT COUNT(bid) FROM %s_bans WHERE (type = 0 AND authid LIKE 'STEAM_%%:%s') \
UNION ALL \
SELECT COUNT(bid) FROM %s_bans WHERE (type = 1 AND ip = '%s') \
SELECT COUNT(bid) FROM %s_bans WHERE (type = 1 AND %s) \
UNION ALL \
SELECT COUNT(bid) FROM %s_comms WHERE authid LIKE 'STEAM_%%:%s' AND type = 1 \
UNION ALL \
SELECT COUNT(bid) FROM %s_comms WHERE authid LIKE 'STEAM_%%:%s' AND type = 2",
g_DatabasePrefix, auth[8],
g_DatabasePrefix, ip,
g_DatabasePrefix, ipCondition,
g_DatabasePrefix, auth[8],
g_DatabasePrefix, auth[8]
);
Expand Down Expand Up @@ -304,9 +310,12 @@ public Action OnListSourceBansCmd(int client, int args)
return Plugin_Handled;
}

char query[1024], ip[30];
GetClientIP(target, ip, sizeof(ip));
FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid WHERE ((type = 0 AND %s_bans.authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], ip);
char query[1024], ip[30], ipCondition[64];
if (SBPP_GetClientPublicIP(target, ip, sizeof(ip)))
FormatEx(ipCondition, sizeof(ipCondition), "ip = '%s'", ip);
else
strcopy(ipCondition, sizeof(ipCondition), "1 = 0");
FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid WHERE ((type = 0 AND %s_bans.authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND %s)) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], ipCondition);

char targetName[MAX_NAME_LENGTH];
GetClientName(target, targetName, sizeof(targetName));
Expand Down
3 changes: 2 additions & 1 deletion game/addons/sourcemod/scripting/sbpp_comms.sp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <sourcemod>
#include <basecomm>
#include <sourcecomms>
#include <sbpp_server_ip>

#undef REQUIRE_PLUGIN
#include <adminmenu>
Expand Down Expand Up @@ -297,7 +298,7 @@ public bool OnClientConnect(int client, char[] rejectmsg, int maxlen)
FormatEx(g_sSteamIDs[client], sizeof(g_sSteamIDs[]), "%s", sSteamID);

char sIP[16];
GetClientIP(client, sIP, sizeof(sIP));
SBPP_GetClientPublicIP(client, sIP, sizeof(sIP));
FormatEx(g_sPlayerIP[client], sizeof(g_sPlayerIP[]), "%s", sIP);
FormatEx(g_sName[client], sizeof(g_sName[]), "%N", client);
g_iUserIDs[client] = GetClientUserId(client);
Expand Down
Loading
Loading