Make some last minute production changes

This commit is contained in:
Justine Tunney 2022-10-19 10:00:29 -07:00
parent f7ff77d865
commit 69bee64a59
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
8 changed files with 166 additions and 61 deletions

View file

@ -1,31 +1,49 @@
-- reverse proxy for turfwar -- reverse proxy for turfwar
ProgramPort(443) if IsDaemon() then
ProgramTokenBucket() ProgramPort(443)
ProgramUid(65534)
ProgramUid(65534)
ProgramLogPath('/var/log/turfbean.log')
ProgramPidPath('/var/log/turfbean.pid')
ProgramTrustedIp(ParseIp(Slurp('/etc/justine-ip.txt')), 32);
ProgramCertificate(Slurp('/etc/letsencrypt/live/ipv4.games-ecdsa/fullchain.pem'))
ProgramPrivateKey(Slurp('/etc/letsencrypt/live/ipv4.games-ecdsa/privkey.pem'))
end
RELAY_HEADERS_TO_CLIENT = { RELAY_HEADERS_TO_CLIENT = {
'Access-Control-Allow-Origin', 'Access-Control-Allow-Origin',
'Cache-Control', 'Cache-Control',
'Connection', 'Connection',
'Content-Encoding',
'Content-Type', 'Content-Type',
'Last-Modified', 'Last-Modified',
'Referrer-Policy', 'Referrer-Policy',
'Vary', 'Vary',
} }
function OnServerStart()
ProgramTokenBucket()
assert(unix.setrlimit(unix.RLIMIT_NPROC, 1000, 1000))
end
function OnWorkerStart() 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.unveil(nil, nil))
assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM))
end end
function OnHttpRequest() function OnHttpRequest()
local url = 'http://127.0.0.1' .. EscapePath(GetPath())
local name = GetParam('name')
if name then
url = url .. '?name=' .. EscapeParam(name)
end
local status, headers, body = local status, headers, body =
Fetch('http://127.0.0.1' .. EscapePath(GetPath()), Fetch(url,
{method = GetMethod(), {method = GetMethod(),
headers = { headers = {
['Accept'] = GetHeader('Accept'), ['Accept'] = GetHeader('Accept'),
['Accept-Encoding'] = GetHeader('Accept-Encoding'),
['CF-IPCountry'] = GetHeader('CF-IPCountry'), ['CF-IPCountry'] = GetHeader('CF-IPCountry'),
['If-Modified-Since'] = GetHeader('If-Modified-Since'), ['If-Modified-Since'] = GetHeader('If-Modified-Since'),
['Referer'] = GetHeader('Referer'), ['Referer'] = GetHeader('Referer'),
@ -39,7 +57,7 @@ function OnHttpRequest()
end end
Write(body) Write(body)
else else
err = headers local err = headers
Log(kLogError, "proxy failed %s" % {err}) Log(kLogError, "proxy failed %s" % {err})
ServeError(503) ServeError(503)
end end

View file

@ -37,18 +37,11 @@ int main(int argc, char *argv[]) {
} }
int fd; int fd;
struct sockaddr_un addr = { struct sockaddr_un addr = {AF_UNIX, "/var/run/blackhole.sock"};
AF_UNIX,
"/var/run/blackhole.sock",
};
if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno)); kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno));
return 3; return 3;
} }
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
kprintf("error: connect(%#s) failed: %s\n", addr.sun_path, strerror(errno));
return 4;
}
int rc = 0; int rc = 0;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
@ -56,8 +49,9 @@ int main(int argc, char *argv[]) {
char buf[4]; char buf[4];
if ((ip = ParseIp(argv[i], -1)) != -1) { if ((ip = ParseIp(argv[i], -1)) != -1) {
WRITE32BE(buf, ip); WRITE32BE(buf, ip);
if (write(fd, buf, 4) == -1) { if (sendto(fd, buf, 4, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
kprintf("error: write() failed: %s\n", strerror(errno)); kprintf("error: sendto(%#s) failed: %s\n", addr.sun_path,
strerror(errno));
rc |= 2; rc |= 2;
} }
} else { } else {

View file

@ -45,6 +45,7 @@
#include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/timer.h" #include "libc/sysv/consts/timer.h"
#include "libc/time/struct/tm.h" #include "libc/time/struct/tm.h"
#include "net/http/http.h"
#include "third_party/getopt/getopt.h" #include "third_party/getopt/getopt.h"
#include "third_party/musl/passwd.h" #include "third_party/musl/passwd.h"
@ -55,12 +56,13 @@
#define DEFAULT_LOGNAME "/var/log/blackhole.log" #define DEFAULT_LOGNAME "/var/log/blackhole.log"
#define DEFAULT_PIDNAME "/var/run/blackhole.pid" #define DEFAULT_PIDNAME "/var/run/blackhole.pid"
#define DEFAULT_SOCKNAME "/var/run/blackhole.sock" #define DEFAULT_SOCKNAME "/var/run/blackhole.sock"
#define GETOPTS "L:S:P:M:G:dh" #define GETOPTS "L:S:P:M:G:W:dh"
#define USAGE \ #define USAGE \
"\ "\
Usage: blackholed [-hdLPSMG]\n\ Usage: blackholed [-hdLPSMGW]\n\
-h help\n\ -h help\n\
-d daemonize\n\ -d daemonize\n\
-W IP whitelist ip address\n\
-L PATH log file name (default: " DEFAULT_LOGNAME ")\n\ -L PATH log file name (default: " DEFAULT_LOGNAME ")\n\
-P PATH pid file name (default: " DEFAULT_PIDNAME ")\n\ -P PATH pid file name (default: " DEFAULT_PIDNAME ")\n\
-S PATH socket file name (default: " DEFAULT_SOCKNAME ")\n\ -S PATH socket file name (default: " DEFAULT_SOCKNAME ")\n\
@ -131,6 +133,7 @@ const char *g_sockname;
const char *g_iptables; const char *g_iptables;
sig_atomic_t g_shutdown; sig_atomic_t g_shutdown;
struct SortedInts g_blocked; struct SortedInts g_blocked;
struct SortedInts g_whitelisted;
static wontreturn void ShowUsage(int fd, int rc) { static wontreturn void ShowUsage(int fd, int rc) {
write(fd, USAGE, sizeof(USAGE) - 1); write(fd, USAGE, sizeof(USAGE) - 1);
@ -139,8 +142,23 @@ static wontreturn void ShowUsage(int fd, int rc) {
_Exit(rc); _Exit(rc);
} }
static void GetOpts(int argc, char *argv[]) { char *GetTimestamp(void) {
struct timespec ts;
static struct tm tm;
static int64_t last;
static char str[27];
clock_gettime(0, &ts);
if (ts.tv_sec != last) {
localtime_r(&ts.tv_sec, &tm);
last = ts.tv_sec;
}
iso8601us(str, &tm, ts.tv_nsec);
return str;
}
void GetOpts(int argc, char *argv[]) {
int opt; int opt;
int64_t ip;
g_sockmode = 0777; g_sockmode = 0777;
g_pidname = DEFAULT_PIDNAME; g_pidname = DEFAULT_PIDNAME;
g_logname = DEFAULT_LOGNAME; g_logname = DEFAULT_LOGNAME;
@ -165,6 +183,16 @@ static void GetOpts(int argc, char *argv[]) {
case 'M': case 'M':
g_sockmode = strtol(optarg, 0, 8) & 0777; g_sockmode = strtol(optarg, 0, 8) & 0777;
break; break;
case 'W':
if ((ip = ParseIp(optarg, -1)) != -1) {
if (InsertInt(&g_whitelisted, ip, true)) {
LOG("whitelisted %s", optarg);
}
} else {
kprintf("error: could not parse -W %#s IP address\n", optarg);
_Exit(1);
}
break;
case 'h': case 'h':
ShowUsage(1, 0); ShowUsage(1, 0);
default: default:
@ -173,20 +201,6 @@ static void GetOpts(int argc, char *argv[]) {
} }
} }
char *GetTimestamp(void) {
struct timespec ts;
static struct tm tm;
static int64_t last;
static char str[27];
clock_gettime(0, &ts);
if (ts.tv_sec != last) {
localtime_r(&ts.tv_sec, &tm);
last = ts.tv_sec;
}
iso8601us(str, &tm, ts.tv_nsec);
return str;
}
void OnTerm(int sig) { void OnTerm(int sig) {
char tmp[15]; char tmp[15];
LOG("got %s", strsignal_r(sig, tmp)); LOG("got %s", strsignal_r(sig, tmp));
@ -278,9 +292,11 @@ void Daemonize(void) {
} }
void UseLog(void) { void UseLog(void) {
_npassert(dup2(g_logfd, 2) == 2); if (g_logfd > 0) {
if (g_logfd != 2) { _npassert(dup2(g_logfd, 2) == 2);
_npassert(!close(g_logfd)); if (g_logfd != 2) {
_npassert(!close(g_logfd));
}
} }
} }
@ -426,6 +442,7 @@ int main(int argc, char *argv[]) {
if ((ip = READ32BE(msg))) { if ((ip = READ32BE(msg))) {
if (IsMyIp(ip) || // nics if (IsMyIp(ip) || // nics
ContainsInt(&g_whitelisted, ip) || // protected
(ip & 0xff000000) == 0x00000000 || // 0.0.0.0/8 (ip & 0xff000000) == 0x00000000 || // 0.0.0.0/8
(ip & 0xff000000) == 0x7f000000) { // 127.0.0.0/8 (ip & 0xff000000) == 0x7f000000) { // 127.0.0.0/8
LOG("won't block %s", FormatIp(ip)); LOG("won't block %s", FormatIp(ip));

View file

@ -1,5 +0,0 @@
#!/bin/sh
make -j16 o//net/turfwar/blackholed.elf &&
sudo chown root o//net/turfwar/blackholed.elf &&
sudo chmod 06755 o//net/turfwar/blackholed.elf &&
exec o//net/turfwar/blackholed.elf

View file

@ -39,6 +39,7 @@
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/mem/gc.h" #include "libc/mem/gc.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/mem/sortedints.internal.h"
#include "libc/nexgen32e/crc32.h" #include "libc/nexgen32e/crc32.h"
#include "libc/paths.h" #include "libc/paths.h"
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
@ -118,13 +119,14 @@
#define TB_BYTES (1u << TB_CIDR) #define TB_BYTES (1u << TB_CIDR)
#define TB_WORDS (TB_BYTES / 8) #define TB_WORDS (TB_BYTES / 8)
#define GETOPTS "idvp:w:k:" #define GETOPTS "idvp:w:k:W:"
#define USAGE \ #define USAGE \
"\ "\
Usage: turfwar.com [-dv] ARGS...\n\ Usage: turfwar.com [-dv] ARGS...\n\
-i integrity check and vacuum at startup\n\ -i integrity check and vacuum at startup\n\
-d daemonize\n\ -d daemonize\n\
-v verbosity\n\ -v verbosity\n\
-W IP whitelist\n\
-p INT port\n\ -p INT port\n\
-w INT workers\n\ -w INT workers\n\
-k INT keepalive\n\ -k INT keepalive\n\
@ -247,6 +249,7 @@ bool g_daemonize;
int g_port = PORT; int g_port = PORT;
int g_workers = WORKERS; int g_workers = WORKERS;
int g_keepalive = KEEPALIVE_MS; int g_keepalive = KEEPALIVE_MS;
struct SortedInts g_whitelisted;
// lifecycle vars // lifecycle vars
pthread_t g_listener; pthread_t g_listener;
@ -417,7 +420,7 @@ bool Blackhole(uint32_t ip) {
sizeof(g_blackhole.addr)) == 4) { sizeof(g_blackhole.addr)) == 4) {
return true; return true;
} else { } else {
kprintf("error: sendto(/var/run/blackhole.sock) failed: %s\n", kprintf("error: sendto(%#s) failed: %s\n", g_blackhole.addr.sun_path,
strerror(errno)); strerror(errno));
return false; return false;
} }
@ -891,7 +894,8 @@ void *HttpWorker(void *arg) {
ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16,
ip >> 8, ip); ip >> 8, ip);
if (!ipv6 && (tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 32) { if (!ipv6 && !ContainsInt(&g_whitelisted, ip) &&
(tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 32) {
if (tok > 4) { if (tok > 4) {
LOG("%s rate limiting client\n", ipbuf, msg->version); LOG("%s rate limiting client\n", ipbuf, msg->version);
Write(client.sock, "HTTP/1.1 429 Too Many Requests\r\n" Write(client.sock, "HTTP/1.1 429 Too Many Requests\r\n"
@ -1342,8 +1346,9 @@ void OnCtrlC(int sig) {
} }
// parses cli arguments // parses cli arguments
static void GetOpts(int argc, char *argv[]) { void GetOpts(int argc, char *argv[]) {
int opt; int opt;
int64_t ip;
while ((opt = getopt(argc, argv, GETOPTS)) != -1) { while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
switch (opt) { switch (opt) {
case 'i': case 'i':
@ -1364,6 +1369,16 @@ static void GetOpts(int argc, char *argv[]) {
case 'v': case 'v':
++__log_level; ++__log_level;
break; break;
case 'W':
if ((ip = ParseIp(optarg, -1)) != -1) {
if (InsertInt(&g_whitelisted, ip, true)) {
LOG("whitelisted %s", optarg);
}
} else {
kprintf("error: could not parse -w %#s IP address\n", optarg);
_Exit(1);
}
break;
case '?': case '?':
write(1, USAGE, sizeof(USAGE) - 1); write(1, USAGE, sizeof(USAGE) - 1);
exit(0); exit(0);

View file

@ -20,6 +20,7 @@
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/struct/timeval.h" #include "libc/calls/struct/timeval.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h"
#include "libc/nt/version.h" #include "libc/nt/version.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/sock/sock.h" #include "libc/sock/sock.h"
@ -119,3 +120,58 @@ TEST(unix, stream) {
EXPECT_EQ(0, WEXITSTATUS(ws)); EXPECT_EQ(0, WEXITSTATUS(ws));
alarm(0); alarm(0);
} }
TEST(unix, serverGoesDown_deletedSockFile) { // field of landmine
if (IsWindows()) return;
int ws, rc;
char buf[8] = {0};
uint32_t len = sizeof(struct sockaddr_un);
struct sockaddr_un addr = {AF_UNIX, "foo.sock"};
ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0));
ASSERT_SYS(0, 0, bind(3, (void *)&addr, len));
ASSERT_SYS(0, 4, socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0));
ASSERT_SYS(0, 0, connect(4, (void *)&addr, len));
ASSERT_SYS(0, 5, write(4, "hello", 5));
ASSERT_SYS(0, 5, read(3, buf, 8));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(IsBsd() ? ECONNRESET : ECONNREFUSED, -1, write(4, "hello", 5));
ASSERT_SYS(0, 0, unlink(addr.sun_path));
ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0));
ASSERT_SYS(0, 0, bind(3, (void *)&addr, len));
rc = write(4, "hello", 5);
ASSERT_TRUE(rc == -1 && (errno == ECONNRESET || //
errno == ENOTCONN || //
errno == ECONNREFUSED || //
errno == EDESTADDRREQ));
errno = 0;
ASSERT_SYS(0, 0, close(4));
ASSERT_SYS(0, 4, socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0));
ASSERT_SYS(0, 0, connect(4, (void *)&addr, len));
ASSERT_SYS(0, 5, write(4, "hello", 5));
ASSERT_SYS(0, 5, read(3, buf, 8));
ASSERT_SYS(0, 0, close(4));
ASSERT_SYS(0, 0, close(3));
}
TEST(unix, serverGoesDown_usingSendTo_unlink) { // much easier
if (IsWindows()) return;
int ws, rc;
char buf[8] = {0};
uint32_t len = sizeof(struct sockaddr_un);
struct sockaddr_un addr = {AF_UNIX, "foo.sock"};
ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0));
ASSERT_SYS(0, 0, bind(3, (void *)&addr, len));
ASSERT_SYS(0, 4, socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0));
ASSERT_SYS(0, 5, sendto(4, "hello", 5, 0, (void *)&addr, len));
ASSERT_SYS(0, 5, read(3, buf, 8));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(ECONNREFUSED, -1, sendto(4, "hello", 5, 0, (void *)&addr, len));
ASSERT_SYS(0, 0, unlink(addr.sun_path));
ASSERT_SYS(ENOENT, -1, sendto(4, "hello", 5, 0, (void *)&addr, len));
ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0));
ASSERT_SYS(0, 0, bind(3, (void *)&addr, len));
ASSERT_SYS(0, 5, sendto(4, "hello", 5, 0, (void *)&addr, len));
ASSERT_SYS(0, 5, read(3, buf, 8));
ASSERT_SYS(0, 0, close(4));
ASSERT_SYS(0, 0, close(3));
}

View file

@ -1935,6 +1935,12 @@ FUNCTIONS
If the client ignores this warning and keeps sending requests, until If the client ignores this warning and keeps sending requests, until
there's no tokens left, then the banhammer finally comes down. there's no tokens left, then the banhammer finally comes down.
function OnServerStart()
ProgramTokenBucket()
ProgramTrustedIp(ParseIp('x.x.x.x'), 32)
assert(unix.setrlimit(unix.RLIMIT_NPROC, 1000, 1000))
end
This model of network rate limiting generously lets people "burst" a This model of network rate limiting generously lets people "burst" a
tiny bit. For example someone might get a strong craving for content tiny bit. For example someone might get a strong craving for content
and smash the reload button in Chrome 64 times in a fow seconds. But and smash the reload button in Chrome 64 times in a fow seconds. But

View file

@ -2171,7 +2171,12 @@ static bool OpenZip(bool force) {
} }
} }
} else { } else {
WARNF("(zip) stat() error: %m"); // avoid noise if we setuid to user who can't see executable
if (errno == EACCES) {
VERBOSEF("(zip) stat(%`'s) error: %m", zpath);
} else {
WARNF("(zip) stat(%`'s) error: %m", zpath);
}
} }
return false; return false;
} }
@ -4749,10 +4754,12 @@ static int LuaIsAssetCompressed(lua_State *L) {
static void Blackhole(uint32_t ip) { static void Blackhole(uint32_t ip) {
char buf[4]; char buf[4];
if (blackhole.fd <= 0) return; if (blackhole.fd > 0) return;
WRITE32BE(buf, ip); WRITE32BE(buf, ip);
if (write(blackhole.fd, buf, 4) == -1) { if (sendto(blackhole.fd, &buf, 4, 0, (struct sockaddr *)&blackhole.addr,
WARNF("error: write(%s) failed: %m\n", blackhole.addr.sun_path); sizeof(blackhole.addr)) == -1) {
VERBOSEF("error: sendto(%s) failed: %m\n", blackhole.addr.sun_path);
errno = 0;
} }
} }
@ -4855,22 +4862,19 @@ static int LuaProgramTokenBucket(lua_State *L) {
if (ignore == -1) ignore = -128; if (ignore == -1) ignore = -128;
if (ban == -1) ban = -128; if (ban == -1) ban = -128;
if (ban >= 0 && (IsLinux() || IsBsd())) { if (ban >= 0 && (IsLinux() || IsBsd())) {
struct Blackhole bh; uint32_t testip = 0;
bh.addr.sun_family = AF_UNIX; blackhole.addr.sun_family = AF_UNIX;
strlcpy(bh.addr.sun_path, "/var/run/blackhole.sock", strlcpy(blackhole.addr.sun_path, "/var/run/blackhole.sock",
sizeof(bh.addr.sun_path)); sizeof(blackhole.addr.sun_path));
if ((bh.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { if ((blackhole.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) {
WARNF("error: socket(AF_UNIX) failed: %m"); WARNF("error: socket(AF_UNIX) failed: %m");
ban = -1; ban = -1;
} else if (connect(bh.fd, (struct sockaddr *)&bh.addr, sizeof(bh.addr)) == } else if (sendto(blackhole.fd, &testip, 4, 0,
-1) { (struct sockaddr *)&blackhole.addr,
WARNF("error: connect(%`'s) failed: %m", bh.addr.sun_path); 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("redbean isn't able to protect your kernel from level 4 ddos");
WARNF("please run the blackholed program, see https://justine.lol/"); WARNF("please run the blackholed program, see https://justine.lol/");
close(bh.fd);
ban = -1;
} else {
blackhole = bh;
} }
} }
tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE)); tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE));