ddat interop with dat js
cable interop with cabal js
ddat's frontend = dat-thang
cable fe = cable-thang
dillo support based on cel's dillo-dat
bootstrap node
xplatform support

Signed-off-by: divo <418@cryspace.space>
This commit is contained in:
divorce committed 2026-10-19 00:17:02 -04:00
1 parent f0f91540be
commit b735900f2e
323 files changed
+134347 -343

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
CC ?= cc
AR ?= ar
RANLIB ?= ranlib
INSTALL ?= install
PREFIX ?= /usr/local
BUILD ?= build
CFLAGS ?= -O2 -std=c99 -Wall -Wextra -Wpedantic
CPPFLAGS ?=
NETWORK_PROTOCOL ?= $(shell tr -d '\r\n' < NETWORK_PROTOCOL)
SODIUM_CFLAGS ?= $(shell pkg-config --cflags libsodium 2>/dev/null)
SODIUM_LIBS ?= $(shell pkg-config --libs libsodium 2>/dev/null || echo -lsodium)
TARGET_TRIPLE = $(shell $(CC) -dumpmachine 2>/dev/null)
ifeq ($(findstring mingw,$(TARGET_TRIPLE)),mingw)
PLATFORM_LIBS = -lws2_32
endif
CPPFLAGS += -Iinclude -include cess_compat.h -DCESSPOOL_HAVE_SODIUM -DCESSPOOL_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\" $(SODIUM_CFLAGS)
OBJECTS = $(BUILD)/cess_net.o $(BUILD)/cesspool.o $(BUILD)/cess_identity.o
.PHONY: all clean install uninstall
all: $(BUILD)/libcess.a $(BUILD)/cesspool-bootstrap
$(BUILD):
mkdir -p $@
$(BUILD)/%.o: src/%.c include/cesspool.h include/cess_net.h | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
$(BUILD)/libcess.a: $(OBJECTS)
$(AR) rcs $@ $(OBJECTS)
$(RANLIB) $@
$(BUILD)/cesspool-bootstrap: tools/cesspool-bootstrap.c $(BUILD)/libcess.a
$(CC) $(CPPFLAGS) $(CFLAGS) $< $(BUILD)/libcess.a $(SODIUM_LIBS) $(PLATFORM_LIBS) -o $@
-include $(OBJECTS:.o=.d)
install: all
$(INSTALL) -d "$(DESTDIR)$(PREFIX)/lib" "$(DESTDIR)$(PREFIX)/include/libcess" "$(DESTDIR)$(PREFIX)/bin" "$(DESTDIR)$(PREFIX)/share/man/man1"
$(INSTALL) -m 644 $(BUILD)/libcess.a "$(DESTDIR)$(PREFIX)/lib/libcess.a"
$(INSTALL) -m 644 include/cesspool.h include/cess_net.h include/cess_compat.h "$(DESTDIR)$(PREFIX)/include/libcess/"
$(INSTALL) -m 755 $(BUILD)/cesspool-bootstrap "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap"
$(INSTALL) -m 644 docs/cesspool-bootstrap.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1"
uninstall:
rm -f "$(DESTDIR)$(PREFIX)/lib/libcess.a" "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap" "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1" "$(DESTDIR)$(PREFIX)/include/libcess/cesspool.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_net.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_compat.h"
clean:
rm -rf "$(BUILD)"
+1
View File
@@ -0,0 +1 @@
cesspool
+32
View File
@@ -0,0 +1,32 @@
# libcess
`libcess` is the Cesspool networking library used by the Dat (`ddat`) and
Cabal-compatible (`cable`) applications. It owns the UDP transport, peer
discovery/rendezvous protocol, bootstrap client/server behavior, and the
persistent Ed25519 identity used by the Cesspool network.
Dat and Cable are applications built on this base. Archive storage remains
SLEEP; room/feed behavior remains in Cable. libcess does not depend on either
application or their storage layers.
## Build
Requires a C99 compiler, GNU make, and libsodium development files.
```sh
make
make install
```
Build artifacts stay under `libcess/build`. Set `PREFIX` or `DESTDIR` for a
staged install. The standalone package reads `libcess/NETWORK_PROTOCOL`;
the full dat-gpt build uses the project-root `NETWORK_PROTOCOL` file so Dat
and Cable share the same Cesspool network identity.
The bootstrap service is `cesspool-bootstrap`. It only provides discovery;
it does not relay chat or archive payloads. Supply `--advertise HOST:PORT`
for the address clients should use, and make that UDP port reachable through
the host firewall and edge router. No external address lookup is performed.
See [the public API](include/cesspool.h), [network API](include/cess_net.h),
and [bootstrap man page](docs/cesspool-bootstrap.1).
+1
View File
@@ -0,0 +1 @@
#include "include/cess_net.h"
+1
View File
@@ -0,0 +1 @@
#include "include/cesspool.h"
+28
View File
@@ -0,0 +1,28 @@
.TH CESSPOOL-BOOTSTRAP 1
.SH NAME
cesspool-bootstrap \- run a Cesspool discovery node
.SH SYNOPSIS
.B cesspool-bootstrap
[ B--bind R IPv4] [ B--port R UDP-PORT] [ B--advertise R HOST:PORT] [ B--state-dir R DIR]
.SH DESCRIPTION
Runs the Cesspool UDP bootstrap and rendezvous service in the foreground.
It helps clients discover peers and does not relay chat or archive payloads.
The persistent Ed25519 node identity is stored in the state directory.
.PP
The listening UDP port must be reachable from clients. Configure firewall and
router forwarding as needed. No public-IP lookup is performed; use
.B --advertise
to print the endpoint that clients should configure.
.SH OPTIONS
.TP
.B --bind IPv4
Local IPv4 address to bind; defaults to 0.0.0.0.
.TP
.B --port UDP-PORT
UDP port to listen on; defaults to 3282.
.TP
.B --advertise HOST:PORT
Document the externally reachable endpoint for client configuration.
.TP
.B --state-dir DIR
Persistent identity directory.
+19
View File
@@ -0,0 +1,19 @@
#ifndef LIBCESS_COMPAT_H
#define LIBCESS_COMPAT_H
/* Small operating-system spelling adjustments needed by the portable
* Cesspool sources. This header has no dependency on Dat or SLEEP. */
#ifdef _WIN32
#include <direct.h>
#include <errno.h>
#include <io.h>
#include <sys/stat.h>
#ifndef mkdir
#define mkdir(path, mode) _mkdir(path)
#endif
#ifndef EKEYREJECTED
#define EKEYREJECTED EACCES
#endif
#endif
#endif
+50
View File
@@ -0,0 +1,50 @@
#ifndef LIBCESS_NET_H
#define LIBCESS_NET_H
#include <stddef.h>
#include <stdint.h>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
typedef SOCKET cess_socket_t;
#define CESS_INVALID_SOCKET INVALID_SOCKET
#else
#include <sys/socket.h>
typedef int cess_socket_t;
#define CESS_INVALID_SOCKET (-1)
#endif
#define CESSNET_PORT 3282
#define CESSNET_MAX_PACKET 1400
#define CESSNET_TOPIC_BYTES 32
#define CESSNET_PEERID_BYTES 32
typedef struct cess_peer {
cess_socket_t fd;
struct sockaddr_storage addr;
socklen_t addrlen;
uint8_t id[CESSNET_PEERID_BYTES];
} cess_peer_t;
typedef struct cessnet {
cess_socket_t sock;
uint8_t topic[CESSNET_TOPIC_BYTES];
uint8_t peerid[CESSNET_PEERID_BYTES];
void *context;
void (*on_message)(struct cessnet *net, cess_peer_t *peer,
const uint8_t *msg, size_t len);
} cessnet_t;
int cessnet_init(cessnet_t *net, const uint8_t topic[CESSNET_TOPIC_BYTES],
const uint8_t peerid[CESSNET_PEERID_BYTES]);
int cessnet_bind(cessnet_t *net, const char *host, uint16_t port);
int cessnet_poll(cessnet_t *net, int timeout_ms);
int cessnet_send(cessnet_t *net, const cess_peer_t *peer,
const uint8_t *message, size_t length);
int cessnet_close(cessnet_t *net);
#endif
+129
View File
@@ -0,0 +1,129 @@
#ifndef LIBCESS_CESSPOOL_H
#define LIBCESS_CESSPOOL_H
#include <stddef.h>
#include <stdint.h>
#include "cess_net.h"
#define CESSPOOL_TOPIC_BYTES 32u
#define CESSPOOL_PEER_BYTES 32u
#define CESSPOOL_MAX_BOOTSTRAP 16u
#define CESSPOOL_MAX_PEERS 256u
#define CESSPOOL_MAX_TOPICS 64u
#define CESSPOOL_MAX_ANNOUNCEMENTS 1024u
#define CESSPOOL_MAX_TRANSACTIONS 64u
typedef struct cesspool_keypair {
uint8_t pubkey[32];
uint8_t seckey[64];
} cesspool_keypair_t;
enum cesspool_message_type {
CESSPOOL_LOOKUP = 0, CESSPOOL_ANNOUNCE = 1, CESSPOOL_UNANNOUNCE = 2,
CESSPOOL_REPLY = 3, CESSPOOL_PING = 4, CESSPOOL_PONG = 5,
CESSPOOL_HOLEPUNCH = 6
};
typedef struct cesspool_message {
uint8_t type, topic[CESSPOOL_TOPIC_BYTES], peer_id[CESSPOOL_PEER_BYTES];
uint16_t port;
uint8_t payload[2048];
size_t payload_len;
} cesspool_message_t;
typedef struct cesspool_request {
uint32_t type;
uint16_t port;
uint8_t local_address[6];
size_t local_address_len;
} cesspool_request_t;
typedef struct cesspool_response {
uint8_t peers[1024]; size_t peers_len;
uint8_t local_peers[1024]; size_t local_peers_len;
uint8_t peer_info[2048]; size_t peer_info_len;
} cesspool_response_t;
typedef struct cesspool_peer {
cess_peer_t address;
uint8_t public_key[32];
uint8_t discovery_topic[CESSPOOL_TOPIC_BYTES];
uint64_t last_seen;
unsigned failures;
int local, discovered;
} cesspool_peer_t;
typedef struct cesspool_topic {
uint8_t key[32]; uint16_t port; int announcing, looking_up; uint64_t refresh_at;
} cesspool_topic_t;
typedef struct cesspool_transaction {
uint16_t id; uint8_t target[32], type; uint64_t expires_at; int active;
} cesspool_transaction_t;
typedef struct cesspool_announcement {
uint8_t topic[CESSPOOL_TOPIC_BYTES], peer_id[CESSPOOL_PEER_BYTES];
cess_peer_t address;
uint64_t last_seen;
} cesspool_announcement_t;
struct cesspool;
typedef void (*cesspool_transport_packet_fn)(struct cesspool *,
const cess_peer_t *,
const uint8_t *, size_t);
typedef struct cesspool {
cessnet_t net;
cess_peer_t bootstrap_nodes[CESSPOOL_MAX_BOOTSTRAP];
size_t bootstrap_count;
uint16_t port;
int ready, bootstrap_server;
cesspool_peer_t peers[CESSPOOL_MAX_PEERS]; size_t peer_count;
cesspool_topic_t topics[CESSPOOL_MAX_TOPICS]; size_t topic_count;
cesspool_announcement_t announcements[CESSPOOL_MAX_ANNOUNCEMENTS];
size_t announcement_count;
uint64_t bootstrap_last_seen, bootstrap_probe_at;
int bootstrap_reachable;
cesspool_transaction_t transactions[CESSPOOL_MAX_TRANSACTIONS];
uint16_t next_transaction;
void (*on_peer)(struct cesspool *, const cesspool_peer_t *);
void (*on_holepunch)(struct cesspool *, const cess_peer_t *);
cesspool_transport_packet_fn on_transport_packet;
void *transport_context;
} cesspool_t;
int cesspool_identity_init(void);
int cesspool_identity_generate(cesspool_keypair_t *identity);
int cesspool_identity_save(const char *path, const cesspool_keypair_t *identity);
int cesspool_identity_load(const char *path, cesspool_keypair_t *identity);
int cesspool_discovery_key(const uint8_t public_key[32], uint8_t topic[32]);
int cesspool_encode(const cesspool_message_t *, uint8_t *, size_t, size_t *);
int cesspool_decode(cesspool_message_t *, const uint8_t *, size_t);
int cesspool_request_encode(const cesspool_request_t *, uint8_t *, size_t, size_t *);
int cesspool_request_decode(cesspool_request_t *, const uint8_t *, size_t);
int cesspool_response_encode(const cesspool_response_t *, uint8_t *, size_t, size_t *);
int cesspool_response_decode(cesspool_response_t *, const uint8_t *, size_t);
int cesspool_process_response(cesspool_t *, const cesspool_response_t *, const uint8_t[2]);
int cesspool_init(cesspool_t *, const uint8_t peer_id[32]);
int cesspool_add_bootstrap(cesspool_t *, const char *, uint16_t);
int cesspool_configure_bootstraps(cesspool_t *);
int cesspool_bootstrap_config_add(const char *);
int cesspool_bootstrap_config_set(const char *);
int cesspool_bootstrap_config_get(char *, size_t);
int cesspool_bootstrap_config_list(char *, size_t);
int cesspool_bootstrap(cesspool_t *);
int cesspool_listen(cesspool_t *, const char *, uint16_t);
int cesspool_set_bootstrap_server(cesspool_t *, int);
int cesspool_announce(cesspool_t *, const uint8_t[32], uint16_t);
int cesspool_lookup(cesspool_t *, const uint8_t[32]);
int cesspool_unannounce(cesspool_t *, const uint8_t[32], uint16_t);
int cesspool_ping(cesspool_t *, const cess_peer_t *);
int cesspool_holepunch(cesspool_t *, const cess_peer_t *);
int cesspool_poll(cesspool_t *, int);
int cesspool_add_peer(cesspool_t *, const cesspool_peer_t *);
int cesspool_remove_peer(cesspool_t *, const uint8_t[32]);
cesspool_peer_t *cesspool_find_peer(cesspool_t *, const uint8_t[32]);
int cesspool_join(cesspool_t *, const uint8_t[32], uint16_t, int, int);
int cesspool_leave(cesspool_t *, const uint8_t[32]);
int cesspool_tick(cesspool_t *, uint64_t);
size_t cesspool_closest(cesspool_t *, const uint8_t[32], cesspool_peer_t **, size_t);
size_t cesspool_topic_peers(cesspool_t *, const uint8_t[CESSPOOL_TOPIC_BYTES], cesspool_peer_t **, size_t);
size_t cesspool_announced_peer_count(const cesspool_t *);
size_t cesspool_announced_topic_count(const cesspool_t *);
int cesspool_transaction_begin(cesspool_t *, uint8_t, const uint8_t[32], uint64_t, uint16_t *);
int cesspool_transaction_finish(cesspool_t *, uint16_t);
int cesspool_destroy(cesspool_t *);
#endif
+75
View File
@@ -0,0 +1,75 @@
#define _POSIX_C_SOURCE 200809L
#include "cesspool.h"
#include "cess_compat.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sodium.h>
#ifdef _WIN32
#include <io.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
int cesspool_identity_init(void)
{
return sodium_init() < 0 ? -1 : 0;
}
int cesspool_identity_generate(cesspool_keypair_t *identity)
{
if (!identity) { errno = EINVAL; return -1; }
if (crypto_sign_keypair(identity->pubkey, identity->seckey) != 0) {
errno = EIO;
return -1;
}
return 0;
}
int cesspool_identity_save(const char *path, const cesspool_keypair_t *identity)
{
FILE *file;
#ifndef _WIN32
int fd;
#endif
size_t written;
if (!path || !identity) { errno = EINVAL; return -1; }
file = fopen(path, "wb");
if (!file) return -1;
#ifdef _WIN32
if (_chmod(path, _S_IREAD | _S_IWRITE) != 0) { fclose(file); return -1; }
#else
fd = fileno(file);
if (fchmod(fd, S_IRUSR | S_IWUSR) != 0) { fclose(file); return -1; }
#endif
written = fwrite(identity, 1, sizeof(*identity), file);
if (fclose(file) != 0 || written != sizeof(*identity)) {
errno = EIO;
return -1;
}
return 0;
}
int cesspool_identity_load(const char *path, cesspool_keypair_t *identity)
{
FILE *file;
size_t count;
int extra;
if (!path || !identity) { errno = EINVAL; return -1; }
file = fopen(path, "rb");
if (!file) return -1;
count = fread(identity, 1, sizeof(*identity), file);
extra = fgetc(file);
if (fclose(file) != 0 || count != sizeof(*identity) || extra != EOF) {
sodium_memzero(identity, sizeof(*identity));
errno = EINVAL;
return -1;
}
#ifdef _WIN32
if (_chmod(path, _S_IREAD | _S_IWRITE) != 0) return -1;
#else
if (chmod(path, S_IRUSR | S_IWUSR) != 0) return -1;
#endif
return 0;
}
+167
View File
@@ -0,0 +1,167 @@
#define _POSIX_C_SOURCE 200112L
#include "cess_compat.h"
#include "cess_net.h"
#include <errno.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#ifndef SIO_UDP_CONNRESET
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
#endif
static int cessnet_winsock_ready;
#else
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#endif
#ifdef _WIN32
static int cessnet_winsock_errno(int error)
{
switch (error) {
case WSAEINTR: return EINTR;
case WSAEACCES: return EACCES;
case WSAEADDRINUSE: return EADDRINUSE;
case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL;
case WSAENETDOWN: return ENETDOWN;
case WSAENETUNREACH: return ENETUNREACH;
case WSAEHOSTUNREACH: return EHOSTUNREACH;
case WSAECONNRESET: return ECONNRESET;
case WSAECONNREFUSED: return ECONNREFUSED;
case WSAETIMEDOUT: return ETIMEDOUT;
case WSAEMSGSIZE: return EMSGSIZE;
case WSAENOBUFS: return ENOBUFS;
case WSAEWOULDBLOCK: return EAGAIN;
default: return EIO;
}
}
#endif
int cessnet_init(cessnet_t *net, const uint8_t topic[32], const uint8_t peerid[32]) {
if (!net || !topic || !peerid) { errno = EINVAL; return -1; }
#ifdef _WIN32
if (!cessnet_winsock_ready) { WSADATA data; if (WSAStartup(MAKEWORD(2,2), &data)) { errno = ENETDOWN; return -1; } cessnet_winsock_ready = 1; }
#endif
memset(net, 0, sizeof(*net)); net->sock = CESS_INVALID_SOCKET; memcpy(net->topic, topic, 32); memcpy(net->peerid, peerid, 32); return 0;
}
int cessnet_bind(cessnet_t *net, const char *host, uint16_t port) {
struct sockaddr_in address; int broadcast = 1;
if (!net) { errno = EINVAL; return -1; }
memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port ? port : CESSNET_PORT);
if (!host || !*host || !strcmp(host, "*")) address.sin_addr.s_addr = htonl(INADDR_ANY);
else {
struct addrinfo hints, *resolved = NULL;
memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM;
if (getaddrinfo(host, NULL, &hints, &resolved) != 0 || !resolved) {
errno = EADDRNOTAVAIL; return -1;
}
address.sin_addr = ((struct sockaddr_in *)resolved->ai_addr)->sin_addr;
freeaddrinfo(resolved);
}
net->sock = socket(AF_INET, SOCK_DGRAM, 0);
if (net->sock == CESS_INVALID_SOCKET) {
#ifdef _WIN32
errno = ENETDOWN;
#endif
return -1;
}
#ifdef _WIN32
if (setsockopt(net->sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast))) {
int error = WSAGetLastError();
closesocket(net->sock); net->sock = CESS_INVALID_SOCKET;
errno = cessnet_winsock_errno(error);
return -1;
}
/* A peer that is not listening can send an ICMP port-unreachable response.
* Winsock reports that asynchronously as WSAECONNRESET on a UDP socket;
* that is a peer/network condition, not a dead local socket. */
{
BOOL disable_udp_connreset = FALSE;
DWORD returned = 0;
(void)WSAIoctl(net->sock, SIO_UDP_CONNRESET, &disable_udp_connreset,
sizeof(disable_udp_connreset), NULL, 0, &returned, NULL, NULL);
}
if (bind(net->sock, (struct sockaddr *)&address, sizeof(address))) {
int error = WSAGetLastError();
closesocket(net->sock); net->sock = CESS_INVALID_SOCKET;
errno = error == WSAEADDRINUSE ? EADDRINUSE :
error == WSAEACCES ? EACCES : EIO;
return -1;
}
#else
if (setsockopt(net->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast))) {
int saved = errno;
close(net->sock); net->sock = CESS_INVALID_SOCKET; errno = saved;
return -1;
}
if (bind(net->sock, (struct sockaddr *)&address, sizeof(address))) { close(net->sock); net->sock = CESS_INVALID_SOCKET; return -1; }
#endif
return 0;
}
int cessnet_poll(cessnet_t *net, int timeout_ms) {
uint8_t packet[CESSNET_MAX_PACKET], peer_id[CESSNET_PEERID_BYTES]; struct sockaddr_storage source; socklen_t source_len = sizeof(source); ssize_t n; cess_peer_t peer;
if (!net || net->sock == CESS_INVALID_SOCKET) { errno = EINVAL; return -1; }
memcpy(peer_id, net->peerid, sizeof(peer_id));
#ifdef _WIN32
{ WSAPOLLFD pfd; int ready; pfd.fd = net->sock; pfd.events = POLLRDNORM; pfd.revents = 0;
ready = WSAPoll(&pfd, 1, timeout_ms);
if (ready == SOCKET_ERROR) { errno = cessnet_winsock_errno(WSAGetLastError()); return -1; }
if (ready == 0) return 0;
if (pfd.revents & (POLLHUP | POLLNVAL)) { errno = EIO; return -1; }
if (pfd.revents & POLLERR) {
int socket_error = 0, error_length = sizeof(socket_error);
if (getsockopt(net->sock, SOL_SOCKET, SO_ERROR, (char *)&socket_error,
&error_length) == SOCKET_ERROR) {
errno = cessnet_winsock_errno(WSAGetLastError());
return -1;
}
if (socket_error == WSAECONNRESET || socket_error == WSAECONNREFUSED ||
socket_error == WSAEHOSTUNREACH || socket_error == WSAENETUNREACH)
return 0;
if (socket_error) { errno = cessnet_winsock_errno(socket_error); return -1; }
}
if (!(pfd.revents & POLLRDNORM)) return 0; }
#else
{ struct pollfd pfd; int ready; pfd.fd = net->sock; pfd.events = POLLIN; pfd.revents = 0;
ready = poll(&pfd, 1, timeout_ms);
if (ready < 0) return -1;
if (ready == 0) return 0;
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { errno = EIO; return -1; }
if (!(pfd.revents & POLLIN)) return 0; }
#endif
#ifdef _WIN32
{ int source_length = (int)source_len; n = recvfrom(net->sock, (char *)packet, sizeof(packet), 0, (struct sockaddr *)&source, &source_length); source_len = (socklen_t)source_length;
if (n < 0) { errno = cessnet_winsock_errno(WSAGetLastError()); return -1; } }
#else
n = recvfrom(net->sock, packet, sizeof(packet), 0, (struct sockaddr *)&source, &source_len); if (n < 0) return -1;
#endif
if (n < 0) return -1;
memset(&peer, 0, sizeof(peer)); peer.fd = net->sock; peer.addr = source; peer.addrlen = source_len;
/* Cesspool decodes discovery records here; other datagrams are passed to
* its authenticated peer-transport hook. */
if (net->on_message) net->on_message(net, &peer, packet, (size_t)n);
/* The identity bound to this UDP socket is immutable. Keep it intact even
* if a protocol callback uses an aliased view while handling the packet. */
memcpy(net->peerid, peer_id, sizeof(peer_id));
return 1;
}
int cessnet_send(cessnet_t *net, const cess_peer_t *peer, const uint8_t *message, size_t length) {
ssize_t n; if (!net || !peer || net->sock == CESS_INVALID_SOCKET || (!message && length) || length > CESSNET_MAX_PACKET) { errno = EINVAL; return -1; }
#ifdef _WIN32
n = sendto(net->sock, (const char *)message, (int)length, 0, (const struct sockaddr *)&peer->addr, (int)peer->addrlen);
if (n == SOCKET_ERROR) { errno = cessnet_winsock_errno(WSAGetLastError()); return -1; }
#else
n = sendto(net->sock, message, length, 0, (const struct sockaddr *)&peer->addr, peer->addrlen);
if (n < 0) return -1;
#endif
return n == (ssize_t)length ? 0 : -1;
}
int cessnet_close(cessnet_t *net) { int rc = 0; if (!net) { errno = EINVAL; return -1; } if (net->sock != CESS_INVALID_SOCKET) {
#ifdef _WIN32
rc = closesocket(net->sock);
#else
rc = close(net->sock);
#endif
} net->sock = CESS_INVALID_SOCKET; return rc; }
+793
View File
@@ -0,0 +1,793 @@
#define _POSIX_C_SOURCE 200112L
#include "cess_compat.h"
#include "cesspool.h"
#include <errno.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#ifdef CESSPOOL_HAVE_SODIUM
#include <sodium.h>
#endif
#define CESSPOOL_PACKET_BYTES 72u
#ifndef CESSPOOL_CONFIG_DIRECTORY
#define CESSPOOL_CONFIG_DIRECTORY "libcess"
#endif
#define CESSPOOL_MAX_RENDEZVOUS_PEERS 34u
#define CESSPOOL_PEER_TTL_MS 600000u
#define CESSPOOL_BOOTSTRAP_PROBE_MS 5000u
#define CESSPOOL_BOOTSTRAP_TIMEOUT_MS 15000u
/* Cesspool owns its compact protobuf-style varint framing; it has no SLEEP
* dependency even though Dat also uses the same wire primitive. */
static int cess_varint_encode(uint64_t value, uint8_t *out, size_t capacity,
size_t *written)
{
size_t n = 0;
if (!out) { errno = EINVAL; return -1; }
do {
if (n >= capacity) { errno = ENOSPC; return -1; }
out[n] = (uint8_t)(value & 0x7fu);
value >>= 7;
if (value) out[n] |= 0x80u;
n++;
} while (value);
if (written) *written = n;
return 0;
}
static int cess_varint_decode(const uint8_t *in, size_t length,
uint64_t *value, size_t *used)
{
uint64_t result = 0;
size_t i;
if (!in || !value) { errno = EINVAL; return -1; }
for (i = 0; i < length && i < 10; i++) {
uint8_t byte = in[i];
if (i == 9 && byte > 1) { errno = EOVERFLOW; return -1; }
result |= (uint64_t)(byte & 0x7fu) << (i * 7);
if (!(byte & 0x80u)) {
*value = result;
if (used) *used = i + 1;
return 0;
}
}
errno = length < 10 ? EAGAIN : EOVERFLOW;
return -1;
}
static uint64_t monotonic_ms(void)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0;
return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u;
}
static int pb_var(uint8_t tag,uint64_t value,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(cess_varint_encode(value,out+*off,cap-*off,&n))return-1;*off+=n;return 0;}
static int pb_bytes(uint8_t tag,const uint8_t*data,size_t len,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(cess_varint_encode(len,out+*off,cap-*off,&n))return-1;*off+=n;if(len>cap-*off){errno=ENOSPC;return-1;}memcpy(out+*off,data,len);*off+=len;return 0;}
int cesspool_request_encode(const cesspool_request_t*r,uint8_t*out,size_t cap,size_t*written){size_t off=0;if(!r||!out){errno=EINVAL;return-1;}if(pb_var(0x08,r->type,out,cap,&off))return-1;if(r->port&&pb_var(0x10,r->port,out,cap,&off))return-1;if(r->local_address_len&&pb_bytes(0x1a,r->local_address,r->local_address_len,out,cap,&off))return-1;if(written)*written=off;return 0;}
int cesspool_request_decode(cesspool_request_t*r,const uint8_t*in,size_t len){size_t off=0,n;uint64_t v;if(!r||!in){errno=EINVAL;return-1;}memset(r,0,sizeof(*r));while(off<len){uint8_t tag=in[off++];if(tag==0x08||tag==0x10){if(cess_varint_decode(in+off,len-off,&v,&n))return-1;off+=n;if(tag==0x08)r->type=(uint32_t)v;else r->port=(uint16_t)v;}else if(tag==0x1a){if(cess_varint_decode(in+off,len-off,&v,&n))return-1;off+=n;if(v>sizeof(r->local_address)||v>len-off){errno=EPROTO;return-1;}memcpy(r->local_address,in+off,(size_t)v);r->local_address_len=(size_t)v;off+=(size_t)v;}else{errno=EPROTO;return-1;}}return 0;}
int cesspool_response_encode(const cesspool_response_t *r, uint8_t *out,
size_t cap, size_t *written)
{
size_t off = 0;
if (!r || !out) { errno = EINVAL; return -1; }
if (r->peers_len && pb_bytes(0x0a, r->peers, r->peers_len, out, cap, &off)) return -1;
if (r->local_peers_len && pb_bytes(0x12, r->local_peers, r->local_peers_len, out, cap, &off)) return -1;
if (r->peer_info_len && pb_bytes(0x1a, r->peer_info, r->peer_info_len, out, cap, &off)) return -1;
if (written) *written = off;
return 0;
}
int cesspool_response_decode(cesspool_response_t *r, const uint8_t *in, size_t len)
{
size_t off = 0, n;
uint64_t v;
if (!r || !in) { errno = EINVAL; return -1; }
memset(r, 0, sizeof(*r));
while (off < len) {
uint8_t tag = in[off++], *dst;
size_t *dstlen, capacity;
if (cess_varint_decode(in + off, len - off, &v, &n)) return -1;
off += n;
if (v > len - off) { errno = EPROTO; return -1; }
if (tag == 0x0a) { dst = r->peers; dstlen = &r->peers_len; capacity = sizeof(r->peers); }
else if (tag == 0x12) { dst = r->local_peers; dstlen = &r->local_peers_len; capacity = sizeof(r->local_peers); }
else if (tag == 0x1a) { dst = r->peer_info; dstlen = &r->peer_info_len; capacity = sizeof(r->peer_info); }
else { off += (size_t)v; continue; }
if (v > capacity) { errno = EOVERFLOW; return -1; }
memcpy(dst, in + off, (size_t)v);
*dstlen = (size_t)v;
off += (size_t)v;
}
return 0;
}
int cesspool_process_response(cesspool_t*p,const cesspool_response_t*r,const uint8_t prefix[2]){size_t off;cesspool_peer_t peer;struct sockaddr_in*address;if(!p||!r){errno=EINVAL;return-1;}if(r->peers_len%6||r->local_peers_len%4){errno=EPROTO;return-1;}for(off=0;off<r->peers_len;off+=6){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;memcpy(&address->sin_addr,r->peers+off,4);memcpy(&address->sin_port,r->peers+off+4,2);peer.address.addrlen=sizeof(*address);memcpy(peer.public_key,r->peers+off,6);cesspool_add_peer(p,&peer);}if(prefix)for(off=0;off<r->local_peers_len;off+=4){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;((uint8_t*)&address->sin_addr)[0]=prefix[0];((uint8_t*)&address->sin_addr)[1]=prefix[1];((uint8_t*)&address->sin_addr)[2]=r->local_peers[off];((uint8_t*)&address->sin_addr)[3]=r->local_peers[off+1];memcpy(&address->sin_port,r->local_peers+off+2,2);peer.address.addrlen=sizeof(*address);peer.local=1;memcpy(peer.public_key,&address->sin_addr,4);memcpy(peer.public_key+4,&address->sin_port,2);cesspool_add_peer(p,&peer);}return 0;}
int cesspool_discovery_key(const uint8_t key[32], uint8_t topic[32])
{
if (!key || !topic) { errno = EINVAL; return -1; }
#ifdef CESSPOOL_HAVE_SODIUM
/* The root NETWORK_PROTOCOL token selects the discovery namespace.
* With "hypercore", this matches legacy Dat's discoveryKey derivation. */
return crypto_generichash(topic, 32,
(const unsigned char *)CESSPOOL_NETWORK_PROTOCOL,
strlen(CESSPOOL_NETWORK_PROTOCOL), key, 32);
#else
memcpy(topic, key, 32);
return 0;
#endif
}
int cesspool_encode(const cesspool_message_t *m, uint8_t *out,
size_t capacity, size_t *written)
{
size_t total = CESSPOOL_PACKET_BYTES;
if (!m || !out) { errno = EINVAL; return -1; }
if (m->type > CESSPOOL_HOLEPUNCH) { errno = EINVAL; return -1; }
if (m->type == CESSPOOL_REPLY) {
if (capacity < CESSPOOL_PACKET_BYTES + 2 ||
m->payload_len > UINT16_MAX ||
m->payload_len > capacity - CESSPOOL_PACKET_BYTES - 2) {
errno = ENOSPC; return -1;
}
total += 2 + m->payload_len;
}
if (capacity < total) { errno = ENOSPC; return -1; }
memcpy(out, "CSP0", 4);
out[4] = m->type;
out[5] = m->type == CESSPOOL_REPLY ? 1 : 0;
out[6] = (uint8_t)(m->port >> 8);
out[7] = (uint8_t)m->port;
memcpy(out + 8, m->topic, 32);
memcpy(out + 40, m->peer_id, 32);
if (m->type == CESSPOOL_REPLY) {
out[72] = (uint8_t)(m->payload_len >> 8);
out[73] = (uint8_t)m->payload_len;
memcpy(out + 74, m->payload, m->payload_len);
}
if (written) *written = total;
return 0;
}
int cesspool_decode(cesspool_message_t *m, const uint8_t *in, size_t len)
{
if (!m || !in) { errno = EINVAL; return -1; }
if (len < CESSPOOL_PACKET_BYTES || memcmp(in, "CSP0", 4) ||
in[4] > CESSPOOL_HOLEPUNCH) {
errno = EPROTO;
return -1;
}
memset(m, 0, sizeof(*m));
m->type = in[4];
m->port = ((uint16_t)in[6] << 8) | in[7];
memcpy(m->topic, in + 8, 32);
memcpy(m->peer_id, in + 40, 32);
if (m->type == CESSPOOL_REPLY) {
if (in[5] != 1 || len < CESSPOOL_PACKET_BYTES + 2) { errno = EPROTO; return -1; }
m->payload_len = ((size_t)in[72] << 8) | in[73];
if (m->payload_len > sizeof(m->payload) || len != CESSPOOL_PACKET_BYTES + 2 + m->payload_len) { errno = EPROTO; return -1; }
memcpy(m->payload, in + 74, m->payload_len);
} else if (in[5] != 0 || len != CESSPOOL_PACKET_BYTES) { errno = EPROTO; return -1; }
return 0;
}
static int send_message(cesspool_t *pool,const cess_peer_t *peer,uint8_t type,const uint8_t topic[32],uint16_t port)
{
cesspool_message_t message;uint8_t packet[CESSNET_MAX_PACKET];size_t len;
memset(&message,0,sizeof(message));message.type=type;message.port=port;if(topic)memcpy(message.topic,topic,32);memcpy(message.peer_id,pool->net.peerid,32);
if(cesspool_encode(&message,packet,sizeof(packet),&len))return-1;
return cessnet_send(&pool->net,peer,packet,len);
}
static int send_reply(cesspool_t *pool, const cess_peer_t *peer,
const uint8_t topic[32], const cesspool_response_t *response)
{
cesspool_message_t message;
uint8_t packet[CESSNET_MAX_PACKET];
size_t payload_len, packet_len;
memset(&message, 0, sizeof(message));
message.type = CESSPOOL_REPLY;
memcpy(message.topic, topic, CESSPOOL_TOPIC_BYTES);
memcpy(message.peer_id, pool->net.peerid, CESSPOOL_PEER_BYTES);
if (cesspool_response_encode(response, message.payload,
sizeof(message.payload), &payload_len)) return -1;
message.payload_len = payload_len;
if (cesspool_encode(&message, packet, sizeof(packet), &packet_len)) return -1;
return cessnet_send(&pool->net, peer, packet, packet_len);
}
static int send_bootstrap(cesspool_t *pool,uint8_t type,const uint8_t topic[32],uint16_t port)
{
static const uint16_t lan_ports[] = {CESSNET_PORT,3000,3002,3004,2001,2003,2005};
size_t i;int sent=0,lan_sent=0;
if (!pool) { errno = EINVAL; return -1; }
for(i=0;i<pool->bootstrap_count;i++)
if(send_message(pool,&pool->bootstrap_nodes[i],type,topic,port)==0)sent++;
/* Always keep zero-configuration LAN discovery active, even when a
* rendezvous node is configured. Routers do not forward these broadcasts;
* this complements Internet discovery and lets same-LAN peers connect
* without requiring them to share bootstrap configuration. */
for (i = 0; i < sizeof(lan_ports) / sizeof(lan_ports[0]); i++) {
cess_peer_t target;
struct sockaddr_in *address;
memset(&target, 0, sizeof(target));
address = (struct sockaddr_in *)&target.addr;
address->sin_family = AF_INET;
address->sin_addr.s_addr = htonl(INADDR_BROADCAST);
address->sin_port = htons(lan_ports[i]);
target.addrlen = sizeof(*address);
if (send_message(pool, &target, type, topic, port) == 0) lan_sent++;
}
if (pool->bootstrap_count) return (sent || lan_sent) ? 0 : -1;
return 0; /* LAN broadcasts are best-effort, never a join failure. */
}
static int has_topic(const cesspool_t *pool,const uint8_t topic[32],int announcing)
{
size_t i;
for(i=0;i<pool->topic_count;i++)if(!memcmp(pool->topics[i].key,topic,32)&&(!announcing||pool->topics[i].announcing))return 1;
return 0;
}
static int same_peer_address(const cess_peer_t *a, const cess_peer_t *b)
{
const struct sockaddr_in *aa, *bb;
if (!a || !b || a->addrlen < (socklen_t)sizeof(struct sockaddr_in) ||
b->addrlen < (socklen_t)sizeof(struct sockaddr_in)) return 0;
aa = (const struct sockaddr_in *)&a->addr;
bb = (const struct sockaddr_in *)&b->addr;
return aa->sin_family == AF_INET && bb->sin_family == AF_INET &&
aa->sin_addr.s_addr == bb->sin_addr.s_addr && aa->sin_port == bb->sin_port;
}
static int announcement_add(cesspool_t *pool, const uint8_t topic[32],
const uint8_t peer_id[32], const cess_peer_t *address,
uint64_t now)
{
size_t i;
for (i = 0; i < pool->announcement_count; i++) {
cesspool_announcement_t *entry = &pool->announcements[i];
if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) {
entry->address = *address;
entry->last_seen = now;
return 0;
}
}
if (pool->announcement_count >= CESSPOOL_MAX_ANNOUNCEMENTS) {
errno = ENOSPC;
return -1;
}
{
cesspool_announcement_t *entry = &pool->announcements[pool->announcement_count++];
memcpy(entry->topic, topic, 32);
memcpy(entry->peer_id, peer_id, 32);
entry->address = *address;
entry->last_seen = now;
}
return 0;
}
static void announcement_remove(cesspool_t *pool, const uint8_t topic[32],
const uint8_t peer_id[32])
{
size_t i = 0;
while (i < pool->announcement_count) {
cesspool_announcement_t *entry = &pool->announcements[i];
if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) {
pool->announcements[i] = pool->announcements[--pool->announcement_count];
continue;
}
i++;
}
}
static int peer_has_announcement(const cesspool_t *pool, const uint8_t peer_id[32])
{
size_t i;
for (i = 0; i < pool->announcement_count; i++)
if (!memcmp(pool->announcements[i].peer_id, peer_id, 32)) return 1;
return 0;
}
static int send_rendezvous_reply(cesspool_t *pool, cess_peer_t *requester,
const uint8_t topic[32])
{
cesspool_response_t response;
size_t i;
memset(&response, 0, sizeof(response));
for (i = 0; i < pool->announcement_count; i++) {
const cesspool_announcement_t *peer = &pool->announcements[i];
const struct sockaddr_in *address;
uint8_t *record;
if (memcmp(peer->topic, topic, 32) || same_peer_address(&peer->address, requester) ||
peer->address.addrlen < (socklen_t)sizeof(struct sockaddr_in)) continue;
address = (const struct sockaddr_in *)&peer->address.addr;
if (address->sin_family != AF_INET ||
response.peer_info_len / 38 >= CESSPOOL_MAX_RENDEZVOUS_PEERS ||
response.peer_info_len + 38 > sizeof(response.peer_info)) continue;
record = response.peer_info + response.peer_info_len;
memcpy(record, &address->sin_addr, 4);
memcpy(record + 4, &address->sin_port, 2);
memcpy(record + 6, peer->peer_id, 32);
response.peer_info_len += 38;
}
return send_reply(pool, requester, topic, &response);
}
static int receive_rendezvous_reply(cesspool_t *pool,
const cesspool_message_t *message)
{
cesspool_response_t response;
size_t i;
if (cesspool_response_decode(&response, message->payload, message->payload_len)) return -1;
if (response.peer_info_len % 38) { errno = EPROTO; return -1; }
for (i = 0; i < response.peer_info_len; i += 38) {
const uint8_t *record = response.peer_info + i;
cesspool_peer_t peer;
struct sockaddr_in *address;
memset(&peer, 0, sizeof(peer));
address = (struct sockaddr_in *)&peer.address.addr;
address->sin_family = AF_INET;
memcpy(&address->sin_addr, record, 4);
memcpy(&address->sin_port, record + 4, 2);
peer.address.addrlen = sizeof(*address);
memcpy(peer.public_key, record + 6, 32);
memcpy(peer.discovery_topic, message->topic, 32);
peer.discovered = 1;
peer.last_seen = monotonic_ms();
if (announcement_add(pool, message->topic, peer.public_key,
&peer.address, peer.last_seen)) continue;
if (cesspool_add_peer(pool, &peer)) return -1;
}
return 0;
}
static void cesspool_on_message(cessnet_t *net,cess_peer_t *source,const uint8_t *packet,size_t length)
{
cesspool_t *pool=(cesspool_t *)net->context;
cesspool_message_t message;
cesspool_peer_t peer;
if(!pool)return;
if(cesspool_decode(&message,packet,length)){
if(pool->on_transport_packet)pool->on_transport_packet(pool,source,packet,length);
return;
}
if(message.type>CESSPOOL_HOLEPUNCH||!memcmp(message.peer_id,net->peerid,32))return;
if (message.type == CESSPOOL_REPLY) {
size_t i;
int from_bootstrap = 0;
for (i = 0; i < pool->bootstrap_count; i++)
if (same_peer_address(&pool->bootstrap_nodes[i], source)) {
from_bootstrap = 1;
break;
}
if (!from_bootstrap) return;
/* A malformed datagram is not proof that the configured rendezvous
* endpoint is speaking Cesspool, so validate before refreshing online. */
if (receive_rendezvous_reply(pool, &message)) return;
pool->bootstrap_last_seen = monotonic_ms();
pool->bootstrap_reachable = 1;
return;
}
if (message.type == CESSPOOL_PONG) {
size_t i;
for (i = 0; i < pool->bootstrap_count; i++) {
if (same_peer_address(&pool->bootstrap_nodes[i], source)) {
pool->bootstrap_last_seen = monotonic_ms();
pool->bootstrap_reachable = 1;
break;
}
}
return;
}
memset(&peer,0,sizeof(peer));peer.address=*source;memcpy(peer.public_key,message.peer_id,32);
peer.last_seen = monotonic_ms();
if (message.type == CESSPOOL_ANNOUNCE) {
peer.discovered = 1;
memcpy(peer.discovery_topic, message.topic, sizeof(peer.discovery_topic));
if (announcement_add(pool, message.topic, message.peer_id, source,
peer.last_seen)) return;
}
cesspool_add_peer(pool,&peer);
if (message.type == CESSPOOL_UNANNOUNCE) {
cesspool_peer_t *known = cesspool_find_peer(pool, message.peer_id);
announcement_remove(pool, message.topic, message.peer_id);
if (known) known->discovered = peer_has_announcement(pool, message.peer_id);
} else if(message.type==CESSPOOL_PING)send_message(pool,source,CESSPOOL_PONG,NULL,pool->port);
else if(message.type==CESSPOOL_LOOKUP&&pool->bootstrap_server)
(void)send_rendezvous_reply(pool, source, message.topic);
else if(message.type==CESSPOOL_LOOKUP&&has_topic(pool,message.topic,1))
send_message(pool,source,CESSPOOL_ANNOUNCE,message.topic,pool->port);
else if(message.type==CESSPOOL_HOLEPUNCH&&pool->on_holepunch)pool->on_holepunch(pool,source);
}
int cesspool_init(cesspool_t *pool,const uint8_t peer_id[32])
{
uint8_t empty[32]={0};if(!pool||!peer_id){errno=EINVAL;return-1;}memset(pool,0,sizeof(*pool));if(cessnet_init(&pool->net,empty,peer_id))return-1;pool->net.context=pool;pool->net.on_message=cesspool_on_message;return 0;
}
int cesspool_add_bootstrap(cesspool_t *pool,const char *host,uint16_t port)
{
struct addrinfo hints, *resolved = NULL;
struct sockaddr_in *address;
cess_peer_t *peer;
if(!pool||!host||!port||pool->bootstrap_count>=CESSPOOL_MAX_BOOTSTRAP){errno=EINVAL;return-1;}
memset(&hints,0,sizeof(hints));hints.ai_family=AF_INET;hints.ai_socktype=SOCK_DGRAM;
if(getaddrinfo(host,NULL,&hints,&resolved)!=0||!resolved){errno=EHOSTUNREACH;return-1;}
peer=&pool->bootstrap_nodes[pool->bootstrap_count];memset(peer,0,sizeof(*peer));
memcpy(&peer->addr,resolved->ai_addr,resolved->ai_addrlen);peer->addrlen=(socklen_t)resolved->ai_addrlen;
address=(struct sockaddr_in*)&peer->addr;address->sin_port=htons(port);
freeaddrinfo(resolved);
{
size_t i;
for (i = 0; i < pool->bootstrap_count; i++)
if (same_peer_address(&pool->bootstrap_nodes[i], peer)) return 0;
}
if (pool->bootstrap_count >= CESSPOOL_MAX_BOOTSTRAP) { errno = ENOSPC; return -1; }
pool->bootstrap_count++;return 0;
}
static int add_endpoint_list(cesspool_t *pool, const char *setting)
{
char list[2048], *item, *next;
int added = 0;
if (!setting || !*setting) return 0;
if (strlen(setting) >= sizeof(list)) { errno = EOVERFLOW; return -1; }
strcpy(list, setting);
for (item = list; item && *item; item = next) {
char *separator, *end;
unsigned long port;
next = strpbrk(item, ",;\r\n");
if (next) *next++ = '\0';
separator = strrchr(item, ':');
if (!separator || separator == item || !separator[1]) continue;
*separator++ = '\0';
port = strtoul(separator, &end, 10);
if (*end || port == 0 || port > 65535) continue;
if (cesspool_add_bootstrap(pool, item, (uint16_t)port) != 0) return -1;
added++;
}
return added;
}
static int bootstrap_config_path(char *path, size_t capacity, int make_dirs)
{
const char *home = getenv("HOME");
const char *config = getenv("XDG_CONFIG_HOME");
char directory[1024], config_directory[1024];
if (!home || !*home) home = getenv("USERPROFILE");
#ifdef _WIN32
if ((!home || !*home) && getenv("HOMEDRIVE") && getenv("HOMEPATH")) {
static char combined[1024];
if (snprintf(combined, sizeof(combined), "%s%s", getenv("HOMEDRIVE"), getenv("HOMEPATH"))
< (int)sizeof(combined)) home = combined;
}
#endif
if (!home || !*home) home = ".";
if (!config || !*config) {
if (snprintf(config_directory, sizeof(config_directory), "%s/.config", home) >= (int)sizeof(config_directory)) {
errno = ENAMETOOLONG; return -1;
}
config = config_directory;
}
if (snprintf(directory, sizeof(directory), "%s/%s", config,
CESSPOOL_CONFIG_DIRECTORY) >= (int)sizeof(directory) ||
snprintf(path, capacity, "%s/bootstrap", directory) >= (int)capacity) {
errno = ENAMETOOLONG; return -1;
}
if (make_dirs) {
char parent[1024];
if (snprintf(parent, sizeof(parent), "%s", config) >= (int)sizeof(parent)) { errno = ENAMETOOLONG; return -1; }
if (mkdir(parent, 0700) && errno != EEXIST) return -1;
if (mkdir(directory, 0700) && errno != EEXIST) return -1;
}
return 0;
}
int cesspool_configure_bootstraps(cesspool_t *pool)
{
char path[1200], line[512];
FILE *file;
int count = 0, loaded;
if (!pool) { errno = EINVAL; return -1; }
loaded = add_endpoint_list(pool, getenv("CESSPOOL_BOOTSTRAP"));
if (loaded < 0) return -1;
count += loaded;
loaded = add_endpoint_list(pool, getenv("DDAT_BOOTSTRAP"));
if (loaded < 0) return -1;
count += loaded;
loaded = add_endpoint_list(pool, getenv("CABLE_BOOTSTRAP"));
if (loaded < 0) return -1;
count += loaded;
if (bootstrap_config_path(path, sizeof(path), 0)) return count;
file = fopen(path, "r");
if (!file) return errno == ENOENT ? count : -1;
while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\r\n#")] = '\0';
loaded = add_endpoint_list(pool, line);
if (loaded < 0) { fclose(file); return -1; }
count += loaded;
}
if (ferror(file)) { fclose(file); return -1; }
fclose(file);
return count;
}
int cesspool_bootstrap_config_add(const char *endpoint)
{
char path[1200], copy[512], *separator, *end;
unsigned long port;
FILE *file;
if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; }
strcpy(copy, endpoint);
separator = strrchr(copy, ':');
if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; }
*separator++ = '\0'; port = strtoul(separator, &end, 10);
if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; }
if (bootstrap_config_path(path, sizeof(path), 1)) return -1;
file = fopen(path, "a");
if (!file) return -1;
{
int failed = fprintf(file, "%s:%lu\n", copy, port) < 0;
if (fclose(file)) failed = 1;
return failed ? -1 : 0;
}
}
int cesspool_bootstrap_config_set(const char *endpoint)
{
char path[1200], temporary[1240], copy[512], *separator, *end;
unsigned long port;
FILE *file;
int n, failed;
if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; }
strcpy(copy, endpoint);
separator = strrchr(copy, ':');
if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; }
*separator++ = '\0';
port = strtoul(separator, &end, 10);
if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; }
if (bootstrap_config_path(path, sizeof(path), 1)) return -1;
n = snprintf(temporary, sizeof(temporary), "%s.tmp", path);
if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; }
file = fopen(temporary, "w");
if (!file) return -1;
failed = fprintf(file, "%s:%lu\n", copy, port) < 0;
if (fclose(file)) failed = 1;
if (failed || chmod(temporary, S_IRUSR | S_IWUSR)) {
int saved = errno;
(void)remove(temporary);
errno = saved;
return -1;
}
#ifdef _WIN32
if (!MoveFileExA(temporary, path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
(void)remove(temporary);
errno = EIO;
return -1;
}
#else
if (rename(temporary, path)) {
int saved = errno;
(void)remove(temporary);
errno = saved;
return -1;
}
#endif
return 0;
}
int cesspool_bootstrap_config_get(char *endpoint, size_t capacity)
{
char path[1200], line[512];
FILE *file;
size_t length;
if (!endpoint || !capacity) { errno = EINVAL; return -1; }
endpoint[0] = '\0';
if (bootstrap_config_path(path, sizeof(path), 0)) return -1;
file = fopen(path, "r");
if (!file) return -1;
while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\r\n#")] = '\0';
if (!line[0]) continue;
length = strlen(line);
if (length >= capacity) { fclose(file); errno = ENOSPC; return -1; }
memcpy(endpoint, line, length + 1);
fclose(file);
return 0;
}
fclose(file);
errno = ENOENT;
return -1;
}
int cesspool_bootstrap_config_list(char *contents, size_t capacity)
{
char path[1200];
FILE *file;
size_t length;
if (!contents || !capacity) { errno = EINVAL; return -1; }
contents[0] = '\0';
if (bootstrap_config_path(path, sizeof(path), 0)) return -1;
file = fopen(path, "r");
if (!file) return -1;
length = fread(contents, 1, capacity - 1, file);
if (ferror(file)) { int saved = errno; fclose(file); errno = saved; return -1; }
if (!feof(file)) { fclose(file); errno = ENOSPC; return -1; }
contents[length] = '\0';
return fclose(file) == 0 ? 0 : -1;
}
int cesspool_bootstrap(cesspool_t *pool){if(!pool||!pool->bootstrap_count){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_PING,NULL,pool->port);}
int cesspool_listen(cesspool_t *pool,const char *host,uint16_t port){
static const uint16_t defaults[]={CESSNET_PORT,3000,3002,3004,2001,2003,2005};
struct sockaddr_in bound;socklen_t length=sizeof(bound);size_t i,count;
if(!pool){errno=EINVAL;return-1;}
count=port?1:sizeof(defaults)/sizeof(defaults[0]);
for(i=0;i<count;i++){
uint16_t candidate=port?port:defaults[i];
if(cessnet_bind(&pool->net,host,candidate)==0)break;
if(port||errno!=EADDRINUSE)return-1;
}
if(i==count){errno=EADDRINUSE;return-1;}
if(getsockname(pool->net.sock,(struct sockaddr*)&bound,&length)){cessnet_close(&pool->net);return-1;}
pool->port=ntohs(bound.sin_port);pool->ready=1;return 0;
}
int cesspool_set_bootstrap_server(cesspool_t *pool, int enabled)
{
if (!pool || !pool->ready) { errno = EINVAL; return -1; }
pool->bootstrap_server = !!enabled;
return 0;
}
int cesspool_announce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_ANNOUNCE,topic,port?port:pool->port);}
int cesspool_lookup(cesspool_t *pool,const uint8_t topic[32]){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_LOOKUP,topic,0);}
int cesspool_unannounce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_UNANNOUNCE,topic,port);}
int cesspool_ping(cesspool_t *pool,const cess_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_PING,NULL,pool->port);}
int cesspool_holepunch(cesspool_t *pool,const cess_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_HOLEPUNCH,NULL,pool->port);}
int cesspool_poll(cesspool_t *pool,int timeout_ms){if(!pool){errno=EINVAL;return-1;}return cessnet_poll(&pool->net,timeout_ms);}
cesspool_peer_t *cesspool_find_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key)return NULL;for(i=0;i<p->peer_count;i++)if(!memcmp(p->peers[i].public_key,key,32))return&p->peers[i];return NULL;}
int cesspool_add_peer(cesspool_t*p,const cesspool_peer_t*peer){cesspool_peer_t*old;int newly_discovered;if(!p||!peer){errno=EINVAL;return-1;}old=cesspool_find_peer(p,peer->public_key);if(old){newly_discovered=peer->discovered&&(!old->discovered||memcmp(peer->discovery_topic,old->discovery_topic,32));{cesspool_peer_t merged=*peer;if(!peer->discovered&&old->discovered){merged.discovered=1;memcpy(merged.discovery_topic,old->discovery_topic,32);}*old=merged;}if(newly_discovered&&p->on_peer)p->on_peer(p,old);return 0;}if(p->peer_count>=CESSPOOL_MAX_PEERS){errno=ENOSPC;return-1;}p->peers[p->peer_count++]=*peer;if(peer->discovered&&p->on_peer)p->on_peer(p,peer);return 0;}
int cesspool_remove_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key){errno=EINVAL;return-1;}for(i=0;i<p->peer_count;i++)if(!memcmp(p->peers[i].public_key,key,32)){p->peers[i]=p->peers[--p->peer_count];return 0;}errno=ENOENT;return-1;}
int cesspool_join(cesspool_t *p, const uint8_t topic[32], uint16_t port,
int announce, int lookup)
{
cesspool_topic_t entry;
int announced_now = 0;
if (!p || !topic || (!announce && !lookup)) { errno = EINVAL; return -1; }
if (p->topic_count >= CESSPOOL_MAX_TOPICS) { errno = ENOSPC; return -1; }
memset(&entry, 0, sizeof(entry));
memcpy(entry.key, topic, CESSPOOL_TOPIC_BYTES);
entry.port = port ? port : p->port;
entry.announcing = !!announce;
entry.looking_up = !!lookup;
/* No configured rendezvous falls back to best-effort LAN broadcast. */
if (announce) {
if (cesspool_announce(p, topic, entry.port)) return -1;
announced_now = 1;
}
if (lookup && cesspool_lookup(p, topic)) {
if (announced_now) (void)cesspool_unannounce(p, topic, entry.port);
return -1;
}
p->topics[p->topic_count++] = entry;
return 0;
}
int cesspool_leave(cesspool_t*p,const uint8_t topic[32]){size_t i;if(!p||!topic){errno=EINVAL;return-1;}for(i=0;i<p->topic_count;i++)if(!memcmp(p->topics[i].key,topic,32)){if(p->topics[i].announcing)cesspool_unannounce(p,topic,p->topics[i].port);p->topics[i]=p->topics[--p->topic_count];return 0;}errno=ENOENT;return-1;}
int cesspool_tick(cesspool_t *p, uint64_t now)
{
size_t i = 0;
uint64_t wall_now;
if (!p) { errno = EINVAL; return -1; }
for (i = 0; i < p->topic_count; i++) if (now >= p->topics[i].refresh_at) {
if (p->topics[i].announcing) (void)cesspool_announce(p, p->topics[i].key, p->topics[i].port);
if (p->topics[i].looking_up) (void)cesspool_lookup(p, p->topics[i].key);
p->topics[i].refresh_at = now + 240000u;
}
if (!p->bootstrap_server && p->bootstrap_count && now >= p->bootstrap_probe_at) {
int probed = 0;
for (i = 0; i < p->topic_count; i++) if (p->topics[i].looking_up) {
(void)cesspool_lookup(p, p->topics[i].key);
probed = 1;
}
if (!probed) (void)cesspool_bootstrap(p);
p->bootstrap_probe_at = now + CESSPOOL_BOOTSTRAP_PROBE_MS;
}
for (i = 0; i < CESSPOOL_MAX_TRANSACTIONS; i++)
if (p->transactions[i].active && now >= p->transactions[i].expires_at)
p->transactions[i].active = 0;
i = 0;
wall_now = monotonic_ms();
if (p->bootstrap_count && p->bootstrap_last_seen && wall_now >= p->bootstrap_last_seen)
p->bootstrap_reachable = wall_now - p->bootstrap_last_seen < CESSPOOL_BOOTSTRAP_TIMEOUT_MS;
else if (p->bootstrap_count) p->bootstrap_reachable = 0;
while (i < p->announcement_count) {
cesspool_announcement_t *entry = &p->announcements[i];
if (!entry->last_seen || (wall_now >= entry->last_seen &&
wall_now - entry->last_seen >= CESSPOOL_PEER_TTL_MS)) {
p->announcements[i] = p->announcements[--p->announcement_count];
continue;
}
i++;
}
/* Client peer rows are a view of live topic announcements. Without
* reconciling this bit after pruning, a peer remains "discovered" forever
* even though its rendezvous record has expired. */
for (i = 0; i < p->peer_count; i++) {
cesspool_peer_t *peer = &p->peers[i];
size_t announcement;
peer->discovered = 0;
for (announcement = 0; announcement < p->announcement_count; announcement++) {
const cesspool_announcement_t *entry = &p->announcements[announcement];
if (memcmp(peer->public_key, entry->peer_id, CESSPOOL_PEER_BYTES)) continue;
peer->discovered = 1;
memcpy(peer->discovery_topic, entry->topic, CESSPOOL_TOPIC_BYTES);
if (entry->last_seen > peer->last_seen) peer->last_seen = entry->last_seen;
break;
}
}
if (p->bootstrap_server) {
i = 0;
while (i < p->peer_count) {
cesspool_peer_t *peer = &p->peers[i];
if (!peer->last_seen || (wall_now >= peer->last_seen &&
wall_now - peer->last_seen >= CESSPOOL_PEER_TTL_MS)) {
p->peers[i] = p->peers[--p->peer_count];
continue;
}
i++;
}
}
return 0;
}
static int distance_less(const uint8_t*a,const uint8_t*b,const uint8_t target[32]){size_t i;for(i=0;i<32;i++){uint8_t da=a[i]^target[i],db=b[i]^target[i];if(da!=db)return da<db;}return 0;}
size_t cesspool_closest(cesspool_t*p,const uint8_t target[32],cesspool_peer_t**out,size_t cap){size_t i,j,n=0;if(!p||!target||(!out&&cap))return 0;for(i=0;i<p->peer_count;i++){if(n<cap)out[n++]=&p->peers[i];else if(cap&&distance_less(p->peers[i].public_key,out[n-1]->public_key,target))out[n-1]=&p->peers[i];for(j=n;j>1&&distance_less(out[j-1]->public_key,out[j-2]->public_key,target);j--){cesspool_peer_t*t=out[j-1];out[j-1]=out[j-2];out[j-2]=t;}}return n;}
size_t cesspool_topic_peers(cesspool_t *p, const uint8_t topic[32],
cesspool_peer_t **out, size_t cap)
{
size_t i, n = 0;
if (!p || !topic || (!out && cap)) return 0;
for (i = 0; i < p->announcement_count && n < cap; i++) {
cesspool_announcement_t *entry = &p->announcements[i];
cesspool_peer_t *peer;
size_t j;
if (memcmp(entry->topic, topic, 32)) continue;
for (j = 0; j < n; j++) if (!memcmp(out[j]->public_key, entry->peer_id, 32)) break;
if (j < n) continue;
peer = cesspool_find_peer(p, entry->peer_id);
if (peer) out[n++] = peer;
}
return n;
}
size_t cesspool_announced_peer_count(const cesspool_t *p)
{
size_t i, j, count = 0;
if (!p) return 0;
for (i = 0; i < p->announcement_count; i++) {
for (j = 0; j < i; j++)
if (!memcmp(p->announcements[i].peer_id, p->announcements[j].peer_id, 32)) break;
if (j == i) count++;
}
return count;
}
size_t cesspool_announced_topic_count(const cesspool_t *p)
{
size_t i, j, count = 0;
if (!p) return 0;
for (i = 0; i < p->announcement_count; i++) {
for (j = 0; j < i; j++)
if (!memcmp(p->announcements[i].topic, p->announcements[j].topic, 32)) break;
if (j == i) count++;
}
return count;
}
int cesspool_transaction_begin(cesspool_t*p,uint8_t type,const uint8_t target[32],uint64_t expires,uint16_t*out){size_t i,j,attempt;uint16_t candidate;if(!p||!target||!out){errno=EINVAL;return-1;}for(i=0;i<CESSPOOL_MAX_TRANSACTIONS;i++)if(!p->transactions[i].active){for(attempt=0;attempt<UINT16_MAX;attempt++){candidate=++p->next_transaction;if(!candidate)candidate=++p->next_transaction;for(j=0;j<CESSPOOL_MAX_TRANSACTIONS;j++)if(p->transactions[j].active&&p->transactions[j].id==candidate)break;if(j==CESSPOOL_MAX_TRANSACTIONS){cesspool_transaction_t*t=&p->transactions[i];memset(t,0,sizeof(*t));t->active=1;t->type=type;t->id=candidate;t->expires_at=expires;memcpy(t->target,target,32);*out=t->id;return 0;}}errno=ENOSPC;return-1;}errno=ENOSPC;return-1;}
int cesspool_transaction_finish(cesspool_t*p,uint16_t id){size_t i;if(!p){errno=EINVAL;return-1;}for(i=0;i<CESSPOOL_MAX_TRANSACTIONS;i++)if(p->transactions[i].active&&p->transactions[i].id==id){p->transactions[i].active=0;return 0;}errno=ENOENT;return-1;}
int cesspool_destroy(cesspool_t *pool){if(!pool){errno=EINVAL;return-1;}pool->ready=0;return cessnet_close(&pool->net);}
+247
View File
@@ -0,0 +1,247 @@
#define _POSIX_C_SOURCE 200809L
#include "cess_compat.h"
#include "cesspool.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <sodium.h>
#ifndef EKEYREJECTED
#define EKEYREJECTED EACCES
#endif
#ifdef _WIN32
#include <sys/stat.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#define BOOTSTRAP_PATH_BYTES 1400u
static volatile sig_atomic_t bootstrap_running = 1;
static void stop_bootstrap(int signal_number)
{
(void)signal_number;
bootstrap_running = 0;
}
static void usage(FILE *out)
{
fputs("cesspool-bootstrap - dedicated Cesspool public rendezvous node\n"
"usage: cesspool-bootstrap [--bind IPv4] [--port UDP-PORT]\n"
" [--advertise HOST:PORT] [--state-dir DIR]\n"
" cesspool-bootstrap --help\n"
"Runs in the foreground for a service manager. It handles peer discovery;\n"
"chat and archive payloads remain peer-to-peer.\n", out);
}
static int parse_port(const char *text, uint16_t *port)
{
char *end;
unsigned long value;
if (!text || !*text || !port) { errno = EINVAL; return -1; }
value = strtoul(text, &end, 10);
if (*end || value == 0 || value > 65535) { errno = EINVAL; return -1; }
*port = (uint16_t)value;
return 0;
}
static int make_one_state_directory(const char *path, int secure_existing)
{
struct stat info;
int created = 0;
if (!path || !*path) { errno = EINVAL; return -1; }
if (mkdir(path, 0700) == 0) created = 1;
else if (errno != EEXIST) return -1;
if (stat(path, &info) != 0) return -1;
if (!S_ISDIR(info.st_mode)) { errno = ENOTDIR; return -1; }
if ((created || secure_existing) && chmod(path, S_IRWXU) != 0) return -1;
return 0;
}
static int make_state_directory(const char *path)
{
char copy[BOOTSTRAP_PATH_BYTES];
size_t length, start = 0, i;
if (!path || !*path) { errno = EINVAL; return -1; }
length = strlen(path);
if (length >= sizeof(copy)) { errno = ENAMETOOLONG; return -1; }
memcpy(copy, path, length + 1);
#ifdef _WIN32
if (length >= 3 && copy[1] == ':') start = 3;
#else
if (copy[0] == '/') start = 1;
#endif
for (i = start; i < length; i++) {
if (copy[i] != '/' && copy[i] != '\\') continue;
if (i == 0 || (i == 2 && copy[1] == ':')) continue;
copy[i] = '\0';
if (make_one_state_directory(copy, 0) != 0) return -1;
copy[i] = path[i];
}
return make_one_state_directory(copy, 1);
}
static int default_state_directory(char *out, size_t capacity)
{
const char *base;
int n;
if (!out || capacity == 0) { errno = EINVAL; return -1; }
#ifdef _WIN32
base = getenv("LOCALAPPDATA");
if (!base || !*base) { errno = ENOENT; return -1; }
n = snprintf(out, capacity, "%s/dat-gpt/cesspool-bootstrap-state", base);
#else
base = getenv("XDG_STATE_HOME");
if (base && *base) n = snprintf(out, capacity, "%s/dat-gpt/cesspool-bootstrap", base);
else {
base = getenv("HOME");
if (!base || !*base) { errno = ENOENT; return -1; }
n = snprintf(out, capacity, "%s/.local/state/dat-gpt/cesspool-bootstrap", base);
}
#endif
if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; }
return 0;
}
static int load_or_create_identity(const char *state_dir, cesspool_keypair_t *identity)
{
char key_path[BOOTSTRAP_PATH_BYTES];
uint8_t derived[32];
int n;
if (!state_dir || !identity) { errno = EINVAL; return -1; }
n = snprintf(key_path, sizeof(key_path), "%s/%s", state_dir, "identity.key");
if (n < 0 || (size_t)n >= sizeof(key_path)) { errno = ENAMETOOLONG; return -1; }
if (cesspool_identity_init()) return -1;
if (cesspool_identity_load(key_path, identity) == 0) {
if (crypto_sign_ed25519_sk_to_pk(derived, identity->seckey) != 0 ||
sodium_memcmp(derived, identity->pubkey, sizeof(derived)) != 0) {
sodium_memzero(identity, sizeof(*identity));
errno = EKEYREJECTED;
return -1;
}
if (chmod(key_path, S_IRUSR | S_IWUSR) != 0) return -1;
return 0;
}
if (errno != ENOENT || cesspool_identity_generate(identity) ||
cesspool_identity_save(key_path, identity) != 0 ||
chmod(key_path, S_IRUSR | S_IWUSR) != 0) {
int saved = errno;
sodium_memzero(identity, sizeof(*identity));
errno = saved;
return -1;
}
return 0;
}
static uint64_t monotonic_milliseconds(void)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0;
return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u;
}
int main(int argc, char **argv)
{
cesspool_t pool;
cesspool_keypair_t identity;
const char *bind_host = "0.0.0.0";
const char *state_dir = NULL;
const char *advertise = NULL;
uint16_t port = CESSNET_PORT;
uint64_t next_status = 0;
char default_state[BOOTSTRAP_PATH_BYTES];
int i, result = 1;
memset(&pool, 0, sizeof(pool));
pool.net.sock = CESS_INVALID_SOCKET;
memset(&identity, 0, sizeof(identity));
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
usage(stdout);
return 0;
}
if ((!strcmp(argv[i], "--bind") || !strcmp(argv[i], "--port") ||
!strcmp(argv[i], "--advertise") || !strcmp(argv[i], "--state-dir")) && i + 1 >= argc) {
fprintf(stderr, "cesspool-bootstrap: %s needs a value\n", argv[i]);
return 2;
}
if (!strcmp(argv[i], "--bind")) bind_host = argv[++i];
else if (!strcmp(argv[i], "--port")) {
if (parse_port(argv[++i], &port)) { perror("cesspool-bootstrap --port"); return 2; }
} else if (!strcmp(argv[i], "--advertise")) advertise = argv[++i];
else if (!strcmp(argv[i], "--state-dir")) state_dir = argv[++i];
else {
fprintf(stderr, "cesspool-bootstrap: unknown option %s\n", argv[i]);
usage(stderr);
return 2;
}
}
if (!state_dir) {
if (default_state_directory(default_state, sizeof(default_state)) != 0) {
perror("cesspool-bootstrap default state directory");
return 1;
}
state_dir = default_state;
}
if (!bind_host[0] || strchr(bind_host, ':')) {
fputs("cesspool-bootstrap: --bind requires an IPv4 host or 0.0.0.0\n", stderr);
return 2;
}
(void)umask(077);
if (make_state_directory(state_dir) || load_or_create_identity(state_dir, &identity) ||
cesspool_init(&pool, identity.pubkey) ||
cesspool_listen(&pool, bind_host, port) ||
cesspool_set_bootstrap_server(&pool, 1)) {
perror("cesspool-bootstrap startup");
goto done;
}
signal(SIGINT, stop_bootstrap);
signal(SIGTERM, stop_bootstrap);
printf("Cesspool public bootstrap ready\n"
" bind: %s:%u/udp\n"
" state: persistent private identity loaded\n"
" peer discovery only; peer payloads are not relayed\n",
bind_host, pool.port);
if (advertise && *advertise)
printf(" advertised endpoint: %s/udp\n"
" client setup: dat bootstrap set %s\n"
" client setup: cable bootstrap set %s\n"
" GUI setup: Settings > Cesspool Node > %s\n",
advertise, advertise, advertise, advertise);
else printf(" advertised endpoint: not set; configure a public host:port in clients\n");
printf(" Windows firewall (run elevated): netsh advfirewall firewall add rule name=\"Cesspool UDP %u\" dir=in action=allow protocol=UDP localport=%u\n",
pool.port, pool.port);
puts(" route the same UDP port through the edge router or cloud firewall");
fflush(stdout);
while (bootstrap_running) {
int polled = cesspool_poll(&pool, 1000);
uint64_t now = monotonic_milliseconds();
if (polled < 0 && errno != EINTR) {
perror("cesspool-bootstrap poll");
goto done;
}
if (cesspool_tick(&pool, now) != 0) {
perror("cesspool-bootstrap maintenance");
goto done;
}
if (now >= next_status) {
fprintf(stderr, "cesspool-bootstrap: peers=%lu topics=%lu\n",
(unsigned long)cesspool_announced_peer_count(&pool),
(unsigned long)cesspool_announced_topic_count(&pool));
next_status = now + 60000u;
}
}
result = 0;
done:
if (pool.net.sock != CESS_INVALID_SOCKET) (void)cesspool_destroy(&pool);
sodium_memzero(&identity, sizeof(identity));
return result;
}