mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-30 00:08:30 +00:00
Make fixes and improvements
- Invent iso8601us() for faster timestamps - Improve --strace descriptions of sigset_t - Rebuild the Landlock Make bootstrap binary - Introduce MODE=sysv for non-Windows builds - Permit OFD fcntl() locks under pledge(flock) - redbean can now protect your kernel from ddos - Have vfork() fallback to sys_fork() not fork() - Change kmalloc() to not die when out of memory - Improve documentation for some termios functions - Rewrite putenv() and friends to conform to POSIX - Fix linenoise + strace verbosity issue on Windows - Fix regressions in our ability to show backtraces - Change redbean SetHeader() to no-op if value is nil - Improve fcntl() so SQLite locks work in non-WAL mode - Remove some unnecessary work during fork() on Windows - Create redbean-based SSL reverse proxy for IPv4 TurfWar - Fix ape/apeinstall.sh warning when using non-bash shells - Add ProgramTrustedIp(), and IsTrustedIp() APIs to redbean - Support $PWD, $UID, $GID, and $EUID in command interpreter - Introduce experimental JTqFpD APE prefix for non-Windows builds - Invent blackhole daemon for firewalling IP addresses via UNIX named socket - Add ProgramTokenBucket(), AcquireToken(), and CountTokens() APIs to redbean
This commit is contained in:
parent
648bf6555c
commit
f7ff77d865
209 changed files with 3818 additions and 998 deletions
|
@ -214,6 +214,7 @@ int ParseForwarded(const char *, size_t, uint32_t *, uint16_t *);
|
|||
bool IsMimeType(const char *, size_t, const char *);
|
||||
ssize_t Unchunk(struct HttpUnchunker *, char *, size_t, size_t *);
|
||||
const char *FindContentType(const char *, size_t);
|
||||
bool IsNoCompressExt(const char *, size_t);
|
||||
char *FoldHeader(struct HttpMessage *, char *, int, size_t *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
|
|
|
@ -57,6 +57,8 @@ o/$(MODE)/net/http/istestnetip.o: private \
|
|||
OVERRIDE_CFLAGS += \
|
||||
-Os
|
||||
|
||||
# we need -O3 because:
|
||||
# we're dividing by constants
|
||||
o/$(MODE)/net/http/formathttpdatetime.o: private\
|
||||
OVERRIDE_CFLAGS += \
|
||||
-O3
|
||||
|
|
73
net/http/isnocompressext.c
Normal file
73
net/http/isnocompressext.c
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/bswap.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/str/tab.internal.h"
|
||||
#include "net/http/http.h"
|
||||
|
||||
static const char kNoCompressExts[][8] = {
|
||||
"bz2", //
|
||||
"gif", //
|
||||
"gz", //
|
||||
"jpg", //
|
||||
"lz4", //
|
||||
"mp4", //
|
||||
"mpeg", //
|
||||
"mpg", //
|
||||
"png", //
|
||||
"webp", //
|
||||
"xz", //
|
||||
"zip", //
|
||||
};
|
||||
|
||||
static bool BisectNoCompressExts(uint64_t ext) {
|
||||
int c, m, l, r;
|
||||
l = 0;
|
||||
r = ARRAYLEN(kNoCompressExts) - 1;
|
||||
while (l <= r) {
|
||||
m = (l + r) >> 1;
|
||||
if (READ64BE(kNoCompressExts[m]) < ext) {
|
||||
l = m + 1;
|
||||
} else if (READ64BE(kNoCompressExts[m]) > ext) {
|
||||
r = m - 1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNoCompressExt(const char *p, size_t n) {
|
||||
int c, i;
|
||||
uint64_t w;
|
||||
if (n == -1) n = p ? strlen(p) : 0;
|
||||
if (n) {
|
||||
for (i = w = 0; n--;) {
|
||||
c = p[n] & 255;
|
||||
if (c == '.') break;
|
||||
if (++i > 8) return false;
|
||||
w <<= 8;
|
||||
w |= kToLower[c];
|
||||
}
|
||||
return BisectNoCompressExts(bswap_64(w));
|
||||
}
|
||||
return false;
|
||||
}
|
46
net/turfwar/.init.lua
Normal file
46
net/turfwar/.init.lua
Normal file
|
@ -0,0 +1,46 @@
|
|||
-- reverse proxy for turfwar
|
||||
|
||||
ProgramPort(443)
|
||||
ProgramTokenBucket()
|
||||
|
||||
RELAY_HEADERS_TO_CLIENT = {
|
||||
'Access-Control-Allow-Origin',
|
||||
'Cache-Control',
|
||||
'Connection',
|
||||
'Content-Encoding',
|
||||
'Content-Type',
|
||||
'Last-Modified',
|
||||
'Referrer-Policy',
|
||||
'Vary',
|
||||
}
|
||||
|
||||
function OnWorkerStart()
|
||||
assert(unix.unveil(nil, nil))
|
||||
assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM))
|
||||
end
|
||||
|
||||
function OnHttpRequest()
|
||||
local status, headers, body =
|
||||
Fetch('http://127.0.0.1' .. EscapePath(GetPath()),
|
||||
{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'),
|
||||
['Sec-CH-UA-Platform'] = GetHeader('Sec-CH-UA-Platform'),
|
||||
['User-Agent'] = GetHeader('User-Agent'),
|
||||
['X-Forwarded-For'] = FormatIp(GetClientAddr())}})
|
||||
if status then
|
||||
SetStatus(status)
|
||||
for k,v in pairs(RELAY_HEADERS_TO_CLIENT) do
|
||||
SetHeader(v, headers[v])
|
||||
end
|
||||
Write(body)
|
||||
else
|
||||
err = headers
|
||||
Log(kLogError, "proxy failed %s" % {err})
|
||||
ServeError(503)
|
||||
end
|
||||
end
|
70
net/turfwar/blackhole.c
Normal file
70
net/turfwar/blackhole.c
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/sock/struct/sockaddr.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/af.h"
|
||||
#include "libc/sysv/consts/ex.h"
|
||||
#include "libc/sysv/consts/sock.h"
|
||||
#include "net/http/http.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
if (argc < 2) {
|
||||
kprintf("usage: blackhole IP...\n");
|
||||
return EX_USAGE;
|
||||
}
|
||||
|
||||
int fd;
|
||||
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) {
|
||||
int64_t ip;
|
||||
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));
|
||||
rc |= 2;
|
||||
}
|
||||
} else {
|
||||
kprintf("error: bad ipv4 address: %s\n", argv[i]);
|
||||
rc |= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
448
net/turfwar/blackholed.c
Normal file
448
net/turfwar/blackholed.c
Normal file
|
@ -0,0 +1,448 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/blocksigs.internal.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/sigaction.h"
|
||||
#include "libc/calls/struct/sigset.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/midpoint.h"
|
||||
#include "libc/intrin/safemacros.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/mem/sortedints.internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/sock/struct/sockaddr.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/af.h"
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/ok.h"
|
||||
#include "libc/sysv/consts/sa.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/sysv/consts/sock.h"
|
||||
#include "libc/sysv/consts/timer.h"
|
||||
#include "libc/time/struct/tm.h"
|
||||
#include "third_party/getopt/getopt.h"
|
||||
#include "third_party/musl/passwd.h"
|
||||
|
||||
#define LOG(FMT, ...) \
|
||||
kprintf("%s %s:%d] " FMT "\n", GetTimestamp(), __FILE__, __LINE__, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
#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 USAGE \
|
||||
"\
|
||||
Usage: blackholed [-hdLPSMG]\n\
|
||||
-h help\n\
|
||||
-d daemonize\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\
|
||||
-M MODE socket mode bits (default: 0777)\n\
|
||||
-G GROUP socket group name or gid (default: n/a)\n\
|
||||
--assimilate change executable header to native format\n\
|
||||
--ftrace function call tracing\n\
|
||||
--strace system call tracing\n\
|
||||
\n\
|
||||
Usage:\n\
|
||||
sudo blackholed -d # run daemon\n\
|
||||
blackhole 1.2.3.4 # anyone can securely ban ips\n\
|
||||
\n\
|
||||
Protocol:\n\
|
||||
Send a 4 byte datagram to the unix socket file containing\n\
|
||||
the IPv4 address you want banned encoded using big endian\n\
|
||||
a.k.a. network byte order. We ignore these ips: 0.0.0.0/8\n\
|
||||
and 127.0.0.0/8 so sending 0 to the socket is a good test\n"
|
||||
|
||||
#define LINUX_DOCS \
|
||||
"\n\
|
||||
Linux Requirements:\n\
|
||||
sudo modprobe ip_tables\n\
|
||||
sudo echo ip_tables >>/etc/modules\n\
|
||||
\n\
|
||||
Administration Notes:\n\
|
||||
This program inserts IP bans into iptables raw prerouting, so\n\
|
||||
the kernel won't track the TCP connections of threat actors.\n\
|
||||
If you restart this program, then you should run\n\
|
||||
sudo iptables -t raw -F\n\
|
||||
to clear the IP blocks. It's a good idea to have a cron job\n\
|
||||
restart this daemon and clear the raw table daily. Use the\n\
|
||||
sudo iptables -t raw -L -vn\n\
|
||||
command to list the IP addresses that have been blocked.\n\
|
||||
\n"
|
||||
|
||||
#define BSD_DOCS \
|
||||
"\n\
|
||||
BSD Requirements:\n\
|
||||
kldload pf\n\
|
||||
echo 'table <badhosts> persist' >>/etc/pf.conf\n\
|
||||
echo 'block on em0 from <badhosts> to any' >>/etc/pf.conf\n\
|
||||
echo 'pf_enable=\"YES\"' >>/etc/rc.conf\n\
|
||||
echo 'pf_rules=\"/etc/pf.conf\"' >>/etc/rc.conf\n\
|
||||
/etc/rc.d/pf start\n\
|
||||
pfctl -t badhosts -T add 1.2.3.4\n\
|
||||
pfctl -t badhosts -T show\n\
|
||||
\n\
|
||||
Administration Notes:\n\
|
||||
If you restart this program, then you should run\n\
|
||||
pfctl -t badhosts -T flush\n\
|
||||
to clear the IP blocks. It's a good idea to have a cron job\n\
|
||||
restart this daemon and clear the raw table daily. Use the\n\
|
||||
pfctl -t badhosts -T show\n\
|
||||
command to list the IP addresses that have been blocked.\n\
|
||||
\n\
|
||||
"
|
||||
|
||||
int g_logfd;
|
||||
int g_sockmode;
|
||||
bool g_daemonize;
|
||||
uint32_t *g_myips;
|
||||
const char *g_group;
|
||||
const char *g_pfctl;
|
||||
const char *g_logname;
|
||||
const char *g_pidname;
|
||||
const char *g_sockname;
|
||||
const char *g_iptables;
|
||||
sig_atomic_t g_shutdown;
|
||||
struct SortedInts g_blocked;
|
||||
|
||||
static wontreturn void ShowUsage(int fd, int rc) {
|
||||
write(fd, USAGE, sizeof(USAGE) - 1);
|
||||
if (IsLinux()) write(fd, LINUX_DOCS, sizeof(LINUX_DOCS) - 1);
|
||||
if (IsBsd()) write(fd, BSD_DOCS, sizeof(BSD_DOCS) - 1);
|
||||
_Exit(rc);
|
||||
}
|
||||
|
||||
static void GetOpts(int argc, char *argv[]) {
|
||||
int opt;
|
||||
g_sockmode = 0777;
|
||||
g_pidname = DEFAULT_PIDNAME;
|
||||
g_logname = DEFAULT_LOGNAME;
|
||||
g_sockname = DEFAULT_SOCKNAME;
|
||||
while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
g_daemonize = true;
|
||||
break;
|
||||
case 'S':
|
||||
g_sockname = optarg;
|
||||
break;
|
||||
case 'L':
|
||||
g_logname = emptytonull(optarg);
|
||||
break;
|
||||
case 'P':
|
||||
g_pidname = emptytonull(optarg);
|
||||
break;
|
||||
case 'G':
|
||||
g_group = emptytonull(optarg);
|
||||
break;
|
||||
case 'M':
|
||||
g_sockmode = strtol(optarg, 0, 8) & 0777;
|
||||
break;
|
||||
case 'h':
|
||||
ShowUsage(1, 0);
|
||||
default:
|
||||
ShowUsage(2, 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
g_shutdown = sig;
|
||||
}
|
||||
|
||||
char *FormatIp(uint32_t ip) {
|
||||
static char ipbuf[16];
|
||||
ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16,
|
||||
ip >> 8, ip);
|
||||
return ipbuf;
|
||||
}
|
||||
|
||||
void BlockIp(uint32_t ip) {
|
||||
if (!vfork()) {
|
||||
if (g_iptables) {
|
||||
execve(g_iptables,
|
||||
(char *const[]){
|
||||
"iptables", //
|
||||
"-t", "raw", //
|
||||
"-I", "PREROUTING", //
|
||||
"-s", FormatIp(ip), //
|
||||
"-j", "DROP", //
|
||||
0, //
|
||||
},
|
||||
(char *const[]){0});
|
||||
} else if (g_pfctl) {
|
||||
execve(g_pfctl,
|
||||
(char *const[]){
|
||||
"pfctl", //
|
||||
"-t", "badhosts", //
|
||||
"-T", "add", //
|
||||
FormatIp(ip), //
|
||||
0, //
|
||||
},
|
||||
(char *const[]){0});
|
||||
}
|
||||
_Exit(127);
|
||||
}
|
||||
}
|
||||
|
||||
void RequireRoot(void) {
|
||||
if (geteuid()) {
|
||||
kprintf("error: need root privileges\n");
|
||||
ShowUsage(2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void ListenForTerm(void) {
|
||||
struct sigaction sa = {.sa_handler = OnTerm};
|
||||
_npassert(!sigaction(SIGTERM, &sa, 0));
|
||||
_npassert(!sigaction(SIGHUP, &sa, 0));
|
||||
_npassert(!sigaction(SIGINT, &sa, 0));
|
||||
}
|
||||
|
||||
void AutomaticallyHarvestZombies(void) {
|
||||
struct sigaction sa = {.sa_handler = SIG_IGN, .sa_flags = SA_NOCLDWAIT};
|
||||
_npassert(!sigaction(SIGCHLD, &sa, 0));
|
||||
}
|
||||
|
||||
void FindFirewall(void) {
|
||||
if (!access("/sbin/iptables", X_OK)) {
|
||||
g_iptables = "/sbin/iptables";
|
||||
} else if (!access("/usr/sbin/iptables", X_OK)) {
|
||||
g_iptables = "/usr/sbin/iptables";
|
||||
} else if (!access("/sbin/pfctl", X_OK)) {
|
||||
g_pfctl = "/sbin/pfctl";
|
||||
} else {
|
||||
kprintf("error: could not find `iptables` or `pfctl` command\n");
|
||||
ShowUsage(2, 3);
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
|
||||
void OpenLog(void) {
|
||||
if (!g_logname) return;
|
||||
if (!g_daemonize) return;
|
||||
if ((g_logfd = open(g_logname, O_WRONLY | O_APPEND | O_CREAT, 0644)) == -1) {
|
||||
kprintf("error: open(%#s) failed: %s\n", g_logname, strerror(errno));
|
||||
ShowUsage(2, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void Daemonize(void) {
|
||||
if (g_daemonize && daemon(false, false)) {
|
||||
kprintf("error: daemon() failed: %s\n", strerror(errno));
|
||||
ShowUsage(2, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void UseLog(void) {
|
||||
_npassert(dup2(g_logfd, 2) == 2);
|
||||
if (g_logfd != 2) {
|
||||
_npassert(!close(g_logfd));
|
||||
}
|
||||
}
|
||||
|
||||
void UninterruptibleSleep(int ms) {
|
||||
struct timespec ts =
|
||||
_timespec_add(_timespec_real(), _timespec_frommillis(ms));
|
||||
while (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0)) errno = 0;
|
||||
}
|
||||
|
||||
void Unlink(const char *path) {
|
||||
if (!path) return;
|
||||
if (!unlink(path)) {
|
||||
LOG("deleted %s", path);
|
||||
} else {
|
||||
if (errno != ENOENT) {
|
||||
LOG("error: unlink(%#s) failed: %s", path, strerror(errno));
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void WritePid(void) {
|
||||
ssize_t rc;
|
||||
int fd, pid;
|
||||
char buf[12] = {0};
|
||||
if (!g_pidname) return;
|
||||
if ((fd = open(g_pidname, O_RDWR | O_CREAT, 0644)) == -1) {
|
||||
LOG("error: open(%#s) failed: %s", g_pidname, strerror(errno));
|
||||
_Exit(4);
|
||||
}
|
||||
_npassert((rc = pread(fd, buf, 11, 0)) != -1);
|
||||
if (rc) {
|
||||
pid = atoi(buf);
|
||||
LOG("killing old blackholed process %d", pid);
|
||||
if (!kill(pid, SIGTERM)) {
|
||||
UninterruptibleSleep(100);
|
||||
if (kill(pid, SIGKILL)) {
|
||||
if (errno != ESRCH) {
|
||||
LOG("kill -KILL %s failed: %s", pid, strerror(errno));
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
} else {
|
||||
if (errno != ESRCH) {
|
||||
LOG("kill -TERM %d failed: %s", pid, strerror(errno));
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
}
|
||||
FormatInt32(buf, getpid());
|
||||
_npassert(!ftruncate(fd, 0));
|
||||
_npassert((rc = pwrite(fd, buf, strlen(buf), 0)) == strlen(buf));
|
||||
_npassert(!close(fd));
|
||||
}
|
||||
|
||||
bool IsMyIp(uint32_t ip) {
|
||||
uint32_t *p;
|
||||
for (p = g_myips; *p; ++p) {
|
||||
if (ip == *p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
if (closefrom(3))
|
||||
for (int i = 3; i < 256; ++i) //
|
||||
close(i);
|
||||
|
||||
GetOpts(argc, argv);
|
||||
RequireRoot();
|
||||
FindFirewall();
|
||||
OpenLog();
|
||||
Daemonize();
|
||||
UseLog();
|
||||
WritePid();
|
||||
Unlink(g_sockname);
|
||||
|
||||
if (!(g_myips = GetHostIps())) {
|
||||
LOG("failed to get host network interface addresses: %s", strerror(errno));
|
||||
}
|
||||
|
||||
int server;
|
||||
struct sockaddr_un addr = {AF_UNIX};
|
||||
strlcpy(addr.sun_path, g_sockname, sizeof(addr.sun_path));
|
||||
if ((server = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
|
||||
LOG("error: socket(AF_UNIX) failed: %s", strerror(errno));
|
||||
_Exit(3);
|
||||
}
|
||||
if (bind(server, (struct sockaddr *)&addr, sizeof(addr))) {
|
||||
LOG("error: bind(%s) failed: %s", g_sockname, strerror(errno));
|
||||
_Exit(4);
|
||||
}
|
||||
if (chmod(g_sockname, g_sockmode)) {
|
||||
LOG("error: chmod(%s, %o) failed: %s", g_sockname, g_sockmode,
|
||||
strerror(errno));
|
||||
_Exit(5);
|
||||
}
|
||||
if (g_group) {
|
||||
int gid;
|
||||
struct group *g;
|
||||
if (isdigit(*g_group)) {
|
||||
gid = atoi(g_group);
|
||||
} else if ((g = getgrnam(g_group))) {
|
||||
gid = g->gr_gid;
|
||||
} else {
|
||||
LOG("error: group %s not found: %s", g_group, strerror(errno));
|
||||
_Exit(6);
|
||||
}
|
||||
if (chown(g_sockname, -1, gid)) {
|
||||
LOG("error: chmod(%s, -1, %o) failed: %s", g_sockname, g_sockmode,
|
||||
strerror(errno));
|
||||
_Exit(7);
|
||||
}
|
||||
}
|
||||
|
||||
AutomaticallyHarvestZombies();
|
||||
ListenForTerm();
|
||||
|
||||
while (!g_shutdown) {
|
||||
ssize_t rc;
|
||||
uint32_t ip;
|
||||
char msg[16];
|
||||
|
||||
if (!(rc = read(server, msg, sizeof(msg)))) {
|
||||
LOG("error: impossible eof", strerror(errno));
|
||||
_Exit(6);
|
||||
} else if (rc == -1) {
|
||||
if (errno == EINTR) {
|
||||
errno = 0;
|
||||
continue;
|
||||
}
|
||||
LOG("error: read failed: %s", strerror(errno));
|
||||
continue;
|
||||
} else if (rc != 4) {
|
||||
LOG("error: read unexpected size of %ld: %s", rc, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
BLOCK_SIGNALS;
|
||||
|
||||
if ((ip = READ32BE(msg))) {
|
||||
if (IsMyIp(ip) || // nics
|
||||
(ip & 0xff000000) == 0x00000000 || // 0.0.0.0/8
|
||||
(ip & 0xff000000) == 0x7f000000) { // 127.0.0.0/8
|
||||
LOG("won't block %s", FormatIp(ip));
|
||||
} else if (InsertInt(&g_blocked, ip, true)) {
|
||||
BlockIp(ip);
|
||||
LOG("blocked %s", FormatIp(ip));
|
||||
} else {
|
||||
LOG("already blocked %s", FormatIp(ip));
|
||||
}
|
||||
}
|
||||
|
||||
ALLOW_SIGNALS;
|
||||
}
|
||||
|
||||
if (g_shutdown == SIGINT || //
|
||||
g_shutdown == SIGHUP) {
|
||||
Unlink(g_sockname);
|
||||
Unlink(g_pidname);
|
||||
}
|
||||
}
|
5
net/turfwar/blackholed.sh
Executable file
5
net/turfwar/blackholed.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/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
|
|
@ -90,7 +90,7 @@
|
|||
|
||||
#define PORT 8080 // default server listening port
|
||||
#define CPUS 64 // number of cpus to actually use
|
||||
#define WORKERS 100 // size of http client thread pool
|
||||
#define WORKERS 500 // size of http client thread pool
|
||||
#define SUPERVISE_MS 1000 // how often to stat() asset files
|
||||
#define KEEPALIVE_MS 60000 // max time to keep idle conn open
|
||||
#define MELTALIVE_MS 2000 // panic keepalive under heavy load
|
||||
|
@ -108,7 +108,7 @@
|
|||
#define BATCH_MAX 64 // max claims to insert per transaction
|
||||
#define NICK_MAX 40 // max length of user nickname string
|
||||
#define TB_INTERVAL 1000 // millis between token replenishes
|
||||
#define TB_CIDR 22 // token bucket cidr specificity
|
||||
#define TB_CIDR 24 // token bucket cidr specificity
|
||||
#define SOCK_MAX 100 // max length of socket queue
|
||||
#define MSG_BUF 512 // small response lookaside
|
||||
|
||||
|
@ -233,6 +233,14 @@ struct Asset {
|
|||
char lastmodified[32];
|
||||
};
|
||||
|
||||
struct Blackhole {
|
||||
struct sockaddr_un addr;
|
||||
int fd;
|
||||
} g_blackhole = {{
|
||||
AF_UNIX,
|
||||
"/var/run/blackhole.sock",
|
||||
}};
|
||||
|
||||
// cli flags
|
||||
bool g_integrity;
|
||||
bool g_daemonize;
|
||||
|
@ -248,6 +256,7 @@ atomic_int g_connections;
|
|||
nsync_note g_shutdown[3];
|
||||
|
||||
// whitebox metrics
|
||||
atomic_long g_banned;
|
||||
atomic_long g_accepts;
|
||||
atomic_long g_dbfails;
|
||||
atomic_long g_proxied;
|
||||
|
@ -401,6 +410,19 @@ int DbPrepare(sqlite3 *db, sqlite3_stmt **stmt, const char *sql) {
|
|||
return sqlite3_prepare_v2(db, sql, -1, stmt, 0);
|
||||
}
|
||||
|
||||
bool Blackhole(uint32_t ip) {
|
||||
char buf[4];
|
||||
WRITE32BE(buf, ip);
|
||||
if (sendto(g_blackhole.fd, buf, 4, 0, (struct sockaddr *)&g_blackhole.addr,
|
||||
sizeof(g_blackhole.addr)) == 4) {
|
||||
return true;
|
||||
} else {
|
||||
kprintf("error: sendto(/var/run/blackhole.sock) failed: %s\n",
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validates name registration validity
|
||||
bool IsValidNick(const char *s, size_t n) {
|
||||
size_t i;
|
||||
|
@ -674,10 +696,11 @@ void ServeStatusz(int client, char *outbuf) {
|
|||
g_messages / MAX(1, _timespec_sub(now, g_started).tv_sec));
|
||||
p = Statusz(p, "started", g_started.tv_sec);
|
||||
p = Statusz(p, "now", now.tv_sec);
|
||||
p = Statusz(p, "messages", g_messages);
|
||||
p = Statusz(p, "connections", g_connections);
|
||||
p = Statusz(p, "banned", g_banned);
|
||||
p = Statusz(p, "workers", g_workers);
|
||||
p = Statusz(p, "accepts", g_accepts);
|
||||
p = Statusz(p, "messages", g_messages);
|
||||
p = Statusz(p, "dbfails", g_dbfails);
|
||||
p = Statusz(p, "proxied", g_proxied);
|
||||
p = Statusz(p, "memfails", g_memfails);
|
||||
|
@ -759,8 +782,8 @@ void *ListenWorker(void *arg) {
|
|||
}
|
||||
if (!AddClient(&g_clients, &client, WaitFor(ACCEPT_DEADLINE_MS))) {
|
||||
++g_rejected;
|
||||
LOG("502 Accept Queue Full\n");
|
||||
Write(client.sock, "HTTP/1.1 502 Accept Queue Full\r\n"
|
||||
LOG("503 Accept Queue Full\n");
|
||||
Write(client.sock, "HTTP/1.1 503 Accept Queue Full\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"
|
||||
|
@ -834,6 +857,9 @@ void *HttpWorker(void *arg) {
|
|||
++g_messages;
|
||||
++g_worker[id].msgcount;
|
||||
|
||||
ipv6 = false;
|
||||
ip = clientip;
|
||||
|
||||
// get client address from frontend
|
||||
if (HasHeader(kHttpXForwardedFor)) {
|
||||
if (!IsLoopbackIp(clientip) && //
|
||||
|
@ -861,17 +887,21 @@ void *HttpWorker(void *arg) {
|
|||
ip = clientip;
|
||||
++g_unproxied;
|
||||
}
|
||||
|
||||
ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16,
|
||||
ip >> 8, ip);
|
||||
|
||||
if ((tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 64) {
|
||||
if (tok > 8) {
|
||||
if (!ipv6 && (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"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"
|
||||
"429 Too Many Requests\n");
|
||||
} else {
|
||||
Blackhole(ip);
|
||||
++g_banned;
|
||||
}
|
||||
++g_ratelimits;
|
||||
break;
|
||||
|
@ -1113,8 +1143,8 @@ void *HttpWorker(void *arg) {
|
|||
sent = write(client.sock, outbuf, p - outbuf);
|
||||
break;
|
||||
} else {
|
||||
LOG("%s: 502 Claims Queue Full\n", ipbuf);
|
||||
Write(client.sock, "HTTP/1.1 502 Claims Queue Full\r\n"
|
||||
LOG("%s: 503 Claims Queue Full\n", ipbuf);
|
||||
Write(client.sock, "HTTP/1.1 503 Claims Queue Full\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"
|
||||
|
@ -1793,6 +1823,15 @@ int main(int argc, char *argv[]) {
|
|||
CHECK_EQ(0, chdir("/opt/turfwar"));
|
||||
putenv("TMPDIR=/opt/turfwar/tmp");
|
||||
|
||||
if ((g_blackhole.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) {
|
||||
kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno));
|
||||
_Exit(3);
|
||||
}
|
||||
if (!Blackhole(0)) {
|
||||
kprintf("redbean isn't able to protect your kernel from level 4 ddos\n");
|
||||
kprintf("please run the blackholed program, see https://justine.lol/\n");
|
||||
}
|
||||
|
||||
// the power to serve
|
||||
if (g_daemonize) {
|
||||
if (fork() > 0) _Exit(0);
|
||||
|
|
|
@ -9,7 +9,8 @@ NET_TURFWAR_OBJS = \
|
|||
$(NET_TURFWAR_SRCS:%.c=o/$(MODE)/%.o)
|
||||
|
||||
NET_TURFWAR_COMS = \
|
||||
$(NET_TURFWAR_SRCS:%.c=o/$(MODE)/%.com)
|
||||
$(NET_TURFWAR_SRCS:%.c=o/$(MODE)/%.com) \
|
||||
o/$(MODE)/net/turfwar/turfbean.com
|
||||
|
||||
NET_TURFWAR_BINS = \
|
||||
$(NET_TURFWAR_COMS) \
|
||||
|
@ -33,6 +34,7 @@ NET_TURFWAR_DIRECTDEPS = \
|
|||
LIBC_X \
|
||||
NET_HTTP \
|
||||
THIRD_PARTY_GETOPT \
|
||||
THIRD_PARTY_MUSL \
|
||||
THIRD_PARTY_NSYNC \
|
||||
THIRD_PARTY_NSYNC_MEM \
|
||||
THIRD_PARTY_SQLITE3 \
|
||||
|
@ -53,6 +55,30 @@ o/$(MODE)/net/turfwar/%.com.dbg: \
|
|||
$(APE_NO_MODIFY_SELF)
|
||||
@$(APELINK)
|
||||
|
||||
o/$(MODE)/net/turfwar/turfbean.com.dbg: \
|
||||
$(TOOL_NET_DEPS) \
|
||||
o/$(MODE)/tool/net/redbean.o \
|
||||
$(TOOL_NET_REDBEAN_LUA_MODULES) \
|
||||
o/$(MODE)/tool/net/net.pkg \
|
||||
o/$(MODE)/net/turfwar/.init.lua.zip.o \
|
||||
o/$(MODE)/tool/net/redbean.png.zip.o \
|
||||
o/$(MODE)/tool/net/favicon.ico.zip.o \
|
||||
$(CRT) \
|
||||
$(APE_NO_MODIFY_SELF)
|
||||
@$(APELINK)
|
||||
|
||||
o/$(MODE)/net/turfwar/turfbean.com: \
|
||||
o/$(MODE)/net/turfwar/turfbean.com.dbg \
|
||||
o/$(MODE)/third_party/zip/zip.com \
|
||||
o/$(MODE)/tool/build/symtab.com
|
||||
@$(MAKE_OBJCOPY)
|
||||
@$(MAKE_SYMTAB_CREATE)
|
||||
@$(MAKE_SYMTAB_ZIP)
|
||||
|
||||
o/$(MODE)/net/turfwar/.init.lua.zip.o: private \
|
||||
ZIPOBJ_FLAGS += \
|
||||
-B
|
||||
|
||||
$(NET_TURFWAR_OBJS): \
|
||||
$(BUILD_FILES) \
|
||||
net/turfwar/turfwar.mk
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue