From d5469f7dc65b0406c60ab99a28ccf7b08ecb2a60 Mon Sep 17 00:00:00 2001 From: HF Date: Thu, 29 Jun 2023 17:17:14 +0200 Subject: [PATCH] add default cooldown for unregistered first-connections --- src/core/draw.js | 25 +++++++++++++++++++------ src/data/redis/cooldown.js | 3 ++- src/data/redis/lua/placePixel.lua | 29 +++++++++++++++-------------- src/socket/SocketServer.js | 4 +++- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/core/draw.js b/src/core/draw.js index 2e7e249..1af2e70 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -50,8 +50,12 @@ setInterval(() => { * @param i Chunk coordinates * @param j * @param pixels Array of individual pixels within the chunk, with: - * [[offset, color], [offset2, color2],...] - * Offset is the offset of the pixel within the chunk + * [[offset, color], [offset2, color2],...] + * Offset is the offset of the pixel within the chunk + * @param connectedTs Timestamp when connection got established. + * if the connection is younger than the cooldown of the canvas, + * we fill up the cd on first pixel to nerf one-connection + * ip-changing cheaters * @return Promise */ export default async function drawByOffsets( @@ -60,6 +64,7 @@ export default async function drawByOffsets( i, j, pixels, + connectedTs, ) { let wait = 0; let coolDown = 0; @@ -122,12 +127,13 @@ export default async function drawByOffsets( const bcd = canvas.bcd * factor; const pcd = (canvas.pcd) ? canvas.pcd * factor : bcd; + const userId = user.id; const pxlOffsets = []; /* * validate pixels */ - let ranked = canvas.ranked && user.id && pcd; + let ranked = canvas.ranked && userId && pcd; for (let u = 0; u < pixels.length; u += 1) { const [offset, color] = pixels[u]; pxlOffsets.push(offset); @@ -135,7 +141,7 @@ export default async function drawByOffsets( const [x, y, z] = getPixelFromChunkOffset(i, j, offset, canvasSize, is3d); pixelLogger.info( // eslint-disable-next-line max-len - `${startTime} ${user.ip} ${user.id} ${canvasId} ${x} ${y} ${z} ${color}`, + `${startTime} ${user.ip} ${userId} ${canvasId} ${x} ${y} ${z} ${color}`, ); const maxSize = (is3d) ? tileSize * tileSize * THREE_CANVAS_HEIGHT @@ -170,11 +176,17 @@ export default async function drawByOffsets( } } + const { cds } = canvas; + // start with almost filled cd on new connections + let cdIfNull = cds - pcd + 1000 - startTime + connectedTs; + if (cdIfNull < 0 || userId || bcd === 0) { + cdIfNull = 0; + } let needProxycheck; [retCode, pxlCnt, wait, coolDown, needProxycheck] = await allowPlace( ip, - user.id, + userId, user.country, ranked, canvasId, @@ -182,7 +194,8 @@ export default async function drawByOffsets( clrIgnore, req, bcd, pcd, - canvas.cds, + cds, + cdIfNull, pxlOffsets, ); diff --git a/src/data/redis/cooldown.js b/src/data/redis/cooldown.js index 74dc06e..28d37ab 100644 --- a/src/data/redis/cooldown.js +++ b/src/data/redis/cooldown.js @@ -39,6 +39,7 @@ export default function allowPlace( bcd, pcd, cds, + cdIfNull, pxls, ) { const isalKey = `${ALLOWED_PREFIX}:${ip}`; @@ -67,7 +68,7 @@ export default function allowPlace( return client.placePxl( // eslint-disable-next-line max-len isalKey, captKey, ipCdKey, idCdKey, chunkKey, rankset, dailyset, DAILY_CRANKED_KEY, PREV_DAY_TOP_KEY, - clrIgnore, bcd, pcd, cds, id, cc, req, + clrIgnore, bcd, pcd, cds, cdIfNull, id, cc, req, ...pxls, ); } diff --git a/src/data/redis/lua/placePixel.lua b/src/data/redis/lua/placePixel.lua index ba18f9b..e177d28 100644 --- a/src/data/redis/lua/placePixel.lua +++ b/src/data/redis/lua/placePixel.lua @@ -20,10 +20,10 @@ -- bcd: number baseCooldown (fixed to cdFactor and 0 if admin) -- pcd: number set pixel cooldown (fixed to cdFactor and 0 if admin) -- cds: max cooldown of canvas +-- cdIfNull: cooldown to use when no cooldown is stored -- userId: '0' if not logged in -- cc country code --- req: requirements of canvas --- 'nope', unsigned integer or 'top' +-- req: requirements of canvas ('nope', unsigned integer or 'top') -- off1, chunk offset of first pixel -- off2, chunk offset of second pixel -- ..., infinite pixels possible @@ -63,23 +63,23 @@ else end end -- check if requirements for canvas met -if ARGV[7] ~= "nope" then - if ARGV[5] == "0" then +if ARGV[8] ~= "nope" then + if ARGV[6] == "0" then -- not logged in ret[1] = 6 return ret; end - if ARGV[7] == "top" then - local pr = redis.call('zrank', KEYS[9], ARGV[5]) + if ARGV[8] == "top" then + local pr = redis.call('zrank', KEYS[9], ARGV[6]) if not pr or pr > 9 then -- not in yesterdays top 10 ret[1] = 12; return ret; end else - local req = tonumber(ARGV[7]) + local req = tonumber(ARGV[8]) if req > 0 then - local sc = tonumber(redis.call('zscore', KEYS[6], ARGV[5])) + local sc = tonumber(redis.call('zscore', KEYS[6], ARGV[6])) if not sc or sc < req then -- not enough pxls placed ret[1] = 7; @@ -91,7 +91,7 @@ end -- get cooldown of user local cd = redis.call('pttl', KEYS[3]) if cd < 0 then - cd = 0 + cd = tonumber(ARGV[5]) end if KEYS[4] ~= "nope" then local icd = redis.call('pttl', KEYS[4]) @@ -106,7 +106,7 @@ local cli = tonumber(ARGV[1]) local bcd = tonumber(ARGV[2]) local pcd = tonumber(ARGV[3]) local cds = tonumber(ARGV[4]) -for c = 8,#ARGV do +for c = 9,#ARGV do local off = tonumber(ARGV[c]) * 8 -- get color of pixel on canvas local sclr = redis.call('bitfield', KEYS[5], 'get', 'u8', off) @@ -144,10 +144,11 @@ if pxlcnt > 0 then end -- increment pixelcount if KEYS[7] ~= 'nope' then - redis.call('zincrby', KEYS[6], pxlcnt, ARGV[5]) - redis.call('zincrby', KEYS[7], pxlcnt, ARGV[5]) - if ARGV[6] ~= 'xx' then - redis.call('zincrby', KEYS[8], pxlcnt, ARGV[6]) + redis.call('zincrby', KEYS[6], pxlcnt, ARGV[6]) + redis.call('zincrby', KEYS[7], pxlcnt, ARGV[6]) + -- increase country stats only by registered users + if ARGV[7] ~= 'xx' then + redis.call('zincrby', KEYS[8], pxlcnt, ARGV[7]) end end end diff --git a/src/socket/SocketServer.js b/src/socket/SocketServer.js index f8b5b9d..d2caf3f 100644 --- a/src/socket/SocketServer.js +++ b/src/socket/SocketServer.js @@ -75,6 +75,7 @@ class SocketServer { wss.on('connection', (ws, req) => { ws.timeLastMsg = Date.now(); + ws.connectedTs = ws.timeLastMsg; ws.canvasId = null; const { user } = req; ws.user = user; @@ -538,7 +539,7 @@ class SocketServer { switch (opcode) { case PIXEL_UPDATE_OP: { - const { canvasId } = ws; + const { canvasId, connectedTs } = ws; if (canvasId === null) { logger.info(`Closing websocket without canvas from ${ip}`); @@ -560,6 +561,7 @@ class SocketServer { canvasId, i, j, pixels, + connectedTs, ); if (retCode > 9 && retCode !== 13) {