Make some more fixes to prod

This commit is contained in:
Justine Tunney 2022-10-19 13:10:00 -07:00
parent 69bee64a59
commit da336b3ea8
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
6 changed files with 90 additions and 28 deletions

View file

@ -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 */);
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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");

View file

@ -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

View file

@ -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) {