mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-19 03:49:10 +01:00
Moves various things to the new DBCore (#21544)
``` - server: "Changed the synthsprites to use the new DBCore" - server: "Changed the MalfAI to use the new DBCore" - server: "Changed the Alien Whitelists to use the new DBCore" - server: "Changed the Requests Console to use the new DBCore" - server: "Changed the Contracts Uplink to use the new DBCore" - server: "Changed the Admin Ranks to use the new DBCore" - server: "Changed the Job Bans to use the new DBCore" - server: "Changed the Tickets to use the new DBCore" - server: "Changed the Create Command Report to use the new DBCore" - server: "Changed the WebInterface interconnect to use the new DBCore" - server: "Changed the CCIA Recorder to use the new DBCore" - server: "Changed the IPCTags to use the new DBCore" - server: "Changed the Main Menu Poll-Check to use the new DBCore" - server: "Changed the Client-Procs to use the new DBCore" ``` --------- Co-authored-by: Werner <Arrow768@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ SUBSYSTEM_DEF(dbcore)
|
||||
wait = 10 // Not seconds because we're running on SS_TICKER
|
||||
runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
|
||||
init_order = INIT_ORDER_DBCORE
|
||||
init_stage = INITSTAGE_FIRST
|
||||
priority = FIRE_PRIORITY_DATABASE
|
||||
|
||||
|
||||
@@ -308,6 +309,8 @@ SUBSYSTEM_DEF(dbcore)
|
||||
|
||||
/// Check if we have established a DB Connection
|
||||
/datum/controller/subsystem/dbcore/proc/IsConnected()
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
if(!GLOB.config.sql_enabled)
|
||||
return FALSE
|
||||
if (!connection)
|
||||
@@ -489,7 +492,7 @@ SUBSYSTEM_DEF(dbcore)
|
||||
if(status == DB_QUERY_STARTED)
|
||||
CRASH("Attempted to start a new query while waiting on the old one")
|
||||
|
||||
if(!SSdbcore.IsConnected())
|
||||
if(!SSdbcore.Connect())
|
||||
last_error = "No connection!"
|
||||
return FALSE
|
||||
|
||||
@@ -528,7 +531,7 @@ SUBSYSTEM_DEF(dbcore)
|
||||
if(status == DB_QUERY_STARTED)
|
||||
CRASH("Attempted to start a new query while waiting on the old one")
|
||||
|
||||
if(!SSdbcore.IsConnected())
|
||||
if(!SSdbcore.Connect())
|
||||
last_error = "No connection!"
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ SUBSYSTEM_DEF(garbage)
|
||||
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
|
||||
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
|
||||
init_order = INIT_ORDER_GARBAGE
|
||||
init_stage = INITSTAGE_EARLY
|
||||
init_stage = INITSTAGE_FIRST
|
||||
|
||||
var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level
|
||||
|
||||
|
||||
@@ -2,13 +2,242 @@ SUBSYSTEM_DEF(ipintel)
|
||||
name = "XKeyScore"
|
||||
init_order = INIT_ORDER_MISC_FIRST
|
||||
flags = SS_NO_FIRE
|
||||
var/enabled = FALSE //disable at round start to avoid checking reconnects
|
||||
/// Whether IPIntel lookups are currently enabled. Disabled until Initialize() to avoid checking reconnects at roundstart.
|
||||
var/enabled = FALSE
|
||||
/// world.timeofday value before which all API requests are suppressed due to prior rate-limit or connection errors
|
||||
var/throttle = 0
|
||||
/// Number of consecutive API failures; used to scale the throttle window
|
||||
var/errors = 0
|
||||
|
||||
/// In-memory cache of recent [/datum/ipintel] results, keyed by IP address string
|
||||
var/list/cache = list()
|
||||
|
||||
/datum/controller/subsystem/ipintel/Initialize()
|
||||
enabled = TRUE
|
||||
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/**
|
||||
* Looks up the IPIntel score for the given IP address.
|
||||
*
|
||||
* Called from [/client/proc/InitClient] during client connection to check
|
||||
* whether the connecting IP is likely a proxy or VPN.
|
||||
*
|
||||
* Checks sources in priority order:
|
||||
* 1. In-memory cache ([/datum/controller/subsystem/ipintel/var/cache])
|
||||
* 2. Database cache (`ss13_ipintel` table)
|
||||
* 3. Live IPIntel API request via [/datum/controller/subsystem/ipintel/proc/query]
|
||||
*
|
||||
* The result is always delivered via `on_complete`, which is called with a [/datum/ipintel]
|
||||
* as its sole argument. For cache hits the callback is invoked before this proc returns;
|
||||
* for live requests it is invoked asynchronously once the HTTP response arrives.
|
||||
*
|
||||
* The return value is set to the [/datum/ipintel] datum immediately, but its `intel` field
|
||||
* is only guaranteed to be populated by the time `on_complete` fires.
|
||||
*
|
||||
* Arguments:
|
||||
* * ip - The IP address string to look up
|
||||
* * bypasscache - If TRUE, skips both memory and database cache and always queries the API
|
||||
* * updatecache - If TRUE, writes a successful live result back to memory and database cache
|
||||
* * on_complete - Callback invoked with the final [/datum/ipintel] datum when the result is ready
|
||||
*/
|
||||
/datum/controller/subsystem/ipintel/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE, datum/callback/on_complete)
|
||||
var/datum/ipintel/res = new()
|
||||
res.ip = ip
|
||||
. = res
|
||||
if (!ip || !GLOB.config.ipintel_email || !enabled)
|
||||
if (on_complete)
|
||||
on_complete.Invoke(res)
|
||||
return
|
||||
if (!bypasscache)
|
||||
var/datum/ipintel/cachedintel = cache[ip]
|
||||
if (cachedintel && cachedintel.is_valid())
|
||||
cachedintel.cache = TRUE
|
||||
if (on_complete)
|
||||
on_complete.Invoke(cachedintel)
|
||||
return cachedintel
|
||||
|
||||
var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery(
|
||||
{"SELECT date, intel, TIMESTAMPDIFF(MINUTE, date, NOW())
|
||||
FROM ss13_ipintel
|
||||
WHERE
|
||||
ip = INET6_ATON(:ip)
|
||||
AND ((
|
||||
intel < :rating_bad
|
||||
AND
|
||||
date + INTERVAL :save_good HOUR > NOW()
|
||||
) OR (
|
||||
intel >= :rating_bad
|
||||
AND
|
||||
date + INTERVAL :save_bad HOUR > NOW()
|
||||
))"},
|
||||
list(
|
||||
"ip" = ip,
|
||||
"rating_bad" = GLOB.config.ipintel_rating_bad,
|
||||
"save_good" = GLOB.config.ipintel_save_good,
|
||||
"save_bad" = GLOB.config.ipintel_save_bad,
|
||||
)
|
||||
)
|
||||
if (!query_get_ip_intel.Execute())
|
||||
qdel(query_get_ip_intel)
|
||||
if (on_complete)
|
||||
on_complete.Invoke(res)
|
||||
return
|
||||
if (query_get_ip_intel.NextRow())
|
||||
res.cache = TRUE
|
||||
res.cachedate = query_get_ip_intel.item[1]
|
||||
res.intel = text2num(query_get_ip_intel.item[2])
|
||||
res.cacheminutesago = text2num(query_get_ip_intel.item[3])
|
||||
res.cacherealtime = world.realtime - (text2num(query_get_ip_intel.item[3])*10*60)
|
||||
cache[ip] = res
|
||||
qdel(query_get_ip_intel)
|
||||
if (on_complete)
|
||||
on_complete.Invoke(res)
|
||||
return
|
||||
qdel(query_get_ip_intel)
|
||||
query(ip, CALLBACK(src, PROC_REF(on_query_complete), ip, res, updatecache, on_complete))
|
||||
|
||||
/**
|
||||
* Internal callback for [/datum/controller/subsystem/ipintel/proc/get_ip_intel] — invoked by
|
||||
* [/datum/controller/subsystem/ipintel/proc/query] once the live API result is available.
|
||||
*
|
||||
* Writes the intel score into `res`, updates memory and database caches when appropriate,
|
||||
* then invokes the outer `on_complete` callback.
|
||||
*
|
||||
* Arguments:
|
||||
* * ip - The IP address that was looked up
|
||||
* * res - The [/datum/ipintel] datum being populated
|
||||
* * updatecache - Whether to persist the result to memory and database cache
|
||||
* * on_complete - The callback originally passed to [/datum/controller/subsystem/ipintel/proc/get_ip_intel]
|
||||
* * intel - The raw intel score returned by [/datum/controller/subsystem/ipintel/proc/query], or -1 on error
|
||||
*/
|
||||
/datum/controller/subsystem/ipintel/proc/on_query_complete(ip, datum/ipintel/res, updatecache, datum/callback/on_complete, intel)
|
||||
PRIVATE_PROC(TRUE)
|
||||
res.intel = intel
|
||||
if (updatecache && res.intel >= 0)
|
||||
cache[ip] = res
|
||||
var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery(
|
||||
"INSERT INTO ss13_ipintel (ip, intel) VALUES (INET6_ATON(:ip), :intel) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()",
|
||||
list("ip" = ip, "intel" = res.intel)
|
||||
)
|
||||
query_add_ip_intel.SetSuccessCallback(CALLBACK(GLOBAL_PROC, /proc/qdel))
|
||||
query_add_ip_intel.SetFailCallback(CALLBACK(GLOBAL_PROC, /proc/qdel))
|
||||
query_add_ip_intel.ExecuteNoSleep(TRUE) // Allow to run while we still load the game
|
||||
if (on_complete)
|
||||
on_complete.Invoke(res)
|
||||
|
||||
/**
|
||||
* Submits an async IPIntel API request for the given IP address via [/datum/controller/subsystem/http].
|
||||
*
|
||||
* The result is delivered to `on_complete` as a raw intel score (0.0–1.0), or -1 on any
|
||||
* failure. Does nothing and invokes `on_complete(-1)` if IPIntel is disabled or currently
|
||||
* throttled due to prior errors.
|
||||
*
|
||||
* Use [/datum/controller/subsystem/ipintel/proc/get_ip_intel] instead of calling this directly;
|
||||
* it handles cache lookup and result storage around this proc.
|
||||
*
|
||||
* Arguments:
|
||||
* * ip - The IP address to query
|
||||
* * on_complete - Callback invoked with the intel score (num) when the response arrives
|
||||
* * retry - TRUE if this is an automatic retry after a transient failure
|
||||
*/
|
||||
/datum/controller/subsystem/ipintel/proc/query(ip, datum/callback/on_complete, retry = FALSE)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if (!ip || !enabled)
|
||||
if (on_complete)
|
||||
on_complete.Invoke(-1)
|
||||
return
|
||||
if (throttle > world.timeofday)
|
||||
if (on_complete)
|
||||
on_complete.Invoke(-1)
|
||||
return
|
||||
|
||||
var/url = "https://[GLOB.config.ipintel_domain]/check.php?ip=[ip]&contact=[GLOB.config.ipintel_email]&format=json&flags=f"
|
||||
SShttp.create_async_request(
|
||||
RUSTG_HTTP_METHOD_GET,
|
||||
url,
|
||||
"",
|
||||
null,
|
||||
CALLBACK(src, PROC_REF(process_response), ip, on_complete, retry)
|
||||
)
|
||||
|
||||
/**
|
||||
* SShttp callback for [/datum/controller/subsystem/ipintel/proc/query] — processes the HTTP
|
||||
* response from the IPIntel API.
|
||||
*
|
||||
* On success, parses the JSON body and invokes `on_complete` with the numeric result.
|
||||
* On transient errors (connection failure, unexpected status, bad JSON) the request is
|
||||
* retried once after a brief delay. On a second failure, or on a hard 429 rate-limit
|
||||
* response, `on_complete` is invoked with -1.
|
||||
*
|
||||
* Arguments:
|
||||
* * ip - The IP address that was queried
|
||||
* * on_complete - Callback to invoke with the final intel score or -1
|
||||
* * retry - TRUE if this invocation is already a retry
|
||||
* * http - The [/datum/http_response] returned by SShttp
|
||||
*/
|
||||
/datum/controller/subsystem/ipintel/proc/process_response(ip, datum/callback/on_complete, retry, datum/http_response/http)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if (http.errored)
|
||||
handle_error("Unable to connect to API.", ip, retry)
|
||||
if (!retry)
|
||||
sleep(25)
|
||||
query(ip, on_complete, TRUE)
|
||||
else
|
||||
if (on_complete)
|
||||
on_complete.Invoke(-1)
|
||||
return
|
||||
|
||||
if (http.status_code == 200)
|
||||
var/response = json_decode(http.body)
|
||||
if (response)
|
||||
if (response["status"] == "success")
|
||||
var/intelnum = text2num(response["result"])
|
||||
if (isnum(intelnum))
|
||||
if (on_complete)
|
||||
on_complete.Invoke(intelnum)
|
||||
return
|
||||
else
|
||||
handle_error("Bad intel from server: [response["result"]].", ip, retry)
|
||||
else
|
||||
handle_error("Bad response from server: [response["status"]].", ip, retry)
|
||||
if (!retry)
|
||||
sleep(25)
|
||||
query(ip, on_complete, TRUE)
|
||||
else
|
||||
if (on_complete)
|
||||
on_complete.Invoke(-1)
|
||||
else if (http.status_code == 429)
|
||||
handle_error("Status Code 429: We have exceeded the rate limit.", ip, TRUE)
|
||||
if (on_complete)
|
||||
on_complete.Invoke(-1)
|
||||
else
|
||||
handle_error("Unknown status code: [http.status_code].", ip, retry)
|
||||
if (!retry)
|
||||
sleep(25)
|
||||
query(ip, on_complete, TRUE)
|
||||
else
|
||||
if (on_complete)
|
||||
on_complete.Invoke(-1)
|
||||
|
||||
/**
|
||||
* Logs an IPIntel error and, on a final retry failure, increments [/datum/controller/subsystem/ipintel/var/errors]
|
||||
* and sets [/datum/controller/subsystem/ipintel/var/throttle] to temporarily suppress further API calls.
|
||||
*
|
||||
* The throttle duration grows linearly with the error count:
|
||||
* each additional failure adds another 12-second window (10 ticks × 120 × error count).
|
||||
* All errors are written via [/proc/log_subsystem_ipintel].
|
||||
*
|
||||
* Arguments:
|
||||
* * error - Base error message to log
|
||||
* * ip - The IP address that triggered the error
|
||||
* * retry - TRUE if this is a final failure (no further retry will be attempted)
|
||||
*/
|
||||
/datum/controller/subsystem/ipintel/proc/handle_error(error, ip, retry)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if (retry)
|
||||
errors++
|
||||
error += " Could not check [ip]. Disabling IPINTEL for [errors] minute\s"
|
||||
throttle = world.timeofday + (10 * 120 * errors)
|
||||
else
|
||||
error += " Attempting retry on [ip]."
|
||||
log_subsystem_ipintel(error)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user