diff --git a/net/http/istestnetip.c b/net/http/istestnetip.c index be57a1d18..273f32e61 100644 --- a/net/http/istestnetip.c +++ b/net/http/istestnetip.c @@ -24,6 +24,6 @@ */ bool IsTestnetIp(uint32_t x) { return (((x & 0xFFFFFF00u) == 0xC0000200u) /* 192.0.2.0/24 */ || - ((x & 0xFFFFFF00u) == 0xC0000200u) /* 198.51.100.0/24 */ || + ((x & 0xFFFFFF00u) == 0x0c6336400) /* 198.51.100.0/24 */ || ((x & 0xFFFFFF00u) == 0xCB007100u) /* 203.0.113.0/24 */); } diff --git a/net/turfwar/.init.lua b/net/turfwar/.init.lua index fd037966f..5380a8ec4 100644 --- a/net/turfwar/.init.lua +++ b/net/turfwar/.init.lua @@ -18,7 +18,6 @@ RELAY_HEADERS_TO_CLIENT = { 'Content-Type', 'Last-Modified', 'Referrer-Policy', - 'Vary', } function OnServerStart() @@ -30,10 +29,27 @@ function OnWorkerStart() assert(unix.setrlimit(unix.RLIMIT_RSS, 2*1024*1024)) assert(unix.setrlimit(unix.RLIMIT_CPU, 2)) assert(unix.unveil(nil, nil)) - assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) + assert(unix.pledge("stdio inet unix", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) end function OnHttpRequest() + local ip = GetClientAddr() + if not IsTrustedIp(ip) then + local tok = AcquireToken(ip) + if tok < 2 then + if Blackhole(ip) then + Log(kLogWarn, "banned %s" % {FormatIp(ip)}) + else + Log(kLogWarn, "failed to ban %s" % {FormatIp(ip)}) + end + end + if tok < 30 then + ServeError(429) + SetHeader('Connection', 'close') + Log(kLogWarn, "warned %s who has %d tokens" % {FormatIp(ip), tok}) + return + end + end local url = 'http://127.0.0.1' .. EscapePath(GetPath()) local name = GetParam('name') if name then @@ -49,7 +65,7 @@ function OnHttpRequest() ['Referer'] = GetHeader('Referer'), ['Sec-CH-UA-Platform'] = GetHeader('Sec-CH-UA-Platform'), ['User-Agent'] = GetHeader('User-Agent'), - ['X-Forwarded-For'] = FormatIp(GetClientAddr())}}) + ['X-Forwarded-For'] = FormatIp(ip)}}) if status then SetStatus(status) for k,v in pairs(RELAY_HEADERS_TO_CLIENT) do diff --git a/net/turfwar/blackholed.c b/net/turfwar/blackholed.c index c83f0202b..7440a07d3 100644 --- a/net/turfwar/blackholed.c +++ b/net/turfwar/blackholed.c @@ -46,6 +46,7 @@ #include "libc/sysv/consts/timer.h" #include "libc/time/struct/tm.h" #include "net/http/http.h" +#include "net/http/ip.h" #include "third_party/getopt/getopt.h" #include "third_party/musl/passwd.h" @@ -355,7 +356,7 @@ void WritePid(void) { bool IsMyIp(uint32_t ip) { uint32_t *p; for (p = g_myips; *p; ++p) { - if (ip == *p) { + if (ip == *p && !IsTestnetIp(ip)) { return true; } } diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 71ef1c908..8a82e40ff 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -1029,8 +1029,6 @@ void *HttpWorker(void *arg) { "Cache-Control: max-age=3600, private\r\n" "Date: "); p = FormatDate(p); - p = stpcpy(p, "\r\nX-Token-Count: "); - p = FormatInt32(p, CountTokens(g_tok.b, ip, TB_CIDR)); p = stpcpy(p, "\r\nContent-Length: "); p = FormatInt32(p, strlen(ipbuf)); p = stpcpy(p, "\r\n\r\n"); diff --git a/tool/net/help.txt b/tool/net/help.txt index 9ee031b39..65f8f305d 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -2038,6 +2038,25 @@ FUNCTIONS `ip` should be an IPv4 address and this defaults to GetClientAddr(), although other interpretations of its meaning are possible. + Blackhole(ip:uint32) + └─→ bool + + Sends IP address to blackholed service. + + ProgramTokenBucket() needs to be called beforehand. The default + settings will blackhole automatically, during the accept() loop + based on the banned threshold. However if your Lua code calls + AcquireToken() manually, then you'll need this function to take + action on the returned values. + + This function returns true if a datagram could be sent sucessfully. + Otherwise false is returned, which can happen if blackholed isn't + running, or if a lot of processes are sending messages to it and the + operation would have blocked. + + It's assumed that the blackholed service is running locally in the + background. + ──────────────────────────────────────────────────────────────────────────────── CONSTANTS diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 44712918b..9dfc9fe36 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -897,7 +897,8 @@ static bool IsTrustedIp(uint32_t ip) { uint32_t *p; if (interfaces) { for (p = interfaces; *p; ++p) { - if (ip == *p) { + if (ip == *p && !IsTestnetIp(ip)) { + DEBUGF("(token) ip is trusted because it's %s", "a local interface"); return true; } } @@ -905,12 +906,19 @@ static bool IsTrustedIp(uint32_t ip) { if (trustedips.n) { for (i = 0; i < trustedips.n; ++i) { if ((ip & trustedips.p[i].mask) == trustedips.p[i].ip) { + DEBUGF("(token) ip is trusted because it's %s", "whitelisted"); return true; } } return false; + } else if (IsPrivateIp(ip) && !IsTestnetIp(ip)) { + DEBUGF("(token) ip is trusted because it's %s", "private"); + return true; + } else if (IsLoopbackIp(ip)) { + DEBUGF("(token) ip is trusted because it's %s", "loopback"); + return true; } else { - return IsPrivateIp(ip) || IsLoopbackIp(ip); + return false; } } @@ -4752,20 +4760,34 @@ static int LuaIsAssetCompressed(lua_State *L) { return 1; } -static void Blackhole(uint32_t ip) { +static bool Blackhole(uint32_t ip) { char buf[4]; - if (blackhole.fd > 0) return; + if (blackhole.fd <= 0) return false; WRITE32BE(buf, ip); if (sendto(blackhole.fd, &buf, 4, 0, (struct sockaddr *)&blackhole.addr, - sizeof(blackhole.addr)) == -1) { - VERBOSEF("error: sendto(%s) failed: %m\n", blackhole.addr.sun_path); + sizeof(blackhole.addr)) != -1) { + return true; + } else { + VERBOSEF("(token) sendto(%s) failed: %m", blackhole.addr.sun_path); errno = 0; + return false; } } +static int LuaBlackhole(lua_State *L) { + lua_Integer ip; + ip = luaL_checkinteger(L, 1); + if (!(0 <= ip && ip <= 0xffffffff)) { + luaL_argerror(L, 1, "ip out of range"); + unreachable; + } + lua_pushboolean(L, Blackhole(ip)); + return 1; +} + wontreturn static void Replenisher(void) { struct timespec ts; - VERBOSEF("token replenish worker started"); + VERBOSEF("(token) replenish worker started"); signal(SIGINT, OnTerm); signal(SIGHUP, OnTerm); signal(SIGTERM, OnTerm); @@ -4779,8 +4801,9 @@ wontreturn static void Replenisher(void) { } ReplenishTokens(tokenbucket.w, (1ul << tokenbucket.cidr) / 8); ts = _timespec_add(ts, tokenbucket.replenish); + DEBUGF("(token) replenished tokens"); } - VERBOSEF("token replenish worker exiting"); + VERBOSEF("(token) replenish worker exiting"); _Exit(0); } @@ -4846,7 +4869,7 @@ static int LuaProgramTokenBucket(lua_State *L) { luaL_argerror(L, 5, "require ban <= ignore"); unreachable; } - INFOF("deploying %,ld buckets " + INFOF("(token) deploying %,ld buckets " "(one for every %ld ips) " "each holding 127 tokens which " "replenish %g times per second, " @@ -4867,14 +4890,14 @@ static int LuaProgramTokenBucket(lua_State *L) { strlcpy(blackhole.addr.sun_path, "/var/run/blackhole.sock", sizeof(blackhole.addr.sun_path)); if ((blackhole.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { - WARNF("error: socket(AF_UNIX) failed: %m"); + WARNF("(token) socket(AF_UNIX) failed: %m"); ban = -1; } else if (sendto(blackhole.fd, &testip, 4, 0, (struct sockaddr *)&blackhole.addr, sizeof(blackhole.addr)) == -1) { - WARNF("error: sendto(%`'s) failed: %m", blackhole.addr.sun_path); - WARNF("redbean isn't able to protect your kernel from level 4 ddos"); - WARNF("please run the blackholed program, see https://justine.lol/"); + WARNF("(token) error: sendto(%`'s) failed: %m", blackhole.addr.sun_path); + WARNF("(token) redbean isn't able to protect your kernel from ddos"); + WARNF("(token) please run the blackholed program; see our website!"); } } tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE)); @@ -5212,6 +5235,7 @@ static const luaL_Reg kLuaFuncs[] = { {"oct", LuaOct}, // #ifndef UNSECURE {"AcquireToken", LuaAcquireToken}, // + {"Blackhole", LuaBlackhole}, // undocumented {"CountTokens", LuaCountTokens}, // {"EvadeDragnetSurveillance", LuaEvadeDragnetSurveillance}, // {"GetSslIdentity", LuaGetSslIdentity}, // @@ -6122,7 +6146,6 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { } else { return ServeError(500, "Internal Server Error"); } -#if 0 // TODO(jart): re-enable me } else if (!IsTiny() && cpm.msg.method != kHttpHead && !IsSslCompressed() && ClientAcceptsGzip() && !(a->file && @@ -6132,7 +6155,6 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { MeasureEntropy(cpm.content, 1000) < 7))) { WARNF("serving compressed asset"); p = ServeAssetCompressed(a); -#endif } else { p = ServeAssetIdentity(a, ct); } @@ -6700,32 +6722,38 @@ static int HandleConnection(size_t i) { LockInc(&shared->c.accepts); GetClientAddr(&ip, 0); if (tokenbucket.cidr && tokenbucket.reject >= 0) { - if (!IsLoopbackIp(ip) && !IsTrustedIp(ip)) { + if (!IsTrustedIp(ip)) { tok = AcquireToken(tokenbucket.b, ip, tokenbucket.cidr); if (tok <= tokenbucket.ban && tokenbucket.ban >= 0) { - WARNF("(srvr) banning %hhu.%hhu.%hhu.%hhu who only has %d tokens", + WARNF("(token) banning %hhu.%hhu.%hhu.%hhu who only has %d tokens", ip >> 24, ip >> 16, ip >> 8, ip, tok); LockInc(&shared->c.bans); Blackhole(ip); close(client); return 0; } else if (tok <= tokenbucket.ignore && tokenbucket.ignore >= 0) { + DEBUGF("(token) ignoring %hhu.%hhu.%hhu.%hhu who only has %d tokens", + ip >> 24, ip >> 16, ip >> 8, ip, tok); LockInc(&shared->c.ignores); close(client); return 0; } else if (tok < tokenbucket.reject) { - WARNF("(srvr) rejecting %hhu.%hhu.%hhu.%hhu who only has %d tokens", + WARNF("(token) rejecting %hhu.%hhu.%hhu.%hhu who only has %d tokens", ip >> 24, ip >> 16, ip >> 8, ip, tok); LockInc(&shared->c.rejects); SendTooManyRequests(); close(client); return 0; + } else { + DEBUGF("(token) %hhu.%hhu.%hhu.%hhu has %d tokens", ip >> 24, + ip >> 16, ip >> 8, ip, tok - 1); } } else { - DEBUGF( - "(srvr) won't acquire token for whitelisted ip %hhu.%hhu.%hhu.%hhu", - ip >> 24, ip >> 16, ip >> 8, ip); + DEBUGF("(token) won't acquire token for trusted ip %hhu.%hhu.%hhu.%hhu", + ip >> 24, ip >> 16, ip >> 8, ip); } + } else { + DEBUGF("(token) can't acquire accept() token for client"); } startconnection = _timespec_real(); if (UNLIKELY(maxworkers) && shared->workers >= maxworkers) {