-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add IPIntel API support. #33339
base: master
Are you sure you want to change the base?
Add IPIntel API support. #33339
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self minor nitpicks
Oh my god i hate this
Content.Server.Database/Migrations/Postgres/20241122174243_IPIntel.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to demand you pull this out into a helper class so that this logic can be thoroughly unit tested. Right now I'm seeing many bugs just through simple review, so I do not trust you've done extensive enough testing manually for me to be confident to deploy this on the servers directly.
Not yet in a helper class just wanna do these and commiting before starting that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have only gone through the ConnectionManager.IpIntel.cs file in anger;
This can be expressed quite a bit more clearly, I'll talk to you directly about the strategy I took when doing a tidy-up.
The rate-limiting code is superfluous. Failing due to a rate limit can be handled as a backoff; it's up to the IPIntel API to decide when we're un-rate-limited, not us, so we can't really programatically decide we're not rate-limited anymore.
Removing this makes us now really only have to worry about doing unit testing that can mock the request. I've taken a look and unfortunately it doesn't look like there's a mock imlementation of IHttpClientHolder, so you'll need to dig into the Mock<> route to make the unit tests.
To give you an idea where I got to with tidying the code:
private async Task<(bool IsBad, string Reason)> ProcessResponse(NetConnectingArgs e, HttpResponseMessage request)
{
switch (request.StatusCode)
{
case HttpStatusCode.OK:
var score = Parse.Float(await request.Content.ReadAsStringAsync());
if (!await _db.UpsertIPIntelCache(DateTime.UtcNow, e.IP.Address, score))
{
_sawmill.Error(
$"Unable to upsert an IP address '{e.IP.Address}' into the IPIntel cache on the database.");
}
return IsScoreTooHigh(score, e);
case HttpStatusCode.TooManyRequests:
_sawmill.Warning($"We've hit the IPIntel request limit.");
_failedRequests++;
_releasePeriod = TimeSpan.FromSeconds(_failedRequests * _backoffSeconds);
return _isRejectClientsOnRateLimitReached
? (true, Loc.GetString("ipintel-server-ratelimited"))
: FalseResp;
case HttpStatusCode.BadRequest:
var errResp = await request.Content.ReadAsStringAsync();
if (IsKnownError(errResp, out var rejectResult))
return rejectResult;
// Oh boy, we don't know this error.
_sawmill.Error(
$"IPIntel returned {errResp} (Status code: {request.StatusCode})... we don't know what this error code is. Please make an issue in upstream!");
return _rejectUnknown ? (true, Loc.GetString("ipintel-unknown")) : FalseResp;
default:
_sawmill.Warning(
$"Unexpected response status code when querying IPIntel: '{request.StatusCode.ToString()}'. Treating as an unknown response.");
_failedRequests++;
_releasePeriod = TimeSpan.FromSeconds(_failedRequests * _backoffSeconds);
return _rejectUnknown ? (true, Loc.GetString("ipintel-unknown")) : FalseResp;
}
}
The above demonstrates a simple switch approach to processing HTTP status codes.
I would strongly recommend treating the else
keyword as something to avoid unless it's trivial code. Switches are a far clearer way of expressing an if-else chain.
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ship it.
(Don't actually we need to update the engine first) |
About the PR
This PR adds support for the IPIntel API and adds it in the connection manager.
Why / Balance
Technical details
Welcome to what I like to call "return spaghetti"
BREATH IN
This function is at the very end of the connection manager and should STAY THERE AT ALL COSTS. Since we have an API limit to abide by, Anyone working with this codebase should prioritize trying to not make a request to IPIntel if possible. To enable this system, you need to manually enable
game.ipintel_enabled
in your config. Along with a contact email for the IPIntel servicegame.ipintel_contact_email
IF YOU DON'T PROVIDE THESE TWO CCVARS IT WILL NOT FUNCTION
If the player has
Datacenter
orBlacklistedRange
ban exemptions, then they get to bypass this system. It's not by any means perfect, so administrators should be expected to receive false positives and act accordingly for their player base. Ensure your admins are aware of theban_exemption_update
andban_exemption_get
commands.Next up, the player's total playtime is checked. By default,
game.ipintel_exempt_playtime
is set to 5 Hours. This is in an effort to preserve the API limit for big servers. You can disable this by setting it to 0 if you really hate this.Next, we will check our cache. Every time we make a request to IPIntel we cache the response for 7 days (
game.ipintel_cache_length
) since we want to hit IPIntel as little as possible. Again for our API limit. If we got one that's still valid, we use it. If we already have an old record, we mark it as expired and move forward. The same if we are seeing it for the first time.Ratelimits are terrible and I hate them
A quick check for our Email happens before continuing to preparing the API request.
game.ipintel_baseurl
controls the base API URL, but you probably don't want to change it unless you decide to go request for a paid plan. We will use theb
flag by default as we don't care about if the IP of the player is compromised. (You can change the flag with thegame.ipintel_flags
ccvar) Just that if it's a datacenter. It also reduces the request time (assuming no latency) to 120ms~ from 200ms-5seconds for a regular scan.Finally comes the decision. We will now compare what we got from the API. If what we got is bigger, then
game.ipintel_bad_rating
then we deny the connection ifgame.ipintel_reject_bad
with an optional reject notice to the admin (game.ipintel_alert_admin_rejected
). Optionally, if you setgame.ipintel_alert_admin_warn_rating
to something, and we did not just reject the connection, then you can warn in game admins.If we had any errors during this process, we will return an unknown result. Whether we kick or not is determined with if
game.ipintel_reject_unknown
is true or notMedia
game.ipintel_alert_admin_rejected
enabledgame.ipintel_alert_admin_warn_rating
enabledDefault Disconnect message to blocked players
Requirements
Changelog
🆑 Myra
ADMIN: