From 69bee64a5944a7e808be0dbf231a38e630af8588 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 19 Oct 2022 10:00:29 -0700 Subject: [PATCH] Make some last minute production changes --- net/turfwar/.init.lua | 30 ++++++++++++++++---- net/turfwar/blackhole.c | 14 +++------- net/turfwar/blackholed.c | 57 +++++++++++++++++++++++++------------- net/turfwar/blackholed.sh | 5 ---- net/turfwar/turfwar.c | 23 ++++++++++++--- test/libc/sock/unix_test.c | 56 +++++++++++++++++++++++++++++++++++++ tool/net/help.txt | 6 ++++ tool/net/redbean.c | 36 +++++++++++++----------- 8 files changed, 166 insertions(+), 61 deletions(-) delete mode 100755 net/turfwar/blackholed.sh diff --git a/net/turfwar/.init.lua b/net/turfwar/.init.lua index deef6b15a..fd037966f 100644 --- a/net/turfwar/.init.lua +++ b/net/turfwar/.init.lua @@ -1,31 +1,49 @@ -- reverse proxy for turfwar -ProgramPort(443) -ProgramTokenBucket() +if IsDaemon() then + 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 = { 'Access-Control-Allow-Origin', 'Cache-Control', 'Connection', - 'Content-Encoding', 'Content-Type', 'Last-Modified', 'Referrer-Policy', 'Vary', } +function OnServerStart() + ProgramTokenBucket() + assert(unix.setrlimit(unix.RLIMIT_NPROC, 1000, 1000)) +end + 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)) end 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 = - Fetch('http://127.0.0.1' .. EscapePath(GetPath()), + Fetch(url, {method = GetMethod(), headers = { ['Accept'] = GetHeader('Accept'), - ['Accept-Encoding'] = GetHeader('Accept-Encoding'), ['CF-IPCountry'] = GetHeader('CF-IPCountry'), ['If-Modified-Since'] = GetHeader('If-Modified-Since'), ['Referer'] = GetHeader('Referer'), @@ -39,7 +57,7 @@ function OnHttpRequest() end Write(body) else - err = headers + local err = headers Log(kLogError, "proxy failed %s" % {err}) ServeError(503) end diff --git a/net/turfwar/blackhole.c b/net/turfwar/blackhole.c index 9f38ca0d1..a57eac330 100644 --- a/net/turfwar/blackhole.c +++ b/net/turfwar/blackhole.c @@ -37,18 +37,11 @@ int main(int argc, char *argv[]) { } int fd; - struct sockaddr_un addr = { - AF_UNIX, - "/var/run/blackhole.sock", - }; + struct sockaddr_un addr = {AF_UNIX, "/var/run/blackhole.sock"}; if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno)); 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; for (int i = 1; i < argc; ++i) { @@ -56,8 +49,9 @@ int main(int argc, char *argv[]) { char buf[4]; if ((ip = ParseIp(argv[i], -1)) != -1) { WRITE32BE(buf, ip); - if (write(fd, buf, 4) == -1) { - kprintf("error: write() failed: %s\n", strerror(errno)); + if (sendto(fd, buf, 4, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + kprintf("error: sendto(%#s) failed: %s\n", addr.sun_path, + strerror(errno)); rc |= 2; } } else { diff --git a/net/turfwar/blackholed.c b/net/turfwar/blackholed.c index 7a3e09fda..c83f0202b 100644 --- a/net/turfwar/blackholed.c +++ b/net/turfwar/blackholed.c @@ -45,6 +45,7 @@ #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/timer.h" #include "libc/time/struct/tm.h" +#include "net/http/http.h" #include "third_party/getopt/getopt.h" #include "third_party/musl/passwd.h" @@ -55,12 +56,13 @@ #define DEFAULT_LOGNAME "/var/log/blackhole.log" #define DEFAULT_PIDNAME "/var/run/blackhole.pid" #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 \ "\ -Usage: blackholed [-hdLPSMG]\n\ +Usage: blackholed [-hdLPSMGW]\n\ -h help\n\ -d daemonize\n\ + -W IP whitelist ip address\n\ -L PATH log file name (default: " DEFAULT_LOGNAME ")\n\ -P PATH pid file name (default: " DEFAULT_PIDNAME ")\n\ -S PATH socket file name (default: " DEFAULT_SOCKNAME ")\n\ @@ -131,6 +133,7 @@ const char *g_sockname; const char *g_iptables; sig_atomic_t g_shutdown; struct SortedInts g_blocked; +struct SortedInts g_whitelisted; static wontreturn void ShowUsage(int fd, int rc) { write(fd, USAGE, sizeof(USAGE) - 1); @@ -139,8 +142,23 @@ static wontreturn void ShowUsage(int fd, int 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; + int64_t ip; g_sockmode = 0777; g_pidname = DEFAULT_PIDNAME; g_logname = DEFAULT_LOGNAME; @@ -165,6 +183,16 @@ static void GetOpts(int argc, char *argv[]) { case 'M': g_sockmode = strtol(optarg, 0, 8) & 0777; 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': ShowUsage(1, 0); 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) { char tmp[15]; LOG("got %s", strsignal_r(sig, tmp)); @@ -278,9 +292,11 @@ void Daemonize(void) { } void UseLog(void) { - _npassert(dup2(g_logfd, 2) == 2); - if (g_logfd != 2) { - _npassert(!close(g_logfd)); + if (g_logfd > 0) { + _npassert(dup2(g_logfd, 2) == 2); + if (g_logfd != 2) { + _npassert(!close(g_logfd)); + } } } @@ -426,6 +442,7 @@ int main(int argc, char *argv[]) { if ((ip = READ32BE(msg))) { if (IsMyIp(ip) || // nics + ContainsInt(&g_whitelisted, ip) || // protected (ip & 0xff000000) == 0x00000000 || // 0.0.0.0/8 (ip & 0xff000000) == 0x7f000000) { // 127.0.0.0/8 LOG("won't block %s", FormatIp(ip)); diff --git a/net/turfwar/blackholed.sh b/net/turfwar/blackholed.sh deleted file mode 100755 index 053defc1a..000000000 --- a/net/turfwar/blackholed.sh +++ /dev/null @@ -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 diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 43a71df8c..71ef1c908 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -39,6 +39,7 @@ #include "libc/macros.internal.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" +#include "libc/mem/sortedints.internal.h" #include "libc/nexgen32e/crc32.h" #include "libc/paths.h" #include "libc/runtime/internal.h" @@ -118,13 +119,14 @@ #define TB_BYTES (1u << TB_CIDR) #define TB_WORDS (TB_BYTES / 8) -#define GETOPTS "idvp:w:k:" +#define GETOPTS "idvp:w:k:W:" #define USAGE \ "\ Usage: turfwar.com [-dv] ARGS...\n\ -i integrity check and vacuum at startup\n\ -d daemonize\n\ -v verbosity\n\ + -W IP whitelist\n\ -p INT port\n\ -w INT workers\n\ -k INT keepalive\n\ @@ -247,6 +249,7 @@ bool g_daemonize; int g_port = PORT; int g_workers = WORKERS; int g_keepalive = KEEPALIVE_MS; +struct SortedInts g_whitelisted; // lifecycle vars pthread_t g_listener; @@ -417,7 +420,7 @@ bool Blackhole(uint32_t ip) { sizeof(g_blackhole.addr)) == 4) { return true; } 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)); return false; } @@ -891,7 +894,8 @@ void *HttpWorker(void *arg) { ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, 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) { LOG("%s rate limiting client\n", ipbuf, msg->version); Write(client.sock, "HTTP/1.1 429 Too Many Requests\r\n" @@ -1342,8 +1346,9 @@ void OnCtrlC(int sig) { } // parses cli arguments -static void GetOpts(int argc, char *argv[]) { +void GetOpts(int argc, char *argv[]) { int opt; + int64_t ip; while ((opt = getopt(argc, argv, GETOPTS)) != -1) { switch (opt) { case 'i': @@ -1364,6 +1369,16 @@ static void GetOpts(int argc, char *argv[]) { case 'v': ++__log_level; 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 '?': write(1, USAGE, sizeof(USAGE) - 1); exit(0); diff --git a/test/libc/sock/unix_test.c b/test/libc/sock/unix_test.c index 3ac20bf97..2b34bed48 100644 --- a/test/libc/sock/unix_test.c +++ b/test/libc/sock/unix_test.c @@ -20,6 +20,7 @@ #include "libc/calls/internal.h" #include "libc/calls/struct/timeval.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/nt/version.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" @@ -119,3 +120,58 @@ TEST(unix, stream) { EXPECT_EQ(0, WEXITSTATUS(ws)); 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)); +} diff --git a/tool/net/help.txt b/tool/net/help.txt index d6e578096..9ee031b39 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -1935,6 +1935,12 @@ FUNCTIONS If the client ignores this warning and keeps sending requests, until 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 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 diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 1f5ca6b41..44712918b 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -2171,7 +2171,12 @@ static bool OpenZip(bool force) { } } } 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; } @@ -4749,10 +4754,12 @@ static int LuaIsAssetCompressed(lua_State *L) { static void Blackhole(uint32_t ip) { char buf[4]; - if (blackhole.fd <= 0) return; + if (blackhole.fd > 0) return; WRITE32BE(buf, ip); - if (write(blackhole.fd, buf, 4) == -1) { - WARNF("error: write(%s) failed: %m\n", blackhole.addr.sun_path); + 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); + errno = 0; } } @@ -4855,22 +4862,19 @@ static int LuaProgramTokenBucket(lua_State *L) { if (ignore == -1) ignore = -128; if (ban == -1) ban = -128; if (ban >= 0 && (IsLinux() || IsBsd())) { - struct Blackhole bh; - bh.addr.sun_family = AF_UNIX; - strlcpy(bh.addr.sun_path, "/var/run/blackhole.sock", - sizeof(bh.addr.sun_path)); - if ((bh.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { + uint32_t testip = 0; + blackhole.addr.sun_family = AF_UNIX; + 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"); ban = -1; - } else if (connect(bh.fd, (struct sockaddr *)&bh.addr, sizeof(bh.addr)) == - -1) { - WARNF("error: connect(%`'s) failed: %m", bh.addr.sun_path); + } 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/"); - close(bh.fd); - ban = -1; - } else { - blackhole = bh; } } tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE));