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

View file

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

View file

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

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/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);

View file

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

View file

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

View file

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