0.0.002
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:
1 parent
f0f91540be
commit
b735900f2e
323 files changed
+134347
-343
No files matched your search
@@ -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)"
|
||||
@@ -0,0 +1 @@
|
||||
cesspool
|
||||
@@ -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).
|
||||
@@ -0,0 +1 @@
|
||||
#include "include/cess_net.h"
|
||||
@@ -0,0 +1 @@
|
||||
#include "include/cesspool.h"
|
||||
@@ -0,0 +1,28 @@
|
||||
.TH CESSPOOL-BOOTSTRAP 1
|
||||
.SH NAME
|
||||
cesspool-bootstrap \- run a Cesspool discovery node
|
||||
.SH SYNOPSIS
|
||||
.B cesspool-bootstrap
|
||||
[B--bindR IPv4] [B--portR UDP-PORT] [B--advertiseR HOST:PORT] [B--state-dirR 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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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);}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user