feat: Go转C
This commit is contained in:
@@ -8,7 +8,8 @@
|
||||
"Bash(find /c/Users/64187/Desktop/Workspace/OmniSocketGo -type f -name *.go)",
|
||||
"Bash(git status:*)",
|
||||
"Bash(git fetch:*)",
|
||||
"Bash(git pull:*)"
|
||||
"Bash(git pull:*)",
|
||||
"Bash(wc:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
67
c/Makefile
Normal file
67
c/Makefile
Normal file
@@ -0,0 +1,67 @@
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -std=c11 -Wall -Wextra -O2 -pthread -D_GNU_SOURCE
|
||||
CPPFLAGS ?= -Iinclude -Ithird_party/cjson -Ithird_party/kcp
|
||||
LDFLAGS ?= -pthread
|
||||
|
||||
BIN_DIR := bin
|
||||
SRC_DIR := src
|
||||
CMD_DIR := cmd
|
||||
|
||||
COMMON_SRCS := \
|
||||
$(SRC_DIR)/omni_common.c \
|
||||
$(SRC_DIR)/protocol.c \
|
||||
$(SRC_DIR)/latencylog.c \
|
||||
$(SRC_DIR)/tx_timestamp_debug.c \
|
||||
$(SRC_DIR)/kcp_packet_debug.c \
|
||||
$(SRC_DIR)/kcp_session_stats.c \
|
||||
$(SRC_DIR)/linux_timestamping.c \
|
||||
$(SRC_DIR)/interactive.c \
|
||||
$(SRC_DIR)/transport_udp.c \
|
||||
$(SRC_DIR)/transport_kcp.c \
|
||||
$(SRC_DIR)/server_udp_relay.c \
|
||||
$(SRC_DIR)/server_udp_hub.c \
|
||||
$(SRC_DIR)/server_kcp_hub.c \
|
||||
$(SRC_DIR)/peer_udp_client.c \
|
||||
$(SRC_DIR)/peer_kcp_client.c \
|
||||
third_party/cjson/cJSON.c \
|
||||
third_party/kcp/ikcp.c
|
||||
|
||||
TARGETS := \
|
||||
$(BIN_DIR)/udpserver \
|
||||
$(BIN_DIR)/udppeer \
|
||||
$(BIN_DIR)/udpping \
|
||||
$(BIN_DIR)/udprelay \
|
||||
$(BIN_DIR)/kcpserver \
|
||||
$(BIN_DIR)/kcppeer \
|
||||
$(BIN_DIR)/kcpping
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
$(BIN_DIR):
|
||||
mkdir -p $(BIN_DIR)
|
||||
|
||||
$(BIN_DIR)/udpserver: $(CMD_DIR)/udpserver.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BIN_DIR)/udppeer: $(CMD_DIR)/udppeer.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BIN_DIR)/udpping: $(CMD_DIR)/udpping.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BIN_DIR)/udprelay: $(CMD_DIR)/udprelay.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BIN_DIR)/kcpserver: $(CMD_DIR)/kcpserver.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BIN_DIR)/kcppeer: $(CMD_DIR)/kcppeer.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BIN_DIR)/kcpping: $(CMD_DIR)/kcpping.c $(COMMON_SRCS) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BIN_DIR)
|
||||
|
||||
.PHONY: all clean
|
||||
88
c/README.md
Normal file
88
c/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# OmniSocketC
|
||||
|
||||
Linux-only C11 implementation of the UDP/KCP transport stack from `OmniSocketGo`.
|
||||
|
||||
This subtree is intentionally standalone. The Go code stays in place as the behavior reference, while the C implementation builds its own binaries under `c/bin/`.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cd c
|
||||
make
|
||||
```
|
||||
|
||||
Build outputs:
|
||||
|
||||
- `c/bin/udpserver`
|
||||
- `c/bin/udppeer`
|
||||
- `c/bin/udpping`
|
||||
- `c/bin/udprelay`
|
||||
- `c/bin/kcpserver`
|
||||
- `c/bin/kcppeer`
|
||||
- `c/bin/kcpping`
|
||||
|
||||
## Run On Different Machines
|
||||
|
||||
Server `D` runs the KCP hub on `0.0.0.0:10909`:
|
||||
|
||||
```bash
|
||||
./c/bin/kcpserver -listen 0.0.0.0:10909 \
|
||||
-kcp-ts-debug-log logs/d-kcp-ts.jsonl \
|
||||
-kcp-session-stats-log logs/d-kcp-stats.jsonl
|
||||
```
|
||||
|
||||
Relay `C` runs a raw UDP forwarder to `D`:
|
||||
|
||||
```bash
|
||||
./c/bin/kcpserver -mode=relay -listen 0.0.0.0:10909 -relay-remote 172.21.32.15:10909
|
||||
```
|
||||
|
||||
Peer `A` dials `D` through relay `C`:
|
||||
|
||||
```bash
|
||||
./c/bin/kcppeer -id peer-a -server 172.21.32.15:10909 -relay-via 106.55.173.235:10909 \
|
||||
-inbox-dir inbox/a \
|
||||
-latency-log logs/a-latency.jsonl \
|
||||
-kcp-ts-debug-log logs/a-kcp-ts.jsonl \
|
||||
-kcp-session-stats-log logs/a-kcp-stats.jsonl
|
||||
```
|
||||
|
||||
Peer `B` dials `D` directly:
|
||||
|
||||
```bash
|
||||
./c/bin/kcppeer -id peer-b -server 81.70.156.140:10909 \
|
||||
-inbox-dir inbox/b \
|
||||
-latency-log logs/b-latency.jsonl \
|
||||
-kcp-ts-debug-log logs/b-kcp-ts.jsonl \
|
||||
-kcp-session-stats-log logs/b-kcp-stats.jsonl
|
||||
```
|
||||
|
||||
Optional ping / echo tools:
|
||||
|
||||
```bash
|
||||
./c/bin/kcpping -id peer-a -server 106.55.173.235:10909 -echo
|
||||
./c/bin/kcpping -id peer-b -server 81.70.156.140:10909 -to peer-a -count 20 -interval 100ms
|
||||
./c/bin/udpserver -listen 0.0.0.0:9001
|
||||
./c/bin/udppeer -id peer-a -server 127.0.0.1:9001
|
||||
./c/bin/udpping -id pinger -server 127.0.0.1:9001 -to peer-a -count 20
|
||||
```
|
||||
|
||||
## Interactive Commands
|
||||
|
||||
`udppeer` and `kcppeer` support the same interactive shell:
|
||||
|
||||
```text
|
||||
help
|
||||
text peer-b hello
|
||||
text peer-a hi
|
||||
file peer-a /tmp/test125.bin
|
||||
quit
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The C project targets Linux only.
|
||||
- It preserves the Go wire format for UDP datagrams and KCP stream frames.
|
||||
- It keeps runtime JSONL logging, UDP TX timestamp debug, KCP packet debug, and KCP session stats.
|
||||
- Offline `latencysummary` and HTML chart generation are intentionally not migrated.
|
||||
- No automated C tests are included in this subtree; validation is expected to happen on Linux via `make` and manual smoke tests.
|
||||
334
c/cmd/kcppeer.c
Normal file
334
c/cmd/kcppeer.c
Normal file
@@ -0,0 +1,334 @@
|
||||
#include "cli_parse.h"
|
||||
#include "interactive.h"
|
||||
#include "peer_kcp_client.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct kcppeer_receive_ctx {
|
||||
kcp_client_t *client;
|
||||
const char *inbox_dir;
|
||||
volatile int stop_requested;
|
||||
int rc;
|
||||
} kcppeer_receive_ctx_t;
|
||||
|
||||
static void kcppeer_usage(FILE *out) {
|
||||
fprintf(out, "usage: kcppeer [-id peer-a] [-server 127.0.0.1:9002] [-relay-via addr]\n");
|
||||
fprintf(out, " [-to peer] [-text msg | -file path] [-bind-ip ip] [-bind-device dev]\n");
|
||||
fprintf(out, " [-inbox-dir dir] [-latency-log path] [-kcp-ts-debug-log path]\n");
|
||||
fprintf(out, " [-kcp-session-stats-log path] [-kcp-session-stats-interval 100ms]\n");
|
||||
fprintf(out, " [-interactive[=true|false]]\n");
|
||||
}
|
||||
|
||||
static void *kcppeer_receive_thread_main(void *arg) {
|
||||
kcppeer_receive_ctx_t *ctx = (kcppeer_receive_ctx_t *) arg;
|
||||
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
char persisted_path[512];
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_client_receive(ctx->client, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = ctx->stop_requested ? 0 : -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case MSG_TYPE_TEXT:
|
||||
if (kcp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
||||
fprintf(stderr, "kcppeer: persist text from %s to %s failed\n", msg.from, msg.to);
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = -1;
|
||||
return NULL;
|
||||
}
|
||||
fprintf(stderr, "received text from %s to %s and persisted to %s\n", msg.from, msg.to, persisted_path);
|
||||
break;
|
||||
case MSG_TYPE_FILE:
|
||||
if (kcp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
||||
fprintf(stderr, "kcppeer: persist file from %s to %s failed\n", msg.from, msg.to);
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = -1;
|
||||
return NULL;
|
||||
}
|
||||
fprintf(stderr, "received file from %s to %s: %s (%lu bytes) -> %s\n", msg.from, msg.to, msg.file_name, (unsigned long) msg.body_len, persisted_path);
|
||||
break;
|
||||
case MSG_TYPE_ERROR:
|
||||
fprintf(stderr, "received error from %s to %s: %.*s\n", msg.from, msg.to, (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "received unexpected message type %s from %s\n", protocol_message_type_name(msg.type), msg.from);
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = -1;
|
||||
return NULL;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *peer_id = "peer-a";
|
||||
const char *server_addr = "127.0.0.1:9002";
|
||||
const char *relay_via = "";
|
||||
const char *target_peer = "";
|
||||
const char *text = "";
|
||||
const char *file_path = "";
|
||||
const char *bind_ip = "";
|
||||
const char *bind_device = "";
|
||||
const char *inbox_dir = "inbox";
|
||||
const char *latency_log_path = "";
|
||||
const char *packet_log_path = "";
|
||||
const char *stats_log_path = "";
|
||||
const char *stats_interval_raw = "";
|
||||
int stats_interval_ms = KCP_DEFAULT_STATS_INTERVAL_MS;
|
||||
int interactive = 1;
|
||||
latency_logger_t *latency_logger = NULL;
|
||||
kcp_packet_debug_logger_t *packet_logger = NULL;
|
||||
kcp_session_stats_logger_t *stats_logger = NULL;
|
||||
kcp_client_t *client = NULL;
|
||||
kcppeer_receive_ctx_t receive_ctx;
|
||||
pthread_t receive_thread;
|
||||
int receive_thread_started = 0;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
memset(&receive_ctx, 0, sizeof(receive_ctx));
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-id", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -id requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
peer_id = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-server", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -server requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
server_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-relay-via", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -relay-via requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
relay_via = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-to", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -to requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
target_peer = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-text", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -text requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
text = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-file", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -file requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
file_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-ip", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -bind-ip requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_ip = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-device", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -bind-device requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_device = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-inbox-dir", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -inbox-dir requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
inbox_dir = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -latency-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
latency_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-ts-debug-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -kcp-ts-debug-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
packet_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-session-stats-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -kcp-session-stats-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
stats_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-session-stats-interval", &value)) < 0) {
|
||||
fprintf(stderr, "kcppeer: flag -kcp-session-stats-interval requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
stats_interval_raw = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_bool_flag(argv[i], "-interactive", &interactive)) < 0) {
|
||||
fprintf(stderr, "kcppeer: invalid -interactive value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
kcppeer_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "kcppeer: unknown argument %s\n", argv[i]);
|
||||
kcppeer_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (text[0] != '\0' && file_path[0] != '\0') {
|
||||
fprintf(stderr, "kcppeer: only one of -text or -file may be specified\n");
|
||||
return 1;
|
||||
}
|
||||
if ((text[0] != '\0' || file_path[0] != '\0') && target_peer[0] == '\0') {
|
||||
fprintf(stderr, "kcppeer: flag -to is required when sending text or file\n");
|
||||
return 1;
|
||||
}
|
||||
if (kcp_session_stats_parse_interval_ms(stats_interval_raw, &stats_interval_ms) != 0) {
|
||||
fprintf(stderr, "kcppeer: invalid -kcp-session-stats-interval value %s\n", stats_interval_raw);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (latency_log_path[0] != '\0') {
|
||||
latency_logger = latencylog_open_jsonl(latency_log_path);
|
||||
if (latency_logger == NULL) {
|
||||
fprintf(stderr, "kcppeer: open latency logger %s failed\n", latency_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (packet_log_path[0] != '\0') {
|
||||
packet_logger = kcp_packet_debug_open_jsonl(packet_log_path);
|
||||
if (packet_logger == NULL) {
|
||||
fprintf(stderr, "kcppeer: open kcp packet debug logger %s failed\n", packet_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (stats_log_path[0] != '\0') {
|
||||
stats_logger = kcp_session_stats_open_jsonl(stats_log_path);
|
||||
if (stats_logger == NULL) {
|
||||
fprintf(stderr, "kcppeer: open kcp session stats logger %s failed\n", stats_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
client = kcp_client_dial(server_addr, relay_via, peer_id, bind_ip, bind_device, latency_logger, packet_logger, stats_logger, stats_interval_ms);
|
||||
if (client == NULL) {
|
||||
fprintf(stderr, "kcppeer: dial kcp server %s failed\n", server_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
if (relay_via[0] != '\0') {
|
||||
fprintf(stderr, "opened KCP session as %s; logical server=%s, actual dial target=%s via relay; register not yet confirmed\n", kcp_client_id(client), server_addr, relay_via);
|
||||
} else {
|
||||
fprintf(stderr, "opened KCP session as %s; logical server=%s, actual dial target=%s; register not yet confirmed\n", kcp_client_id(client), server_addr, server_addr);
|
||||
}
|
||||
|
||||
receive_ctx.client = client;
|
||||
receive_ctx.inbox_dir = inbox_dir;
|
||||
if (pthread_create(&receive_thread, NULL, kcppeer_receive_thread_main, &receive_ctx) != 0) {
|
||||
fprintf(stderr, "kcppeer: create receive thread failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
receive_thread_started = 1;
|
||||
|
||||
if (target_peer[0] != '\0' && text[0] != '\0') {
|
||||
if (kcp_client_send_text(client, target_peer, text) != 0) {
|
||||
fprintf(stderr, "kcppeer: send text to %s failed\n", target_peer);
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "sent text to %s\n", target_peer);
|
||||
}
|
||||
if (target_peer[0] != '\0' && file_path[0] != '\0') {
|
||||
if (kcp_client_send_file_path(client, target_peer, file_path) != 0) {
|
||||
fprintf(stderr, "kcppeer: send file %s to %s failed\n", file_path, target_peer);
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "sent file %s to %s\n", file_path, target_peer);
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
char line[2048];
|
||||
char prompt[128];
|
||||
|
||||
snprintf(prompt, sizeof(prompt), "%s> ", kcp_client_id(client));
|
||||
interactive_print_help(stdout, "KCP");
|
||||
while (fputs(prompt, stdout) >= 0 && fflush(stdout) == 0 && fgets(line, sizeof(line), stdin) != NULL) {
|
||||
interactive_command_t command;
|
||||
char err[128];
|
||||
|
||||
omni_trim_newline(line);
|
||||
if (interactive_parse_command(line, &command, err, sizeof(err)) != 0) {
|
||||
if (strstr(err, "empty command") == NULL) {
|
||||
fprintf(stderr, "%s\n", err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_HELP) {
|
||||
interactive_print_help(stdout, "KCP");
|
||||
continue;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_QUIT) {
|
||||
break;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_TEXT) {
|
||||
if (kcp_client_send_text(client, command.to, command.value) != 0) {
|
||||
fprintf(stderr, "kcppeer: send text to %s failed\n", command.to);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "sent text to %s\n", command.to);
|
||||
continue;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_FILE) {
|
||||
if (kcp_client_send_file_path(client, command.to, command.value) != 0) {
|
||||
fprintf(stderr, "kcppeer: send file %s to %s failed\n", command.value, command.to);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "sent file %s to %s\n", command.value, command.to);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
receive_ctx.stop_requested = 1;
|
||||
kcp_client_close(client);
|
||||
if (receive_thread_started) {
|
||||
pthread_join(receive_thread, NULL);
|
||||
if (rc == 0 && receive_ctx.rc != 0) {
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
kcp_client_free(client);
|
||||
kcp_session_stats_close(stats_logger);
|
||||
kcp_packet_debug_close(packet_logger);
|
||||
latencylog_close(latency_logger);
|
||||
return rc;
|
||||
}
|
||||
788
c/cmd/kcpping.c
Normal file
788
c/cmd/kcpping.c
Normal file
@@ -0,0 +1,788 @@
|
||||
#include "cli_parse.h"
|
||||
#include "peer_kcp_client.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct kcp_ping_message_node {
|
||||
struct kcp_ping_message_node *next;
|
||||
message_t msg;
|
||||
} kcp_ping_message_node_t;
|
||||
|
||||
typedef struct kcp_ping_receiver_ctx {
|
||||
kcp_client_t *client;
|
||||
pthread_mutex_t mu;
|
||||
kcp_ping_message_node_t *head;
|
||||
kcp_ping_message_node_t *tail;
|
||||
volatile int stop_requested;
|
||||
int closed;
|
||||
int rc;
|
||||
} kcp_ping_receiver_ctx_t;
|
||||
|
||||
typedef struct kcp_pending_ping {
|
||||
struct kcp_pending_ping *next;
|
||||
uint64_t seq;
|
||||
int64_t deadline_ns;
|
||||
} kcp_pending_ping_t;
|
||||
|
||||
typedef struct kcp_ping_tracker {
|
||||
kcp_pending_ping_t *pending;
|
||||
int pending_count;
|
||||
int sent;
|
||||
int duplicates;
|
||||
uint64_t max_seq_sent;
|
||||
int64_t *samples_ns;
|
||||
size_t sample_count;
|
||||
size_t sample_cap;
|
||||
} kcp_ping_tracker_t;
|
||||
|
||||
static volatile sig_atomic_t g_kcpping_stop = 0;
|
||||
|
||||
static void kcpping_on_signal(int signo) {
|
||||
(void) signo;
|
||||
g_kcpping_stop = 1;
|
||||
}
|
||||
|
||||
static void kcpping_usage(FILE *out) {
|
||||
fprintf(out, "usage: kcpping [-id pinger] [-server 127.0.0.1:9002] [-to peer] [-echo]\n");
|
||||
fprintf(out, " [-count 100] [-interval 100ms] [-size 64] [-timeout 3s]\n");
|
||||
fprintf(out, " [-bind-ip ip] [-bind-device dev] [-latency-log path]\n");
|
||||
}
|
||||
|
||||
static int kcp_ping_compare_i64(const void *left, const void *right) {
|
||||
const int64_t *a = (const int64_t *) left;
|
||||
const int64_t *b = (const int64_t *) right;
|
||||
if (*a < *b) {
|
||||
return -1;
|
||||
}
|
||||
if (*a > *b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double kcp_ping_sqrt(double value) {
|
||||
double x = value;
|
||||
int i;
|
||||
|
||||
if (value <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (x < 1.0) {
|
||||
x = 1.0;
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
x = 0.5 * (x + value / x);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static int kcp_ping_build_payload(uint64_t seq, int64_t ts_ns, int size, char **out_body, size_t *out_len) {
|
||||
cJSON *root = NULL;
|
||||
char *json = NULL;
|
||||
char *pad = NULL;
|
||||
size_t base_len;
|
||||
size_t pad_len;
|
||||
|
||||
*out_body = NULL;
|
||||
*out_len = 0;
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "seq", (double) seq);
|
||||
cJSON_AddNumberToObject(root, "ts_ns", (double) ts_ns);
|
||||
cJSON_AddStringToObject(root, "pad", "");
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (json == NULL) {
|
||||
return -1;
|
||||
}
|
||||
base_len = strlen(json);
|
||||
cJSON_free(json);
|
||||
if ((int) base_len > size) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pad_len = (size_t) size - base_len;
|
||||
pad = (char *) malloc(pad_len + 1U);
|
||||
if (pad == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memset(pad, 'A', pad_len);
|
||||
pad[pad_len] = '\0';
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
free(pad);
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "seq", (double) seq);
|
||||
cJSON_AddNumberToObject(root, "ts_ns", (double) ts_ns);
|
||||
cJSON_AddStringToObject(root, "pad", pad);
|
||||
free(pad);
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (json == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if ((int) strlen(json) != size) {
|
||||
cJSON_free(json);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out_body = json;
|
||||
*out_len = (size_t) size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_ping_parse_payload(const uint8_t *body, size_t body_len, uint64_t *seq, int64_t *ts_ns) {
|
||||
char *text;
|
||||
cJSON *root;
|
||||
const cJSON *seq_item;
|
||||
const cJSON *ts_item;
|
||||
|
||||
if (body == NULL || seq == NULL || ts_ns == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
text = (char *) malloc(body_len + 1U);
|
||||
if (text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(text, body, body_len);
|
||||
text[body_len] = '\0';
|
||||
root = cJSON_Parse(text);
|
||||
free(text);
|
||||
if (root == NULL) {
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
seq_item = cJSON_GetObjectItemCaseSensitive(root, "seq");
|
||||
ts_item = cJSON_GetObjectItemCaseSensitive(root, "ts_ns");
|
||||
if (!cJSON_IsNumber(seq_item) || !cJSON_IsNumber(ts_item) || seq_item->valuedouble <= 0 || ts_item->valuedouble <= 0) {
|
||||
cJSON_Delete(root);
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
*seq = (uint64_t) seq_item->valuedouble;
|
||||
*ts_ns = (int64_t) ts_item->valuedouble;
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kcp_ping_receiver_ctx_init(kcp_ping_receiver_ctx_t *ctx, kcp_client_t *client) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->client = client;
|
||||
pthread_mutex_init(&ctx->mu, NULL);
|
||||
}
|
||||
|
||||
static void kcp_ping_receiver_ctx_destroy(kcp_ping_receiver_ctx_t *ctx) {
|
||||
kcp_ping_message_node_t *node;
|
||||
kcp_ping_message_node_t *next;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
for (node = ctx->head; node != NULL; node = next) {
|
||||
next = node->next;
|
||||
protocol_message_clear(&node->msg);
|
||||
free(node);
|
||||
}
|
||||
pthread_mutex_destroy(&ctx->mu);
|
||||
}
|
||||
|
||||
static void *kcpping_receive_thread_main(void *arg) {
|
||||
kcp_ping_receiver_ctx_t *ctx = (kcp_ping_receiver_ctx_t *) arg;
|
||||
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
kcp_ping_message_node_t *node;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_client_receive(ctx->client, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
ctx->rc = ctx->stop_requested ? 0 : -1;
|
||||
ctx->closed = 1;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = (kcp_ping_message_node_t *) calloc(1, sizeof(*node));
|
||||
if (node == NULL) {
|
||||
protocol_message_clear(&msg);
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
ctx->rc = -1;
|
||||
ctx->closed = 1;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
return NULL;
|
||||
}
|
||||
node->msg = msg;
|
||||
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
if (ctx->tail == NULL) {
|
||||
ctx->head = node;
|
||||
} else {
|
||||
ctx->tail->next = node;
|
||||
}
|
||||
ctx->tail = node;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
}
|
||||
}
|
||||
|
||||
static int kcp_ping_receiver_pop(kcp_ping_receiver_ctx_t *ctx, message_t *out_msg) {
|
||||
kcp_ping_message_node_t *node;
|
||||
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
node = ctx->head;
|
||||
if (node != NULL) {
|
||||
ctx->head = node->next;
|
||||
if (ctx->head == NULL) {
|
||||
ctx->tail = NULL;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
*out_msg = node->msg;
|
||||
free(node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int kcp_ping_receiver_status(kcp_ping_receiver_ctx_t *ctx, int *closed, int *rc) {
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
*closed = ctx->closed;
|
||||
*rc = ctx->rc;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kcp_ping_tracker_init(kcp_ping_tracker_t *tracker) {
|
||||
memset(tracker, 0, sizeof(*tracker));
|
||||
}
|
||||
|
||||
static void kcp_ping_tracker_destroy(kcp_ping_tracker_t *tracker) {
|
||||
kcp_pending_ping_t *pending;
|
||||
kcp_pending_ping_t *next;
|
||||
|
||||
for (pending = tracker->pending; pending != NULL; pending = next) {
|
||||
next = pending->next;
|
||||
free(pending);
|
||||
}
|
||||
free(tracker->samples_ns);
|
||||
}
|
||||
|
||||
static int kcp_ping_tracker_mark_sent(kcp_ping_tracker_t *tracker, uint64_t seq, int64_t sent_at_ns, int64_t timeout_ns) {
|
||||
kcp_pending_ping_t *pending = (kcp_pending_ping_t *) calloc(1, sizeof(*pending));
|
||||
if (pending == NULL) {
|
||||
return -1;
|
||||
}
|
||||
pending->seq = seq;
|
||||
pending->deadline_ns = sent_at_ns + timeout_ns;
|
||||
pending->next = tracker->pending;
|
||||
tracker->pending = pending;
|
||||
tracker->pending_count++;
|
||||
tracker->sent++;
|
||||
tracker->max_seq_sent = seq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static kcp_pending_ping_t *kcp_ping_tracker_find_pending(kcp_ping_tracker_t *tracker, uint64_t seq, kcp_pending_ping_t **out_prev) {
|
||||
kcp_pending_ping_t *prev = NULL;
|
||||
kcp_pending_ping_t *cur;
|
||||
|
||||
for (cur = tracker->pending; cur != NULL; cur = cur->next) {
|
||||
if (cur->seq == seq) {
|
||||
if (out_prev != NULL) {
|
||||
*out_prev = prev;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
if (out_prev != NULL) {
|
||||
*out_prev = NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int kcp_ping_tracker_add_sample(kcp_ping_tracker_t *tracker, int64_t rtt_ns) {
|
||||
int64_t *next_samples;
|
||||
size_t next_cap;
|
||||
|
||||
if (tracker->sample_count == tracker->sample_cap) {
|
||||
next_cap = tracker->sample_cap == 0 ? 16U : tracker->sample_cap * 2U;
|
||||
next_samples = (int64_t *) realloc(tracker->samples_ns, next_cap * sizeof(*next_samples));
|
||||
if (next_samples == NULL) {
|
||||
return -1;
|
||||
}
|
||||
tracker->samples_ns = next_samples;
|
||||
tracker->sample_cap = next_cap;
|
||||
}
|
||||
tracker->samples_ns[tracker->sample_count++] = rtt_ns;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_ping_tracker_observe_reply(kcp_ping_tracker_t *tracker, uint64_t seq, int64_t sent_ts_ns, int64_t received_ts_ns, int *disposition, int64_t *rtt_ns) {
|
||||
kcp_pending_ping_t *prev = NULL;
|
||||
kcp_pending_ping_t *pending;
|
||||
|
||||
if (seq == 0 || seq > tracker->max_seq_sent) {
|
||||
*disposition = 2;
|
||||
*rtt_ns = 0;
|
||||
return 0;
|
||||
}
|
||||
pending = kcp_ping_tracker_find_pending(tracker, seq, &prev);
|
||||
if (pending == NULL) {
|
||||
tracker->duplicates++;
|
||||
*disposition = 1;
|
||||
*rtt_ns = 0;
|
||||
return 0;
|
||||
}
|
||||
if (prev == NULL) {
|
||||
tracker->pending = pending->next;
|
||||
} else {
|
||||
prev->next = pending->next;
|
||||
}
|
||||
tracker->pending_count--;
|
||||
free(pending);
|
||||
|
||||
*rtt_ns = received_ts_ns - sent_ts_ns;
|
||||
if (*rtt_ns < 0) {
|
||||
*rtt_ns = 0;
|
||||
}
|
||||
if (kcp_ping_tracker_add_sample(tracker, *rtt_ns) != 0) {
|
||||
return -1;
|
||||
}
|
||||
*disposition = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kcp_ping_tracker_expire(kcp_ping_tracker_t *tracker, int64_t now_ns, FILE *out) {
|
||||
kcp_pending_ping_t *prev = NULL;
|
||||
kcp_pending_ping_t *cur = tracker->pending;
|
||||
|
||||
while (cur != NULL) {
|
||||
if (cur->deadline_ns <= now_ns) {
|
||||
kcp_pending_ping_t *next = cur->next;
|
||||
fprintf(out, "seq=%" PRIu64 " timeout\n", cur->seq);
|
||||
if (prev == NULL) {
|
||||
tracker->pending = next;
|
||||
} else {
|
||||
prev->next = next;
|
||||
}
|
||||
free(cur);
|
||||
tracker->pending_count--;
|
||||
cur = next;
|
||||
continue;
|
||||
}
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t kcp_ping_percentile_ns(const int64_t *sorted, size_t count, double percentile) {
|
||||
size_t index;
|
||||
double raw_index;
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (percentile <= 0.0) {
|
||||
return sorted[0];
|
||||
}
|
||||
if (percentile >= 1.0) {
|
||||
return sorted[count - 1];
|
||||
}
|
||||
raw_index = percentile * (double) count;
|
||||
index = (size_t) raw_index;
|
||||
if ((double) index < raw_index) {
|
||||
index++;
|
||||
}
|
||||
if (index > 0) {
|
||||
index--;
|
||||
}
|
||||
if (index >= count) {
|
||||
index = count - 1;
|
||||
}
|
||||
return sorted[index];
|
||||
}
|
||||
|
||||
static void kcp_ping_print_summary(FILE *out, const char *target, const kcp_ping_tracker_t *tracker) {
|
||||
int received = (int) tracker->sample_count;
|
||||
double loss_pct = tracker->sent == 0 ? 0.0 : ((double) (tracker->sent - received) * 100.0 / (double) tracker->sent);
|
||||
|
||||
fprintf(out, "--- %s kcp ping statistics ---\n", target);
|
||||
fprintf(out, "%d packets transmitted, %d received, %d duplicates, %.2f%% packet loss\n", tracker->sent, received, tracker->duplicates, loss_pct);
|
||||
if (tracker->sample_count == 0) {
|
||||
fprintf(out, "rtt min/avg/max/p50/p95/p99 = n/a/n/a/n/a/n/a/n/a/n/a, stddev=n/a\n");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int64_t *sorted = (int64_t *) malloc(tracker->sample_count * sizeof(*sorted));
|
||||
size_t i;
|
||||
double sum = 0.0;
|
||||
double variance = 0.0;
|
||||
double avg;
|
||||
int64_t min_ns;
|
||||
int64_t max_ns;
|
||||
int64_t p50_ns;
|
||||
int64_t p95_ns;
|
||||
int64_t p99_ns;
|
||||
|
||||
if (sorted == NULL) {
|
||||
fprintf(out, "rtt summary unavailable: memory allocation failed\n");
|
||||
return;
|
||||
}
|
||||
memcpy(sorted, tracker->samples_ns, tracker->sample_count * sizeof(*sorted));
|
||||
qsort(sorted, tracker->sample_count, sizeof(*sorted), kcp_ping_compare_i64);
|
||||
for (i = 0; i < tracker->sample_count; ++i) {
|
||||
sum += (double) sorted[i];
|
||||
}
|
||||
avg = sum / (double) tracker->sample_count;
|
||||
for (i = 0; i < tracker->sample_count; ++i) {
|
||||
double delta = (double) sorted[i] - avg;
|
||||
variance += delta * delta;
|
||||
}
|
||||
variance /= (double) tracker->sample_count;
|
||||
|
||||
min_ns = sorted[0];
|
||||
max_ns = sorted[tracker->sample_count - 1];
|
||||
p50_ns = kcp_ping_percentile_ns(sorted, tracker->sample_count, 0.50);
|
||||
p95_ns = kcp_ping_percentile_ns(sorted, tracker->sample_count, 0.95);
|
||||
p99_ns = kcp_ping_percentile_ns(sorted, tracker->sample_count, 0.99);
|
||||
|
||||
fprintf(
|
||||
out,
|
||||
"rtt min/avg/max/p50/p95/p99 = %.2fms/%.2fms/%.2fms/%.2fms/%.2fms/%.2fms, stddev=%.2fms\n",
|
||||
(double) min_ns / 1000000.0,
|
||||
avg / 1000000.0,
|
||||
(double) max_ns / 1000000.0,
|
||||
(double) p50_ns / 1000000.0,
|
||||
(double) p95_ns / 1000000.0,
|
||||
(double) p99_ns / 1000000.0,
|
||||
kcp_ping_sqrt(variance) / 1000000.0
|
||||
);
|
||||
free(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
static int kcp_ping_expiry_poll_ms(int timeout_ms) {
|
||||
int interval = timeout_ms / 4;
|
||||
if (interval < 10) {
|
||||
return 10;
|
||||
}
|
||||
if (interval > 100) {
|
||||
return 100;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *peer_id = "pinger";
|
||||
const char *server_addr = "127.0.0.1:9002";
|
||||
const char *target_peer = "";
|
||||
const char *bind_ip = "";
|
||||
const char *bind_device = "";
|
||||
const char *latency_log_path = "";
|
||||
int echo_mode = 0;
|
||||
int count = 100;
|
||||
int interval_ms = 100;
|
||||
int size = 64;
|
||||
int timeout_ms = 3000;
|
||||
latency_logger_t *latency_logger = NULL;
|
||||
kcp_client_t *client = NULL;
|
||||
kcp_ping_receiver_ctx_t receiver_ctx;
|
||||
pthread_t receiver_thread;
|
||||
int receiver_ctx_initialized = 0;
|
||||
int receiver_thread_started = 0;
|
||||
kcp_ping_tracker_t tracker;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
kcp_ping_tracker_init(&tracker);
|
||||
memset(&receiver_ctx, 0, sizeof(receiver_ctx));
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-id", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -id requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
peer_id = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-server", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -server requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
server_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-to", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -to requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
target_peer = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-count", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -count requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
count = atoi(value);
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-interval", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -interval requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
if (omni_parse_duration_ms(value, interval_ms, &interval_ms) != 0) {
|
||||
fprintf(stderr, "kcpping: invalid -interval value %s\n", value);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-size", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -size requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
size = atoi(value);
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-timeout", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -timeout requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
if (omni_parse_duration_ms(value, timeout_ms, &timeout_ms) != 0) {
|
||||
fprintf(stderr, "kcpping: invalid -timeout value %s\n", value);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-ip", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -bind-ip requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_ip = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-device", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -bind-device requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_device = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcpping: flag -latency-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
latency_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_bool_flag(argv[i], "-echo", &echo_mode)) < 0) {
|
||||
fprintf(stderr, "kcpping: invalid -echo value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
kcpping_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "kcpping: unknown argument %s\n", argv[i]);
|
||||
kcpping_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (peer_id[0] == '\0' || server_addr[0] == '\0') {
|
||||
fprintf(stderr, "kcpping: flags -id and -server are required\n");
|
||||
return 1;
|
||||
}
|
||||
if (!echo_mode && target_peer[0] == '\0') {
|
||||
fprintf(stderr, "kcpping: flag -to is required unless -echo is set\n");
|
||||
return 1;
|
||||
}
|
||||
if (count < 0 || interval_ms <= 0 || size <= 0 || timeout_ms <= 0) {
|
||||
fprintf(stderr, "kcpping: invalid numeric flag value\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
signal(SIGINT, kcpping_on_signal);
|
||||
|
||||
if (latency_log_path[0] != '\0') {
|
||||
latency_logger = latencylog_open_jsonl(latency_log_path);
|
||||
if (latency_logger == NULL) {
|
||||
fprintf(stderr, "kcpping: open latency logger %s failed\n", latency_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
client = kcp_client_dial(server_addr, NULL, peer_id, bind_ip, bind_device, latency_logger, NULL, NULL, KCP_DEFAULT_STATS_INTERVAL_MS);
|
||||
if (client == NULL) {
|
||||
fprintf(stderr, "kcpping: dial kcp server %s failed\n", server_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (echo_mode) {
|
||||
while (!g_kcpping_stop) {
|
||||
message_t msg;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_client_receive(client, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
if (g_kcpping_stop) {
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "kcpping: receive failed in echo mode\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (msg.type == MSG_TYPE_TEXT) {
|
||||
char *text = (char *) malloc(msg.body_len + 1U);
|
||||
if (text == NULL) {
|
||||
protocol_message_clear(&msg);
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(text, msg.body, msg.body_len);
|
||||
text[msg.body_len] = '\0';
|
||||
if (kcp_client_send_text(client, msg.from, text) != 0) {
|
||||
free(text);
|
||||
protocol_message_clear(&msg);
|
||||
fprintf(stderr, "kcpping: echo send back to %s failed\n", msg.from);
|
||||
goto cleanup;
|
||||
}
|
||||
free(text);
|
||||
} else if (msg.type == MSG_TYPE_ERROR) {
|
||||
fprintf(stderr, "server error: %.*s\n", (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
||||
} else {
|
||||
fprintf(stderr, "unexpected message type %s from %s ignored\n", protocol_message_type_name(msg.type), msg.from);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
rc = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stdout, "KCP PING %s via %s (payload=%d bytes, KCP)\n", target_peer, server_addr, size);
|
||||
kcp_ping_receiver_ctx_init(&receiver_ctx, client);
|
||||
receiver_ctx_initialized = 1;
|
||||
if (pthread_create(&receiver_thread, NULL, kcpping_receive_thread_main, &receiver_ctx) != 0) {
|
||||
fprintf(stderr, "kcpping: create receive thread failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
receiver_thread_started = 1;
|
||||
|
||||
{
|
||||
uint64_t next_seq = 1;
|
||||
int stop_sending = 0;
|
||||
int64_t next_send_at_ns = omni_now_unix_nano();
|
||||
int poll_ms = kcp_ping_expiry_poll_ms(timeout_ms);
|
||||
int64_t timeout_ns = (int64_t) timeout_ms * 1000000LL;
|
||||
|
||||
while (!g_kcpping_stop || tracker.pending_count > 0 || !stop_sending) {
|
||||
int64_t now_ns = omni_now_unix_nano();
|
||||
message_t msg;
|
||||
int popped;
|
||||
int receiver_closed;
|
||||
int receiver_status_rc;
|
||||
|
||||
if (!stop_sending && now_ns >= next_send_at_ns) {
|
||||
char *payload = NULL;
|
||||
size_t payload_len = 0;
|
||||
|
||||
if (count > 0 && tracker.sent >= count) {
|
||||
stop_sending = 1;
|
||||
} else {
|
||||
if (kcp_ping_build_payload(next_seq, now_ns, size, &payload, &payload_len) != 0) {
|
||||
fprintf(stderr, "kcpping: build payload for seq=%" PRIu64 " failed\n", next_seq);
|
||||
free(payload);
|
||||
goto cleanup;
|
||||
}
|
||||
if (kcp_client_send_text(client, target_peer, payload) != 0) {
|
||||
fprintf(stderr, "kcpping: send ping seq=%" PRIu64 " failed\n", next_seq);
|
||||
free(payload);
|
||||
goto cleanup;
|
||||
}
|
||||
free(payload);
|
||||
if (kcp_ping_tracker_mark_sent(&tracker, next_seq, now_ns, timeout_ns) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
next_seq++;
|
||||
next_send_at_ns = now_ns + (int64_t) interval_ms * 1000000LL;
|
||||
if (count > 0 && tracker.sent >= count) {
|
||||
stop_sending = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kcp_ping_tracker_expire(&tracker, now_ns, stdout);
|
||||
|
||||
do {
|
||||
popped = kcp_ping_receiver_pop(&receiver_ctx, &msg);
|
||||
if (popped == 1) {
|
||||
if (msg.type == MSG_TYPE_TEXT) {
|
||||
uint64_t seq;
|
||||
int64_t sent_ts_ns;
|
||||
int disposition;
|
||||
int64_t rtt_ns;
|
||||
|
||||
if (kcp_ping_parse_payload(msg.body, msg.body_len, &seq, &sent_ts_ns) != 0) {
|
||||
fprintf(stderr, "ignore non-ping text message from %s\n", msg.from);
|
||||
} else if (kcp_ping_tracker_observe_reply(&tracker, seq, sent_ts_ns, omni_now_unix_nano(), &disposition, &rtt_ns) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
goto cleanup;
|
||||
} else if (disposition == 0) {
|
||||
fprintf(stdout, "seq=%" PRIu64 " rtt=%.2fms\n", seq, (double) rtt_ns / 1000000.0);
|
||||
} else if (disposition == 1) {
|
||||
fprintf(stderr, "seq=%" PRIu64 " duplicate or late reply ignored\n", seq);
|
||||
} else {
|
||||
fprintf(stderr, "seq=%" PRIu64 " unexpected reply ignored\n", seq);
|
||||
}
|
||||
} else if (msg.type == MSG_TYPE_ERROR) {
|
||||
fprintf(stderr, "server error: %.*s\n", (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
||||
} else {
|
||||
fprintf(stderr, "unexpected message type %s from %s ignored\n", protocol_message_type_name(msg.type), msg.from);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
} while (popped == 1);
|
||||
|
||||
kcp_ping_receiver_status(&receiver_ctx, &receiver_closed, &receiver_status_rc);
|
||||
if (receiver_closed && receiver_status_rc != 0) {
|
||||
fprintf(stderr, "kcpping: receive loop failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if ((g_kcpping_stop || stop_sending) && tracker.pending_count == 0) {
|
||||
break;
|
||||
}
|
||||
usleep((useconds_t) poll_ms * 1000U);
|
||||
}
|
||||
}
|
||||
|
||||
kcp_ping_print_summary(stdout, target_peer, &tracker);
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
receiver_ctx.stop_requested = 1;
|
||||
kcp_client_close(client);
|
||||
if (receiver_thread_started) {
|
||||
pthread_join(receiver_thread, NULL);
|
||||
kcp_ping_receiver_ctx_destroy(&receiver_ctx);
|
||||
} else if (receiver_ctx_initialized) {
|
||||
kcp_ping_receiver_ctx_destroy(&receiver_ctx);
|
||||
}
|
||||
kcp_client_free(client);
|
||||
latencylog_close(latency_logger);
|
||||
kcp_ping_tracker_destroy(&tracker);
|
||||
return rc;
|
||||
}
|
||||
223
c/cmd/kcpserver.c
Normal file
223
c/cmd/kcpserver.c
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "cli_parse.h"
|
||||
#include "server_kcp_hub.h"
|
||||
#include "server_udp_relay.h"
|
||||
|
||||
static void kcpserver_usage(FILE *out) {
|
||||
fprintf(out, "usage: kcpserver [-mode hub|relay] [-listen addr] [-bind-device dev]\n");
|
||||
fprintf(out, " [-latency-log path] [-kcp-ts-debug-log path]\n");
|
||||
fprintf(out, " [-kcp-session-stats-log path] [-kcp-session-stats-interval 100ms]\n");
|
||||
fprintf(out, " [-relay-remote addr] [-relay-listen addr] [-relay-peer addr]\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *mode = "hub";
|
||||
const char *listen_addr = ":9002";
|
||||
const char *bind_device = "";
|
||||
const char *latency_log_path = "";
|
||||
const char *packet_log_path = "";
|
||||
const char *stats_log_path = "";
|
||||
const char *stats_interval_raw = "";
|
||||
const char *relay_listen_alias = "";
|
||||
const char *relay_remote_addr = "";
|
||||
const char *relay_peer_alias = "";
|
||||
int stats_interval_ms = KCP_DEFAULT_STATS_INTERVAL_MS;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
latency_logger_t *latency_logger = NULL;
|
||||
kcp_packet_debug_logger_t *packet_logger = NULL;
|
||||
kcp_session_stats_logger_t *stats_logger = NULL;
|
||||
kcp_listener_t *listener = NULL;
|
||||
kcp_hub_t *hub = NULL;
|
||||
udp_relay_t *relay = NULL;
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-mode", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -mode requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
mode = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-listen", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -listen requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
listen_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-device", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -bind-device requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_device = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -latency-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
latency_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-ts-debug-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -kcp-ts-debug-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
packet_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-session-stats-log", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -kcp-session-stats-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
stats_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-session-stats-interval", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -kcp-session-stats-interval requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
stats_interval_raw = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-relay-listen", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -relay-listen requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
relay_listen_alias = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-relay-remote", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -relay-remote requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
relay_remote_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-relay-peer", &value)) < 0) {
|
||||
fprintf(stderr, "kcpserver: flag -relay-peer requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
relay_peer_alias = value;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
kcpserver_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "kcpserver: unknown argument %s\n", argv[i]);
|
||||
kcpserver_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (kcp_session_stats_parse_interval_ms(stats_interval_raw, &stats_interval_ms) != 0) {
|
||||
fprintf(stderr, "kcpserver: invalid -kcp-session-stats-interval value %s\n", stats_interval_raw);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (relay_peer_alias[0] != '\0' && relay_remote_addr[0] != '\0' && strcmp(relay_peer_alias, relay_remote_addr) != 0) {
|
||||
fprintf(stderr, "kcpserver: flags -relay-remote and -relay-peer must match when both are set\n");
|
||||
return 1;
|
||||
}
|
||||
if (relay_remote_addr[0] == '\0' && relay_peer_alias[0] != '\0') {
|
||||
relay_remote_addr = relay_peer_alias;
|
||||
}
|
||||
if (relay_peer_alias[0] != '\0') {
|
||||
fprintf(stderr, "warning: flag -relay-peer is deprecated; use -relay-remote instead\n");
|
||||
}
|
||||
if (relay_listen_alias[0] != '\0') {
|
||||
if (strcmp(mode, "relay") != 0) {
|
||||
fprintf(stderr, "kcpserver: flag -relay-listen may only be used in relay mode\n");
|
||||
return 1;
|
||||
}
|
||||
if (listen_addr[0] != '\0' && strcmp(listen_addr, ":9002") != 0 && strcmp(listen_addr, relay_listen_alias) != 0) {
|
||||
fprintf(stderr, "kcpserver: flags -listen and -relay-listen must match when both are set in relay mode\n");
|
||||
return 1;
|
||||
}
|
||||
listen_addr = relay_listen_alias;
|
||||
fprintf(stderr, "warning: flag -relay-listen is deprecated; use -listen with -mode=relay instead\n");
|
||||
}
|
||||
|
||||
if (strcmp(mode, "hub") == 0) {
|
||||
if (relay_remote_addr[0] != '\0') {
|
||||
fprintf(stderr, "kcpserver: flag -relay-remote may only be used in relay mode\n");
|
||||
return 1;
|
||||
}
|
||||
if (latency_log_path[0] != '\0') {
|
||||
latency_logger = latencylog_open_jsonl(latency_log_path);
|
||||
if (latency_logger == NULL) {
|
||||
fprintf(stderr, "kcpserver: open latency logger %s failed\n", latency_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (packet_log_path[0] != '\0') {
|
||||
packet_logger = kcp_packet_debug_open_jsonl(packet_log_path);
|
||||
if (packet_logger == NULL) {
|
||||
fprintf(stderr, "kcpserver: open packet debug logger %s failed\n", packet_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (stats_log_path[0] != '\0') {
|
||||
stats_logger = kcp_session_stats_open_jsonl(stats_log_path);
|
||||
if (stats_logger == NULL) {
|
||||
fprintf(stderr, "kcpserver: open session stats logger %s failed\n", stats_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
listener = kcp_listener_listen(listen_addr, bind_device, packet_logger, OMNI_NODE_ROLE_SERVER, "hub");
|
||||
if (listener == NULL) {
|
||||
fprintf(stderr, "kcpserver: listen on %s failed\n", listen_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
hub = kcp_hub_new(latency_logger, stats_logger, stats_interval_ms);
|
||||
if (hub == NULL) {
|
||||
fprintf(stderr, "kcpserver: create hub failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "kcp hub listening on %s\n", listen_addr);
|
||||
if (kcp_hub_serve_listener(hub, listener) != 0) {
|
||||
fprintf(stderr, "kcpserver: serve listener failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
rc = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (strcmp(mode, "relay") == 0) {
|
||||
if (bind_device[0] != '\0') {
|
||||
fprintf(stderr, "kcpserver: flag -bind-device is not supported in relay mode\n");
|
||||
return 1;
|
||||
}
|
||||
if (relay_remote_addr[0] == '\0') {
|
||||
fprintf(stderr, "kcpserver: flag -relay-remote is required in relay mode\n");
|
||||
return 1;
|
||||
}
|
||||
relay = udp_relay_open(listen_addr, relay_remote_addr);
|
||||
if (relay == NULL) {
|
||||
fprintf(stderr, "kcpserver: open udp relay %s -> %s failed\n", listen_addr, relay_remote_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "udp relay listening on %s and forwarding to %s\n", listen_addr, relay_remote_addr);
|
||||
if (udp_relay_serve(relay) != 0) {
|
||||
fprintf(stderr, "kcpserver: udp relay stopped with error\n");
|
||||
goto cleanup;
|
||||
}
|
||||
rc = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stderr, "kcpserver: unsupported -mode=%s; want hub or relay\n", mode);
|
||||
|
||||
cleanup:
|
||||
udp_relay_free(relay);
|
||||
kcp_hub_free(hub);
|
||||
kcp_listener_free(listener);
|
||||
kcp_session_stats_close(stats_logger);
|
||||
kcp_packet_debug_close(packet_logger);
|
||||
latencylog_close(latency_logger);
|
||||
return rc;
|
||||
}
|
||||
282
c/cmd/udppeer.c
Normal file
282
c/cmd/udppeer.c
Normal file
@@ -0,0 +1,282 @@
|
||||
#include "cli_parse.h"
|
||||
#include "interactive.h"
|
||||
#include "peer_udp_client.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct udppeer_receive_ctx {
|
||||
udp_client_t *client;
|
||||
const char *inbox_dir;
|
||||
volatile int stop_requested;
|
||||
int rc;
|
||||
} udppeer_receive_ctx_t;
|
||||
|
||||
static void udppeer_usage(FILE *out) {
|
||||
fprintf(out, "usage: udppeer [-id peer-a] [-server 127.0.0.1:9001] [-to peer] [-text msg | -file path]\n");
|
||||
fprintf(out, " [-bind-ip ip] [-inbox-dir dir] [-latency-log path] [-tx-ts-debug-log path]\n");
|
||||
fprintf(out, " [-interactive[=true|false]]\n");
|
||||
}
|
||||
|
||||
static void *udppeer_receive_thread_main(void *arg) {
|
||||
udppeer_receive_ctx_t *ctx = (udppeer_receive_ctx_t *) arg;
|
||||
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
char persisted_path[512];
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (udp_client_receive(ctx->client, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = ctx->stop_requested ? 0 : -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case MSG_TYPE_TEXT:
|
||||
if (udp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
||||
fprintf(stderr, "udppeer: persist text from %s to %s failed\n", msg.from, msg.to);
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = -1;
|
||||
return NULL;
|
||||
}
|
||||
fprintf(stderr, "received text from %s to %s and persisted to %s\n", msg.from, msg.to, persisted_path);
|
||||
break;
|
||||
case MSG_TYPE_FILE:
|
||||
if (udp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
||||
fprintf(stderr, "udppeer: persist file from %s to %s failed\n", msg.from, msg.to);
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = -1;
|
||||
return NULL;
|
||||
}
|
||||
fprintf(stderr, "received file from %s to %s: %s (%lu bytes) -> %s\n", msg.from, msg.to, msg.file_name, (unsigned long) msg.body_len, persisted_path);
|
||||
break;
|
||||
case MSG_TYPE_ERROR:
|
||||
fprintf(stderr, "received error from %s to %s: %.*s\n", msg.from, msg.to, (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "received unexpected message type %s from %s\n", protocol_message_type_name(msg.type), msg.from);
|
||||
protocol_message_clear(&msg);
|
||||
ctx->rc = -1;
|
||||
return NULL;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *peer_id = "peer-a";
|
||||
const char *server_addr = "127.0.0.1:9001";
|
||||
const char *target_peer = "";
|
||||
const char *text = "";
|
||||
const char *file_path = "";
|
||||
const char *bind_ip = "";
|
||||
const char *inbox_dir = "inbox";
|
||||
const char *latency_log_path = "";
|
||||
const char *tx_debug_log_path = "";
|
||||
int interactive = 1;
|
||||
latency_logger_t *latency_logger = NULL;
|
||||
tx_timestamp_debug_logger_t *debug_logger = NULL;
|
||||
udp_client_t *client = NULL;
|
||||
udppeer_receive_ctx_t receive_ctx;
|
||||
pthread_t receive_thread;
|
||||
int receive_thread_started = 0;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
memset(&receive_ctx, 0, sizeof(receive_ctx));
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-id", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -id requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
peer_id = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-server", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -server requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
server_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-to", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -to requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
target_peer = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-text", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -text requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
text = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-file", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -file requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
file_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-ip", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -bind-ip requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_ip = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-inbox-dir", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -inbox-dir requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
inbox_dir = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -latency-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
latency_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-tx-ts-debug-log", &value)) < 0) {
|
||||
fprintf(stderr, "udppeer: flag -tx-ts-debug-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
tx_debug_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_bool_flag(argv[i], "-interactive", &interactive)) < 0) {
|
||||
fprintf(stderr, "udppeer: invalid -interactive value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
udppeer_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "udppeer: unknown argument %s\n", argv[i]);
|
||||
udppeer_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (text[0] != '\0' && file_path[0] != '\0') {
|
||||
fprintf(stderr, "udppeer: only one of -text or -file may be specified\n");
|
||||
return 1;
|
||||
}
|
||||
if ((text[0] != '\0' || file_path[0] != '\0') && target_peer[0] == '\0') {
|
||||
fprintf(stderr, "udppeer: flag -to is required when sending text or file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (latency_log_path[0] != '\0') {
|
||||
latency_logger = latencylog_open_jsonl(latency_log_path);
|
||||
if (latency_logger == NULL) {
|
||||
fprintf(stderr, "udppeer: open latency logger %s failed\n", latency_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (tx_debug_log_path[0] != '\0') {
|
||||
debug_logger = tx_timestamp_debug_open_jsonl(tx_debug_log_path);
|
||||
if (debug_logger == NULL) {
|
||||
fprintf(stderr, "udppeer: open tx timestamp debug logger %s failed\n", tx_debug_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
client = udp_client_dial(server_addr, peer_id, bind_ip, latency_logger, debug_logger, tx_debug_log_path[0] != '\0');
|
||||
if (client == NULL) {
|
||||
fprintf(stderr, "udppeer: dial udp server %s failed\n", server_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "connected to %s as %s (UDP)\n", server_addr, udp_client_id(client));
|
||||
|
||||
receive_ctx.client = client;
|
||||
receive_ctx.inbox_dir = inbox_dir;
|
||||
if (pthread_create(&receive_thread, NULL, udppeer_receive_thread_main, &receive_ctx) != 0) {
|
||||
fprintf(stderr, "udppeer: create receive thread failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
receive_thread_started = 1;
|
||||
|
||||
if (target_peer[0] != '\0' && text[0] != '\0') {
|
||||
if (udp_client_send_text(client, target_peer, text) != 0) {
|
||||
fprintf(stderr, "udppeer: send text to %s failed\n", target_peer);
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "sent text to %s\n", target_peer);
|
||||
}
|
||||
if (target_peer[0] != '\0' && file_path[0] != '\0') {
|
||||
if (udp_client_send_file_path(client, target_peer, file_path) != 0) {
|
||||
fprintf(stderr, "udppeer: send file %s to %s failed\n", file_path, target_peer);
|
||||
goto cleanup;
|
||||
}
|
||||
fprintf(stderr, "sent file %s to %s\n", file_path, target_peer);
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
char line[2048];
|
||||
char prompt[128];
|
||||
|
||||
snprintf(prompt, sizeof(prompt), "%s> ", udp_client_id(client));
|
||||
interactive_print_help(stdout, "UDP");
|
||||
while (fputs(prompt, stdout) >= 0 && fflush(stdout) == 0 && fgets(line, sizeof(line), stdin) != NULL) {
|
||||
interactive_command_t command;
|
||||
char err[128];
|
||||
|
||||
omni_trim_newline(line);
|
||||
if (interactive_parse_command(line, &command, err, sizeof(err)) != 0) {
|
||||
if (strstr(err, "empty command") == NULL) {
|
||||
fprintf(stderr, "%s\n", err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_HELP) {
|
||||
interactive_print_help(stdout, "UDP");
|
||||
continue;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_QUIT) {
|
||||
break;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_TEXT) {
|
||||
if (udp_client_send_text(client, command.to, command.value) != 0) {
|
||||
fprintf(stderr, "udppeer: send text to %s failed\n", command.to);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "sent text to %s\n", command.to);
|
||||
continue;
|
||||
}
|
||||
if (command.type == INTERACTIVE_CMD_FILE) {
|
||||
if (udp_client_send_file_path(client, command.to, command.value) != 0) {
|
||||
fprintf(stderr, "udppeer: send file %s to %s failed\n", command.value, command.to);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "sent file %s to %s\n", command.value, command.to);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
receive_ctx.stop_requested = 1;
|
||||
udp_client_close(client);
|
||||
if (receive_thread_started) {
|
||||
pthread_join(receive_thread, NULL);
|
||||
if (rc == 0 && receive_ctx.rc != 0) {
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
udp_client_free(client);
|
||||
tx_timestamp_debug_close(debug_logger);
|
||||
latencylog_close(latency_logger);
|
||||
return rc;
|
||||
}
|
||||
780
c/cmd/udpping.c
Normal file
780
c/cmd/udpping.c
Normal file
@@ -0,0 +1,780 @@
|
||||
#include "cli_parse.h"
|
||||
#include "peer_udp_client.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct ping_message_node {
|
||||
struct ping_message_node *next;
|
||||
message_t msg;
|
||||
} ping_message_node_t;
|
||||
|
||||
typedef struct ping_receiver_ctx {
|
||||
udp_client_t *client;
|
||||
pthread_mutex_t mu;
|
||||
ping_message_node_t *head;
|
||||
ping_message_node_t *tail;
|
||||
volatile int stop_requested;
|
||||
int closed;
|
||||
int rc;
|
||||
} ping_receiver_ctx_t;
|
||||
|
||||
typedef struct pending_ping {
|
||||
struct pending_ping *next;
|
||||
uint64_t seq;
|
||||
int64_t deadline_ns;
|
||||
} pending_ping_t;
|
||||
|
||||
typedef struct ping_tracker {
|
||||
pending_ping_t *pending;
|
||||
int pending_count;
|
||||
int sent;
|
||||
int duplicates;
|
||||
uint64_t max_seq_sent;
|
||||
int64_t *samples_ns;
|
||||
size_t sample_count;
|
||||
size_t sample_cap;
|
||||
} ping_tracker_t;
|
||||
|
||||
static volatile sig_atomic_t g_udpping_stop = 0;
|
||||
|
||||
static void udpping_on_signal(int signo) {
|
||||
(void) signo;
|
||||
g_udpping_stop = 1;
|
||||
}
|
||||
|
||||
static void udpping_usage(FILE *out) {
|
||||
fprintf(out, "usage: udpping [-id pinger] [-server 127.0.0.1:9001] [-to peer] [-echo]\n");
|
||||
fprintf(out, " [-count 100] [-interval 100ms] [-size 64] [-timeout 3s]\n");
|
||||
fprintf(out, " [-bind-ip ip] [-latency-log path]\n");
|
||||
}
|
||||
|
||||
static int ping_compare_i64(const void *left, const void *right) {
|
||||
const int64_t *a = (const int64_t *) left;
|
||||
const int64_t *b = (const int64_t *) right;
|
||||
if (*a < *b) {
|
||||
return -1;
|
||||
}
|
||||
if (*a > *b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double ping_sqrt(double value) {
|
||||
double x = value;
|
||||
int i;
|
||||
|
||||
if (value <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (x < 1.0) {
|
||||
x = 1.0;
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
x = 0.5 * (x + value / x);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static int ping_build_payload(uint64_t seq, int64_t ts_ns, int size, char **out_body, size_t *out_len) {
|
||||
cJSON *root = NULL;
|
||||
char *json = NULL;
|
||||
char *pad = NULL;
|
||||
size_t base_len;
|
||||
size_t pad_len;
|
||||
|
||||
*out_body = NULL;
|
||||
*out_len = 0;
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "seq", (double) seq);
|
||||
cJSON_AddNumberToObject(root, "ts_ns", (double) ts_ns);
|
||||
cJSON_AddStringToObject(root, "pad", "");
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (json == NULL) {
|
||||
return -1;
|
||||
}
|
||||
base_len = strlen(json);
|
||||
cJSON_free(json);
|
||||
if ((int) base_len > size) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pad_len = (size_t) size - base_len;
|
||||
pad = (char *) malloc(pad_len + 1U);
|
||||
if (pad == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memset(pad, 'A', pad_len);
|
||||
pad[pad_len] = '\0';
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
free(pad);
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "seq", (double) seq);
|
||||
cJSON_AddNumberToObject(root, "ts_ns", (double) ts_ns);
|
||||
cJSON_AddStringToObject(root, "pad", pad);
|
||||
free(pad);
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (json == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if ((int) strlen(json) != size) {
|
||||
cJSON_free(json);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out_body = json;
|
||||
*out_len = (size_t) size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ping_parse_payload(const uint8_t *body, size_t body_len, uint64_t *seq, int64_t *ts_ns) {
|
||||
char *text;
|
||||
cJSON *root;
|
||||
const cJSON *seq_item;
|
||||
const cJSON *ts_item;
|
||||
|
||||
if (body == NULL || seq == NULL || ts_ns == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
text = (char *) malloc(body_len + 1U);
|
||||
if (text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(text, body, body_len);
|
||||
text[body_len] = '\0';
|
||||
root = cJSON_Parse(text);
|
||||
free(text);
|
||||
if (root == NULL) {
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
seq_item = cJSON_GetObjectItemCaseSensitive(root, "seq");
|
||||
ts_item = cJSON_GetObjectItemCaseSensitive(root, "ts_ns");
|
||||
if (!cJSON_IsNumber(seq_item) || !cJSON_IsNumber(ts_item) || seq_item->valuedouble <= 0 || ts_item->valuedouble <= 0) {
|
||||
cJSON_Delete(root);
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
*seq = (uint64_t) seq_item->valuedouble;
|
||||
*ts_ns = (int64_t) ts_item->valuedouble;
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ping_receiver_ctx_init(ping_receiver_ctx_t *ctx, udp_client_t *client) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->client = client;
|
||||
pthread_mutex_init(&ctx->mu, NULL);
|
||||
}
|
||||
|
||||
static void ping_receiver_ctx_destroy(ping_receiver_ctx_t *ctx) {
|
||||
ping_message_node_t *node;
|
||||
ping_message_node_t *next;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
for (node = ctx->head; node != NULL; node = next) {
|
||||
next = node->next;
|
||||
protocol_message_clear(&node->msg);
|
||||
free(node);
|
||||
}
|
||||
pthread_mutex_destroy(&ctx->mu);
|
||||
}
|
||||
|
||||
static void *udpping_receive_thread_main(void *arg) {
|
||||
ping_receiver_ctx_t *ctx = (ping_receiver_ctx_t *) arg;
|
||||
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
ping_message_node_t *node;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (udp_client_receive(ctx->client, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
ctx->rc = ctx->stop_requested ? 0 : -1;
|
||||
ctx->closed = 1;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = (ping_message_node_t *) calloc(1, sizeof(*node));
|
||||
if (node == NULL) {
|
||||
protocol_message_clear(&msg);
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
ctx->rc = -1;
|
||||
ctx->closed = 1;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
return NULL;
|
||||
}
|
||||
node->msg = msg;
|
||||
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
if (ctx->tail == NULL) {
|
||||
ctx->head = node;
|
||||
} else {
|
||||
ctx->tail->next = node;
|
||||
}
|
||||
ctx->tail = node;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
}
|
||||
}
|
||||
|
||||
static int ping_receiver_pop(ping_receiver_ctx_t *ctx, message_t *out_msg) {
|
||||
ping_message_node_t *node;
|
||||
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
node = ctx->head;
|
||||
if (node != NULL) {
|
||||
ctx->head = node->next;
|
||||
if (ctx->head == NULL) {
|
||||
ctx->tail = NULL;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
*out_msg = node->msg;
|
||||
free(node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ping_receiver_status(ping_receiver_ctx_t *ctx, int *closed, int *rc) {
|
||||
pthread_mutex_lock(&ctx->mu);
|
||||
*closed = ctx->closed;
|
||||
*rc = ctx->rc;
|
||||
pthread_mutex_unlock(&ctx->mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ping_tracker_init(ping_tracker_t *tracker) {
|
||||
memset(tracker, 0, sizeof(*tracker));
|
||||
}
|
||||
|
||||
static void ping_tracker_destroy(ping_tracker_t *tracker) {
|
||||
pending_ping_t *pending;
|
||||
pending_ping_t *next;
|
||||
|
||||
for (pending = tracker->pending; pending != NULL; pending = next) {
|
||||
next = pending->next;
|
||||
free(pending);
|
||||
}
|
||||
free(tracker->samples_ns);
|
||||
}
|
||||
|
||||
static int ping_tracker_mark_sent(ping_tracker_t *tracker, uint64_t seq, int64_t sent_at_ns, int64_t timeout_ns) {
|
||||
pending_ping_t *pending = (pending_ping_t *) calloc(1, sizeof(*pending));
|
||||
if (pending == NULL) {
|
||||
return -1;
|
||||
}
|
||||
pending->seq = seq;
|
||||
pending->deadline_ns = sent_at_ns + timeout_ns;
|
||||
pending->next = tracker->pending;
|
||||
tracker->pending = pending;
|
||||
tracker->pending_count++;
|
||||
tracker->sent++;
|
||||
tracker->max_seq_sent = seq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pending_ping_t *ping_tracker_find_pending(ping_tracker_t *tracker, uint64_t seq, pending_ping_t **out_prev) {
|
||||
pending_ping_t *prev = NULL;
|
||||
pending_ping_t *cur;
|
||||
|
||||
for (cur = tracker->pending; cur != NULL; cur = cur->next) {
|
||||
if (cur->seq == seq) {
|
||||
if (out_prev != NULL) {
|
||||
*out_prev = prev;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
if (out_prev != NULL) {
|
||||
*out_prev = NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ping_tracker_add_sample(ping_tracker_t *tracker, int64_t rtt_ns) {
|
||||
int64_t *next_samples;
|
||||
size_t next_cap;
|
||||
|
||||
if (tracker->sample_count == tracker->sample_cap) {
|
||||
next_cap = tracker->sample_cap == 0 ? 16U : tracker->sample_cap * 2U;
|
||||
next_samples = (int64_t *) realloc(tracker->samples_ns, next_cap * sizeof(*next_samples));
|
||||
if (next_samples == NULL) {
|
||||
return -1;
|
||||
}
|
||||
tracker->samples_ns = next_samples;
|
||||
tracker->sample_cap = next_cap;
|
||||
}
|
||||
tracker->samples_ns[tracker->sample_count++] = rtt_ns;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ping_tracker_observe_reply(ping_tracker_t *tracker, uint64_t seq, int64_t sent_ts_ns, int64_t received_ts_ns, int *disposition, int64_t *rtt_ns) {
|
||||
pending_ping_t *prev = NULL;
|
||||
pending_ping_t *pending;
|
||||
|
||||
if (seq == 0 || seq > tracker->max_seq_sent) {
|
||||
*disposition = 2;
|
||||
*rtt_ns = 0;
|
||||
return 0;
|
||||
}
|
||||
pending = ping_tracker_find_pending(tracker, seq, &prev);
|
||||
if (pending == NULL) {
|
||||
tracker->duplicates++;
|
||||
*disposition = 1;
|
||||
*rtt_ns = 0;
|
||||
return 0;
|
||||
}
|
||||
if (prev == NULL) {
|
||||
tracker->pending = pending->next;
|
||||
} else {
|
||||
prev->next = pending->next;
|
||||
}
|
||||
tracker->pending_count--;
|
||||
free(pending);
|
||||
|
||||
*rtt_ns = received_ts_ns - sent_ts_ns;
|
||||
if (*rtt_ns < 0) {
|
||||
*rtt_ns = 0;
|
||||
}
|
||||
if (ping_tracker_add_sample(tracker, *rtt_ns) != 0) {
|
||||
return -1;
|
||||
}
|
||||
*disposition = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ping_tracker_expire(ping_tracker_t *tracker, int64_t now_ns, FILE *out) {
|
||||
pending_ping_t *prev = NULL;
|
||||
pending_ping_t *cur = tracker->pending;
|
||||
|
||||
while (cur != NULL) {
|
||||
if (cur->deadline_ns <= now_ns) {
|
||||
pending_ping_t *next = cur->next;
|
||||
fprintf(out, "seq=%" PRIu64 " timeout\n", cur->seq);
|
||||
if (prev == NULL) {
|
||||
tracker->pending = next;
|
||||
} else {
|
||||
prev->next = next;
|
||||
}
|
||||
free(cur);
|
||||
tracker->pending_count--;
|
||||
cur = next;
|
||||
continue;
|
||||
}
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t ping_percentile_ns(const int64_t *sorted, size_t count, double percentile) {
|
||||
size_t index;
|
||||
double raw_index;
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (percentile <= 0.0) {
|
||||
return sorted[0];
|
||||
}
|
||||
if (percentile >= 1.0) {
|
||||
return sorted[count - 1];
|
||||
}
|
||||
raw_index = percentile * (double) count;
|
||||
index = (size_t) raw_index;
|
||||
if ((double) index < raw_index) {
|
||||
index++;
|
||||
}
|
||||
if (index > 0) {
|
||||
index--;
|
||||
}
|
||||
if (index >= count) {
|
||||
index = count - 1;
|
||||
}
|
||||
return sorted[index];
|
||||
}
|
||||
|
||||
static void ping_print_summary(FILE *out, const char *target, const ping_tracker_t *tracker) {
|
||||
int received = (int) tracker->sample_count;
|
||||
double loss_pct = tracker->sent == 0 ? 0.0 : ((double) (tracker->sent - received) * 100.0 / (double) tracker->sent);
|
||||
|
||||
fprintf(out, "--- %s udp ping statistics ---\n", target);
|
||||
fprintf(out, "%d packets transmitted, %d received, %d duplicates, %.2f%% packet loss\n", tracker->sent, received, tracker->duplicates, loss_pct);
|
||||
if (tracker->sample_count == 0) {
|
||||
fprintf(out, "rtt min/avg/max/p50/p95/p99 = n/a/n/a/n/a/n/a/n/a/n/a, stddev=n/a\n");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int64_t *sorted = (int64_t *) malloc(tracker->sample_count * sizeof(*sorted));
|
||||
size_t i;
|
||||
double sum = 0.0;
|
||||
double variance = 0.0;
|
||||
double avg;
|
||||
int64_t min_ns;
|
||||
int64_t max_ns;
|
||||
int64_t p50_ns;
|
||||
int64_t p95_ns;
|
||||
int64_t p99_ns;
|
||||
|
||||
if (sorted == NULL) {
|
||||
fprintf(out, "rtt summary unavailable: memory allocation failed\n");
|
||||
return;
|
||||
}
|
||||
memcpy(sorted, tracker->samples_ns, tracker->sample_count * sizeof(*sorted));
|
||||
qsort(sorted, tracker->sample_count, sizeof(*sorted), ping_compare_i64);
|
||||
for (i = 0; i < tracker->sample_count; ++i) {
|
||||
sum += (double) sorted[i];
|
||||
}
|
||||
avg = sum / (double) tracker->sample_count;
|
||||
for (i = 0; i < tracker->sample_count; ++i) {
|
||||
double delta = (double) sorted[i] - avg;
|
||||
variance += delta * delta;
|
||||
}
|
||||
variance /= (double) tracker->sample_count;
|
||||
|
||||
min_ns = sorted[0];
|
||||
max_ns = sorted[tracker->sample_count - 1];
|
||||
p50_ns = ping_percentile_ns(sorted, tracker->sample_count, 0.50);
|
||||
p95_ns = ping_percentile_ns(sorted, tracker->sample_count, 0.95);
|
||||
p99_ns = ping_percentile_ns(sorted, tracker->sample_count, 0.99);
|
||||
|
||||
fprintf(
|
||||
out,
|
||||
"rtt min/avg/max/p50/p95/p99 = %.2fms/%.2fms/%.2fms/%.2fms/%.2fms/%.2fms, stddev=%.2fms\n",
|
||||
(double) min_ns / 1000000.0,
|
||||
avg / 1000000.0,
|
||||
(double) max_ns / 1000000.0,
|
||||
(double) p50_ns / 1000000.0,
|
||||
(double) p95_ns / 1000000.0,
|
||||
(double) p99_ns / 1000000.0,
|
||||
ping_sqrt(variance) / 1000000.0
|
||||
);
|
||||
free(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
static int ping_expiry_poll_ms(int timeout_ms) {
|
||||
int interval = timeout_ms / 4;
|
||||
if (interval < 10) {
|
||||
return 10;
|
||||
}
|
||||
if (interval > 100) {
|
||||
return 100;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *peer_id = "pinger";
|
||||
const char *server_addr = "127.0.0.1:9001";
|
||||
const char *target_peer = "";
|
||||
const char *bind_ip = "";
|
||||
const char *latency_log_path = "";
|
||||
int echo_mode = 0;
|
||||
int count = 100;
|
||||
int interval_ms = 100;
|
||||
int size = 64;
|
||||
int timeout_ms = 3000;
|
||||
latency_logger_t *latency_logger = NULL;
|
||||
udp_client_t *client = NULL;
|
||||
ping_receiver_ctx_t receiver_ctx;
|
||||
pthread_t receiver_thread;
|
||||
int receiver_ctx_initialized = 0;
|
||||
int receiver_thread_started = 0;
|
||||
ping_tracker_t tracker;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
ping_tracker_init(&tracker);
|
||||
memset(&receiver_ctx, 0, sizeof(receiver_ctx));
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-id", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -id requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
peer_id = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-server", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -server requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
server_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-to", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -to requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
target_peer = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-count", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -count requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
count = atoi(value);
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-interval", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -interval requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
if (omni_parse_duration_ms(value, interval_ms, &interval_ms) != 0) {
|
||||
fprintf(stderr, "udpping: invalid -interval value %s\n", value);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-size", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -size requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
size = atoi(value);
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-timeout", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -timeout requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
if (omni_parse_duration_ms(value, timeout_ms, &timeout_ms) != 0) {
|
||||
fprintf(stderr, "udpping: invalid -timeout value %s\n", value);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-ip", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -bind-ip requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
bind_ip = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
||||
fprintf(stderr, "udpping: flag -latency-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
latency_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_bool_flag(argv[i], "-echo", &echo_mode)) < 0) {
|
||||
fprintf(stderr, "udpping: invalid -echo value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
udpping_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "udpping: unknown argument %s\n", argv[i]);
|
||||
udpping_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (peer_id[0] == '\0' || server_addr[0] == '\0') {
|
||||
fprintf(stderr, "udpping: flags -id and -server are required\n");
|
||||
return 1;
|
||||
}
|
||||
if (!echo_mode && target_peer[0] == '\0') {
|
||||
fprintf(stderr, "udpping: flag -to is required unless -echo is set\n");
|
||||
return 1;
|
||||
}
|
||||
if (count < 0 || interval_ms <= 0 || size <= 0 || timeout_ms <= 0) {
|
||||
fprintf(stderr, "udpping: invalid numeric flag value\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
signal(SIGINT, udpping_on_signal);
|
||||
|
||||
if (latency_log_path[0] != '\0') {
|
||||
latency_logger = latencylog_open_jsonl(latency_log_path);
|
||||
if (latency_logger == NULL) {
|
||||
fprintf(stderr, "udpping: open latency logger %s failed\n", latency_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
client = udp_client_dial(server_addr, peer_id, bind_ip, latency_logger, NULL, 0);
|
||||
if (client == NULL) {
|
||||
fprintf(stderr, "udpping: dial udp server %s failed\n", server_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (echo_mode) {
|
||||
while (!g_udpping_stop) {
|
||||
message_t msg;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (udp_client_receive(client, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
if (g_udpping_stop) {
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "udpping: receive failed in echo mode\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (msg.type == MSG_TYPE_TEXT) {
|
||||
char *text = (char *) malloc(msg.body_len + 1U);
|
||||
if (text == NULL) {
|
||||
protocol_message_clear(&msg);
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(text, msg.body, msg.body_len);
|
||||
text[msg.body_len] = '\0';
|
||||
if (udp_client_send_text(client, msg.from, text) != 0) {
|
||||
free(text);
|
||||
protocol_message_clear(&msg);
|
||||
fprintf(stderr, "udpping: echo send back to %s failed\n", msg.from);
|
||||
goto cleanup;
|
||||
}
|
||||
free(text);
|
||||
} else if (msg.type == MSG_TYPE_ERROR) {
|
||||
fprintf(stderr, "server error: %.*s\n", (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
||||
} else {
|
||||
fprintf(stderr, "unexpected message type %s from %s ignored\n", protocol_message_type_name(msg.type), msg.from);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
rc = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stdout, "UDP PING %s via %s (payload=%d bytes, UDP)\n", target_peer, server_addr, size);
|
||||
ping_receiver_ctx_init(&receiver_ctx, client);
|
||||
receiver_ctx_initialized = 1;
|
||||
if (pthread_create(&receiver_thread, NULL, udpping_receive_thread_main, &receiver_ctx) != 0) {
|
||||
fprintf(stderr, "udpping: create receive thread failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
receiver_thread_started = 1;
|
||||
|
||||
{
|
||||
uint64_t next_seq = 1;
|
||||
int stop_sending = 0;
|
||||
int64_t next_send_at_ns = omni_now_unix_nano();
|
||||
int poll_ms = ping_expiry_poll_ms(timeout_ms);
|
||||
int64_t timeout_ns = (int64_t) timeout_ms * 1000000LL;
|
||||
|
||||
while (!g_udpping_stop || tracker.pending_count > 0 || !stop_sending) {
|
||||
int64_t now_ns = omni_now_unix_nano();
|
||||
message_t msg;
|
||||
int popped;
|
||||
int receiver_closed;
|
||||
int receiver_status_rc;
|
||||
|
||||
if (!stop_sending && now_ns >= next_send_at_ns) {
|
||||
char *payload = NULL;
|
||||
size_t payload_len = 0;
|
||||
|
||||
if (count > 0 && tracker.sent >= count) {
|
||||
stop_sending = 1;
|
||||
} else {
|
||||
if (ping_build_payload(next_seq, now_ns, size, &payload, &payload_len) != 0) {
|
||||
fprintf(stderr, "udpping: build payload for seq=%" PRIu64 " failed\n", next_seq);
|
||||
free(payload);
|
||||
goto cleanup;
|
||||
}
|
||||
if (udp_client_send_text(client, target_peer, payload) != 0) {
|
||||
fprintf(stderr, "udpping: send ping seq=%" PRIu64 " failed\n", next_seq);
|
||||
free(payload);
|
||||
goto cleanup;
|
||||
}
|
||||
free(payload);
|
||||
if (ping_tracker_mark_sent(&tracker, next_seq, now_ns, timeout_ns) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
next_seq++;
|
||||
next_send_at_ns = now_ns + (int64_t) interval_ms * 1000000LL;
|
||||
if (count > 0 && tracker.sent >= count) {
|
||||
stop_sending = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ping_tracker_expire(&tracker, now_ns, stdout);
|
||||
|
||||
do {
|
||||
popped = ping_receiver_pop(&receiver_ctx, &msg);
|
||||
if (popped == 1) {
|
||||
if (msg.type == MSG_TYPE_TEXT) {
|
||||
uint64_t seq;
|
||||
int64_t sent_ts_ns;
|
||||
int disposition;
|
||||
int64_t rtt_ns;
|
||||
|
||||
if (ping_parse_payload(msg.body, msg.body_len, &seq, &sent_ts_ns) != 0) {
|
||||
fprintf(stderr, "ignore non-ping text message from %s\n", msg.from);
|
||||
} else if (ping_tracker_observe_reply(&tracker, seq, sent_ts_ns, omni_now_unix_nano(), &disposition, &rtt_ns) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
goto cleanup;
|
||||
} else if (disposition == 0) {
|
||||
fprintf(stdout, "seq=%" PRIu64 " rtt=%.2fms\n", seq, (double) rtt_ns / 1000000.0);
|
||||
} else if (disposition == 1) {
|
||||
fprintf(stderr, "seq=%" PRIu64 " duplicate or late reply ignored\n", seq);
|
||||
} else {
|
||||
fprintf(stderr, "seq=%" PRIu64 " unexpected reply ignored\n", seq);
|
||||
}
|
||||
} else if (msg.type == MSG_TYPE_ERROR) {
|
||||
fprintf(stderr, "server error: %.*s\n", (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
||||
} else {
|
||||
fprintf(stderr, "unexpected message type %s from %s ignored\n", protocol_message_type_name(msg.type), msg.from);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
} while (popped == 1);
|
||||
|
||||
ping_receiver_status(&receiver_ctx, &receiver_closed, &receiver_status_rc);
|
||||
if (receiver_closed && receiver_status_rc != 0) {
|
||||
fprintf(stderr, "udpping: receive loop failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if ((g_udpping_stop || stop_sending) && tracker.pending_count == 0) {
|
||||
break;
|
||||
}
|
||||
usleep((useconds_t) poll_ms * 1000U);
|
||||
}
|
||||
}
|
||||
|
||||
ping_print_summary(stdout, target_peer, &tracker);
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
receiver_ctx.stop_requested = 1;
|
||||
udp_client_close(client);
|
||||
if (receiver_thread_started) {
|
||||
pthread_join(receiver_thread, NULL);
|
||||
ping_receiver_ctx_destroy(&receiver_ctx);
|
||||
} else if (receiver_ctx_initialized) {
|
||||
ping_receiver_ctx_destroy(&receiver_ctx);
|
||||
}
|
||||
udp_client_free(client);
|
||||
latencylog_close(latency_logger);
|
||||
ping_tracker_destroy(&tracker);
|
||||
return rc;
|
||||
}
|
||||
59
c/cmd/udprelay.c
Normal file
59
c/cmd/udprelay.c
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "cli_parse.h"
|
||||
#include "server_udp_relay.h"
|
||||
|
||||
static void udprelay_usage(FILE *out) {
|
||||
fprintf(out, "usage: udprelay [-listen addr] [-upstream addr]\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *listen_addr = ":9003";
|
||||
const char *upstream_addr = "127.0.0.1:9002";
|
||||
udp_relay_t *relay = NULL;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-listen", &value)) < 0) {
|
||||
fprintf(stderr, "udprelay: flag -listen requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
listen_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-upstream", &value)) < 0) {
|
||||
fprintf(stderr, "udprelay: flag -upstream requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
upstream_addr = value;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
udprelay_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "udprelay: unknown argument %s\n", argv[i]);
|
||||
udprelay_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
relay = udp_relay_open(listen_addr, upstream_addr);
|
||||
if (relay == NULL) {
|
||||
fprintf(stderr, "udprelay: open relay %s -> %s failed\n", listen_addr, upstream_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stderr, "udp relay listening on %s, upstream %s\n", listen_addr, upstream_addr);
|
||||
if (udp_relay_serve(relay) != 0) {
|
||||
fprintf(stderr, "udprelay: relay serve failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
udp_relay_free(relay);
|
||||
return rc;
|
||||
}
|
||||
88
c/cmd/udpserver.c
Normal file
88
c/cmd/udpserver.c
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "cli_parse.h"
|
||||
#include "server_udp_hub.h"
|
||||
|
||||
static void udpserver_usage(FILE *out) {
|
||||
fprintf(out, "usage: udpserver [-listen addr] [-latency-log path] [-tx-ts-debug-log path]\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *listen_addr = ":9001";
|
||||
const char *latency_log_path = "";
|
||||
const char *tx_debug_log_path = "";
|
||||
latency_logger_t *latency_logger = NULL;
|
||||
tx_timestamp_debug_logger_t *debug_logger = NULL;
|
||||
udp_hub_t *hub = NULL;
|
||||
int enable_timestamping = 0;
|
||||
int i;
|
||||
int rc = 1;
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const char *value = NULL;
|
||||
int handled;
|
||||
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-listen", &value)) < 0) {
|
||||
fprintf(stderr, "udpserver: flag -listen requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
listen_addr = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
||||
fprintf(stderr, "udpserver: flag -latency-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
latency_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-tx-ts-debug-log", &value)) < 0) {
|
||||
fprintf(stderr, "udpserver: flag -tx-ts-debug-log requires a value\n");
|
||||
return 1;
|
||||
} else if (handled) {
|
||||
tx_debug_log_path = value;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
udpserver_usage(stdout);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "udpserver: unknown argument %s\n", argv[i]);
|
||||
udpserver_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (latency_log_path[0] != '\0') {
|
||||
latency_logger = latencylog_open_jsonl(latency_log_path);
|
||||
if (latency_logger == NULL) {
|
||||
fprintf(stderr, "udpserver: open latency logger %s failed\n", latency_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (tx_debug_log_path[0] != '\0') {
|
||||
debug_logger = tx_timestamp_debug_open_jsonl(tx_debug_log_path);
|
||||
if (debug_logger == NULL) {
|
||||
fprintf(stderr, "udpserver: open tx timestamp debug logger %s failed\n", tx_debug_log_path);
|
||||
goto cleanup;
|
||||
}
|
||||
enable_timestamping = 1;
|
||||
}
|
||||
|
||||
hub = udp_hub_open(listen_addr, latency_logger, debug_logger, enable_timestamping);
|
||||
if (hub == NULL) {
|
||||
fprintf(stderr, "udpserver: listen on %s failed\n", listen_addr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stderr, "udp server listening on %s\n", listen_addr);
|
||||
if (udp_hub_serve(hub) != 0) {
|
||||
fprintf(stderr, "udpserver: serve failed\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
udp_hub_free(hub);
|
||||
tx_timestamp_debug_close(debug_logger);
|
||||
latencylog_close(latency_logger);
|
||||
return rc;
|
||||
}
|
||||
57
c/include/cli_parse.h
Normal file
57
c/include/cli_parse.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef OMNI_CLI_PARSE_H
|
||||
#define OMNI_CLI_PARSE_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
static int cli_parse_bool_text(const char *raw, int *out_value) {
|
||||
if (raw == NULL || out_value == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (strcmp(raw, "1") == 0 || strcmp(raw, "true") == 0 || strcmp(raw, "yes") == 0 || strcmp(raw, "on") == 0) {
|
||||
*out_value = 1;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(raw, "0") == 0 || strcmp(raw, "false") == 0 || strcmp(raw, "no") == 0 || strcmp(raw, "off") == 0) {
|
||||
*out_value = 0;
|
||||
return 0;
|
||||
}
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cli_parse_value_flag(int argc, char **argv, int *index, const char *arg, const char *flag, const char **out_value) {
|
||||
size_t flag_len = strlen(flag);
|
||||
|
||||
if (strcmp(arg, flag) == 0) {
|
||||
if (*index + 1 >= argc) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out_value = argv[++(*index)];
|
||||
return 1;
|
||||
}
|
||||
if (strncmp(arg, flag, flag_len) == 0 && arg[flag_len] == '=') {
|
||||
*out_value = arg + flag_len + 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cli_parse_bool_flag(const char *arg, const char *flag, int *out_value) {
|
||||
size_t flag_len = strlen(flag);
|
||||
|
||||
if (strcmp(arg, flag) == 0) {
|
||||
*out_value = 1;
|
||||
return 1;
|
||||
}
|
||||
if (strncmp(arg, flag, flag_len) == 0 && arg[flag_len] == '=') {
|
||||
if (cli_parse_bool_text(arg + flag_len + 1, out_value) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
30
c/include/interactive.h
Normal file
30
c/include/interactive.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef OMNI_INTERACTIVE_H
|
||||
#define OMNI_INTERACTIVE_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum interactive_command_type {
|
||||
INTERACTIVE_CMD_HELP = 0,
|
||||
INTERACTIVE_CMD_QUIT = 1,
|
||||
INTERACTIVE_CMD_TEXT = 2,
|
||||
INTERACTIVE_CMD_FILE = 3
|
||||
} interactive_command_type_t;
|
||||
|
||||
typedef struct interactive_command {
|
||||
interactive_command_type_t type;
|
||||
char to[OMNI_MAX_PEER_ID];
|
||||
char value[1024];
|
||||
} interactive_command_t;
|
||||
|
||||
int interactive_parse_command(const char *line, interactive_command_t *command, char *err, size_t err_len);
|
||||
void interactive_print_help(FILE *out, const char *transport_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
49
c/include/kcp_packet_debug.h
Normal file
49
c/include/kcp_packet_debug.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef OMNI_KCP_PACKET_DEBUG_H
|
||||
#define OMNI_KCP_PACKET_DEBUG_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct kcp_packet_debug_segment {
|
||||
uint8_t cmd;
|
||||
uint32_t sn;
|
||||
uint32_t una;
|
||||
uint8_t frg;
|
||||
uint16_t wnd;
|
||||
uint32_t len;
|
||||
} kcp_packet_debug_segment_t;
|
||||
|
||||
typedef struct kcp_packet_debug_record {
|
||||
char event[OMNI_MAX_EVENT_NAME];
|
||||
char node_role[OMNI_MAX_NODE_ROLE];
|
||||
char node_id[OMNI_MAX_PEER_ID];
|
||||
char local_addr[OMNI_MAX_ADDR_TEXT];
|
||||
char remote_addr[OMNI_MAX_ADDR_TEXT];
|
||||
int packet_bytes;
|
||||
int has_udp_tx_id;
|
||||
uint32_t udp_tx_id;
|
||||
int has_kcp_conv;
|
||||
uint32_t kcp_conv;
|
||||
int64_t ts_unix_nano;
|
||||
kcp_packet_debug_segment_t *segments;
|
||||
size_t segment_count;
|
||||
} kcp_packet_debug_record_t;
|
||||
|
||||
typedef struct kcp_packet_debug_logger {
|
||||
omni_file_logger_t file_logger;
|
||||
int enabled;
|
||||
} kcp_packet_debug_logger_t;
|
||||
|
||||
kcp_packet_debug_logger_t *kcp_packet_debug_open_jsonl(const char *path);
|
||||
void kcp_packet_debug_close(kcp_packet_debug_logger_t *logger);
|
||||
int kcp_packet_debug_log(kcp_packet_debug_logger_t *logger, const kcp_packet_debug_record_t *record);
|
||||
void kcp_packet_debug_record_clear(kcp_packet_debug_record_t *record);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
78
c/include/kcp_session_stats.h
Normal file
78
c/include/kcp_session_stats.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef OMNI_KCP_SESSION_STATS_H
|
||||
#define OMNI_KCP_SESSION_STATS_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define KCP_SESSION_STATS_RECORD_SESSION_SAMPLE "session_sample"
|
||||
#define KCP_SESSION_STATS_RECORD_PROCESS_SAMPLE "process_snmp_sample"
|
||||
|
||||
typedef struct kcp_session_stats_record {
|
||||
char record_type[32];
|
||||
char node_role[OMNI_MAX_NODE_ROLE];
|
||||
char node_id[OMNI_MAX_PEER_ID];
|
||||
char local_addr[OMNI_MAX_ADDR_TEXT];
|
||||
char remote_addr[OMNI_MAX_ADDR_TEXT];
|
||||
int has_conv;
|
||||
uint32_t conv;
|
||||
int64_t ts_unix_nano;
|
||||
char sample_reason[32];
|
||||
int has_rto_ms;
|
||||
uint32_t rto_ms;
|
||||
int has_srtt_ms;
|
||||
int32_t srtt_ms;
|
||||
int has_srttvar_ms;
|
||||
int32_t srttvar_ms;
|
||||
int has_bytes_sent;
|
||||
uint64_t bytes_sent;
|
||||
int has_bytes_received;
|
||||
uint64_t bytes_received;
|
||||
int has_in_pkts;
|
||||
uint64_t in_pkts;
|
||||
int has_out_pkts;
|
||||
uint64_t out_pkts;
|
||||
int has_in_segs;
|
||||
uint64_t in_segs;
|
||||
int has_out_segs;
|
||||
uint64_t out_segs;
|
||||
int has_retrans_segs;
|
||||
uint64_t retrans_segs;
|
||||
int has_fast_retrans_segs;
|
||||
uint64_t fast_retrans_segs;
|
||||
int has_early_retrans_segs;
|
||||
uint64_t early_retrans_segs;
|
||||
int has_lost_segs;
|
||||
uint64_t lost_segs;
|
||||
int has_repeat_segs;
|
||||
uint64_t repeat_segs;
|
||||
int has_in_errs;
|
||||
uint64_t in_errs;
|
||||
int has_kcp_in_errs;
|
||||
uint64_t kcp_in_errs;
|
||||
int has_ring_buffer_snd_queue;
|
||||
uint64_t ring_buffer_snd_queue;
|
||||
int has_ring_buffer_rcv_queue;
|
||||
uint64_t ring_buffer_rcv_queue;
|
||||
int has_ring_buffer_snd_buffer;
|
||||
uint64_t ring_buffer_snd_buffer;
|
||||
int has_curr_estab;
|
||||
uint64_t curr_estab;
|
||||
} kcp_session_stats_record_t;
|
||||
|
||||
typedef struct kcp_session_stats_logger {
|
||||
omni_file_logger_t file_logger;
|
||||
int enabled;
|
||||
} kcp_session_stats_logger_t;
|
||||
|
||||
kcp_session_stats_logger_t *kcp_session_stats_open_jsonl(const char *path);
|
||||
void kcp_session_stats_close(kcp_session_stats_logger_t *logger);
|
||||
int kcp_session_stats_log(kcp_session_stats_logger_t *logger, const kcp_session_stats_record_t *record);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
51
c/include/latencylog.h
Normal file
51
c/include/latencylog.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef OMNI_LATENCYLOG_H
|
||||
#define OMNI_LATENCYLOG_H
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define EVENT_A_APP_PREP_BEGIN "A_APP_PREP_BEGIN"
|
||||
#define EVENT_A_TX_SCHED "A_TX_SCHED"
|
||||
#define EVENT_A_TX_SOFTWARE "A_TX_SOFTWARE"
|
||||
#define EVENT_A_TX_HARDWARE "A_TX_HARDWARE"
|
||||
#define EVENT_B_RX_HARDWARE "B_RX_HARDWARE"
|
||||
#define EVENT_B_RX_SOFTWARE "B_RX_SOFTWARE"
|
||||
#define EVENT_B_APP_RECV "B_APP_RECV"
|
||||
#define EVENT_B_PERSIST_BEGIN "B_PERSIST_BEGIN"
|
||||
#define EVENT_B_PERSIST_END "B_PERSIST_END"
|
||||
#define EVENT_SEND_HANDOFF_BEGIN "send_handoff_begin"
|
||||
#define EVENT_SEND_HANDOFF_END "send_handoff_end"
|
||||
|
||||
typedef struct latency_event {
|
||||
int64_t ts_unix_nano;
|
||||
char node_role[OMNI_MAX_NODE_ROLE];
|
||||
char node_id[OMNI_MAX_PEER_ID];
|
||||
char event[OMNI_MAX_EVENT_NAME];
|
||||
message_type_t message_type;
|
||||
uint64_t message_id;
|
||||
char from[OMNI_MAX_PEER_ID];
|
||||
char to[OMNI_MAX_PEER_ID];
|
||||
char file_name[OMNI_MAX_FILE_NAME];
|
||||
int body_size;
|
||||
} latency_event_t;
|
||||
|
||||
typedef struct latency_logger {
|
||||
omni_file_logger_t file_logger;
|
||||
int enabled;
|
||||
} latency_logger_t;
|
||||
|
||||
latency_logger_t *latencylog_open_jsonl(const char *path);
|
||||
void latencylog_close(latency_logger_t *logger);
|
||||
int latencylog_log_event(latency_logger_t *logger, const latency_event_t *event);
|
||||
int latencylog_is_business_message(const message_t *msg);
|
||||
void latencylog_log_message_event(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, const message_t *msg);
|
||||
void latencylog_log_message_event_at(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, int64_t ts_unix_nano, const message_t *msg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
25
c/include/linux_timestamping.h
Normal file
25
c/include/linux_timestamping.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef OMNI_LINUX_TIMESTAMPING_H
|
||||
#define OMNI_LINUX_TIMESTAMPING_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct omni_tx_timestamp_event {
|
||||
char event_name[OMNI_MAX_EVENT_NAME];
|
||||
int64_t ts_unix_nano;
|
||||
uint32_t ee_info;
|
||||
uint32_t ee_data;
|
||||
} omni_tx_timestamp_event_t;
|
||||
|
||||
int linux_timestamping_enable_udp_socket(int fd, int enable_rx);
|
||||
int64_t linux_timestamping_parse_rx_timestamp(const struct msghdr *msg);
|
||||
int linux_timestamping_parse_tx_timestamp(const struct msghdr *msg, omni_tx_timestamp_event_t *out_event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
67
c/include/omni_common.h
Normal file
67
c/include/omni_common.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef OMNI_COMMON_H
|
||||
#define OMNI_COMMON_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#define OMNI_NODE_ROLE_PEER "peer"
|
||||
#define OMNI_NODE_ROLE_SERVER "server"
|
||||
|
||||
#define OMNI_MAX_PEER_ID 64
|
||||
#define OMNI_MAX_NODE_ROLE 16
|
||||
#define OMNI_MAX_EVENT_NAME 64
|
||||
#define OMNI_MAX_FILE_NAME 256
|
||||
#define OMNI_MAX_ADDR_TEXT 128
|
||||
#define OMNI_MAX_FRAME_SIZE (8U * 1024U * 1024U)
|
||||
|
||||
#define OMNI_ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
typedef struct omni_file_logger {
|
||||
FILE *file;
|
||||
pthread_mutex_t mutex;
|
||||
} omni_file_logger_t;
|
||||
|
||||
int64_t omni_now_unix_nano(void);
|
||||
uint32_t omni_now_millis32(void);
|
||||
|
||||
int omni_set_nonblocking(int fd, int enabled);
|
||||
int omni_parse_sockaddr(const char *raw, int passive, struct sockaddr_storage *addr, socklen_t *addr_len, int *family_out);
|
||||
int omni_clone_sockaddr(const struct sockaddr *src, socklen_t src_len, struct sockaddr_storage *dst, socklen_t *dst_len);
|
||||
const char *omni_sockaddr_to_string(const struct sockaddr *addr, socklen_t addr_len, char *buffer, size_t buffer_len);
|
||||
|
||||
int omni_bind_device(int fd, const char *device);
|
||||
int omni_ensure_dir(const char *path);
|
||||
int omni_ensure_parent_dir(const char *path);
|
||||
int omni_read_file(const char *path, uint8_t **out, size_t *out_len);
|
||||
int omni_write_full_fd(int fd, const uint8_t *data, size_t len);
|
||||
int omni_append_file(const char *path, const uint8_t *data, size_t len);
|
||||
int omni_write_file(const char *path, const uint8_t *data, size_t len);
|
||||
int omni_random_u32(uint32_t *out);
|
||||
|
||||
char *omni_strdup(const char *src);
|
||||
char *omni_strdup_printf(const char *fmt, ...);
|
||||
char *omni_json_escape(const char *src);
|
||||
char *omni_json_escape_bytes(const uint8_t *src, size_t len);
|
||||
int omni_utf8_valid(const uint8_t *data, size_t len);
|
||||
void omni_trim_newline(char *line);
|
||||
int omni_parse_duration_ms(const char *raw, int default_ms, int *out_ms);
|
||||
double omni_duration_ms_to_ns(double ms);
|
||||
const char *omni_path_base_name(const char *path);
|
||||
|
||||
void omni_file_logger_init(omni_file_logger_t *logger, FILE *file);
|
||||
void omni_file_logger_destroy(omni_file_logger_t *logger);
|
||||
int omni_file_logger_write_line(omni_file_logger_t *logger, const char *line);
|
||||
|
||||
#endif
|
||||
25
c/include/peer_kcp_client.h
Normal file
25
c/include/peer_kcp_client.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef OMNI_PEER_KCP_CLIENT_H
|
||||
#define OMNI_PEER_KCP_CLIENT_H
|
||||
|
||||
#include "transport_kcp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct kcp_client kcp_client_t;
|
||||
|
||||
kcp_client_t *kcp_client_dial(const char *server_addr, const char *dial_addr, const char *peer_id, const char *bind_ip, const char *bind_device, latency_logger_t *logger, kcp_packet_debug_logger_t *packet_logger, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms);
|
||||
const char *kcp_client_id(const kcp_client_t *client);
|
||||
int kcp_client_send_text(kcp_client_t *client, const char *to, const char *text);
|
||||
int kcp_client_send_file_path(kcp_client_t *client, const char *to, const char *path);
|
||||
int kcp_client_receive(kcp_client_t *client, message_t *out_msg);
|
||||
int kcp_client_persist_message(kcp_client_t *client, const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len);
|
||||
int kcp_client_close(kcp_client_t *client);
|
||||
void kcp_client_free(kcp_client_t *client);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
25
c/include/peer_udp_client.h
Normal file
25
c/include/peer_udp_client.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef OMNI_PEER_UDP_CLIENT_H
|
||||
#define OMNI_PEER_UDP_CLIENT_H
|
||||
|
||||
#include "transport_udp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct udp_client udp_client_t;
|
||||
|
||||
udp_client_t *udp_client_dial(const char *server_addr, const char *peer_id, const char *bind_ip, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping);
|
||||
const char *udp_client_id(const udp_client_t *client);
|
||||
int udp_client_send_text(udp_client_t *client, const char *to, const char *text);
|
||||
int udp_client_send_file_path(udp_client_t *client, const char *to, const char *path);
|
||||
int udp_client_receive(udp_client_t *client, message_t *out_msg);
|
||||
int udp_client_persist_message(udp_client_t *client, const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len);
|
||||
int udp_client_close(udp_client_t *client);
|
||||
void udp_client_free(udp_client_t *client);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
61
c/include/protocol.h
Normal file
61
c/include/protocol.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef OMNI_PROTOCOL_H
|
||||
#define OMNI_PROTOCOL_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum message_type {
|
||||
MSG_TYPE_TEXT = 0,
|
||||
MSG_TYPE_FILE = 1,
|
||||
MSG_TYPE_REGISTER = 2,
|
||||
MSG_TYPE_ERROR = 3,
|
||||
MSG_TYPE_INVALID = 255
|
||||
} message_type_t;
|
||||
|
||||
#define SERVER_PEER_ID "server"
|
||||
|
||||
typedef struct message {
|
||||
message_type_t type;
|
||||
uint64_t id;
|
||||
char from[OMNI_MAX_PEER_ID];
|
||||
char to[OMNI_MAX_PEER_ID];
|
||||
char file_name[OMNI_MAX_FILE_NAME];
|
||||
uint8_t *body;
|
||||
size_t body_len;
|
||||
} message_t;
|
||||
|
||||
typedef struct protocol_frame_decoder {
|
||||
uint8_t *buffer;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} protocol_frame_decoder_t;
|
||||
|
||||
const char *protocol_message_type_name(message_type_t type);
|
||||
int protocol_message_type_from_name(const char *raw, message_type_t *out);
|
||||
|
||||
void protocol_message_init(message_t *msg);
|
||||
void protocol_message_clear(message_t *msg);
|
||||
int protocol_message_copy(message_t *dst, const message_t *src);
|
||||
|
||||
int protocol_validate_message(const message_t *msg, char *err, size_t err_len);
|
||||
|
||||
int protocol_encode_message_datagram(const message_t *msg, uint8_t **out, size_t *out_len);
|
||||
int protocol_decode_message_datagram(const uint8_t *data, size_t data_len, message_t *out_msg, char *err, size_t err_len);
|
||||
|
||||
int protocol_encode_message_stream(const message_t *msg, uint8_t **out, size_t *out_len);
|
||||
int protocol_decode_message_stream_payload(const uint8_t *payload, size_t payload_len, message_t *out_msg, char *err, size_t err_len);
|
||||
|
||||
void protocol_frame_decoder_init(protocol_frame_decoder_t *decoder);
|
||||
void protocol_frame_decoder_reset(protocol_frame_decoder_t *decoder);
|
||||
void protocol_frame_decoder_destroy(protocol_frame_decoder_t *decoder);
|
||||
int protocol_frame_decoder_feed(protocol_frame_decoder_t *decoder, const uint8_t *data, size_t data_len);
|
||||
int protocol_frame_decoder_next(protocol_frame_decoder_t *decoder, uint8_t **payload, size_t *payload_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
26
c/include/server_kcp_hub.h
Normal file
26
c/include/server_kcp_hub.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef OMNI_SERVER_KCP_HUB_H
|
||||
#define OMNI_SERVER_KCP_HUB_H
|
||||
|
||||
#include "transport_kcp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct kcp_hub kcp_hub_t;
|
||||
|
||||
kcp_hub_t *kcp_hub_new(latency_logger_t *logger, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms);
|
||||
int kcp_hub_serve_listener(kcp_hub_t *hub, kcp_listener_t *listener);
|
||||
int kcp_hub_serve_session(kcp_hub_t *hub, kcp_conn_t *conn);
|
||||
|
||||
int kcp_hub_set_relay(kcp_hub_t *hub, int relay_fd, const struct sockaddr *peer_addr, socklen_t peer_addr_len, int learn_peer);
|
||||
int kcp_hub_serve_relay(kcp_hub_t *hub);
|
||||
|
||||
int kcp_hub_close(kcp_hub_t *hub);
|
||||
void kcp_hub_free(kcp_hub_t *hub);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
21
c/include/server_udp_hub.h
Normal file
21
c/include/server_udp_hub.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef OMNI_SERVER_UDP_HUB_H
|
||||
#define OMNI_SERVER_UDP_HUB_H
|
||||
|
||||
#include "transport_udp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct udp_hub udp_hub_t;
|
||||
|
||||
udp_hub_t *udp_hub_open(const char *listen_addr, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping);
|
||||
int udp_hub_serve(udp_hub_t *hub);
|
||||
int udp_hub_close(udp_hub_t *hub);
|
||||
void udp_hub_free(udp_hub_t *hub);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
21
c/include/server_udp_relay.h
Normal file
21
c/include/server_udp_relay.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef OMNI_SERVER_UDP_RELAY_H
|
||||
#define OMNI_SERVER_UDP_RELAY_H
|
||||
|
||||
#include "omni_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct udp_relay udp_relay_t;
|
||||
|
||||
udp_relay_t *udp_relay_open(const char *listen_addr, const char *upstream_addr);
|
||||
int udp_relay_serve(udp_relay_t *relay);
|
||||
int udp_relay_close(udp_relay_t *relay);
|
||||
void udp_relay_free(udp_relay_t *relay);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
43
c/include/transport_kcp.h
Normal file
43
c/include/transport_kcp.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef OMNI_TRANSPORT_KCP_H
|
||||
#define OMNI_TRANSPORT_KCP_H
|
||||
|
||||
#include "kcp_packet_debug.h"
|
||||
#include "kcp_session_stats.h"
|
||||
#include "latencylog.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define KCP_NODELAY 1
|
||||
#define KCP_INTERVAL 10
|
||||
#define KCP_RESEND 2
|
||||
#define KCP_NC 1
|
||||
#define KCP_WND_SIZE 256
|
||||
#define KCP_MTU 1400
|
||||
#define KCP_DEFAULT_STATS_INTERVAL_MS 100
|
||||
|
||||
typedef struct kcp_conn kcp_conn_t;
|
||||
typedef struct kcp_listener kcp_listener_t;
|
||||
|
||||
kcp_conn_t *kcp_conn_dial(const char *server_addr, const char *bind_ip, const char *bind_device, kcp_packet_debug_logger_t *packet_logger, latency_logger_t *logger, const char *node_role, const char *node_id, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms);
|
||||
int kcp_conn_configure_runtime(kcp_conn_t *conn, latency_logger_t *logger, const char *node_role, const char *node_id, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms);
|
||||
int kcp_conn_send(kcp_conn_t *conn, const message_t *msg);
|
||||
int kcp_conn_receive(kcp_conn_t *conn, message_t *out_msg);
|
||||
int kcp_conn_close(kcp_conn_t *conn);
|
||||
void kcp_conn_free(kcp_conn_t *conn);
|
||||
uint32_t kcp_conn_conv(const kcp_conn_t *conn);
|
||||
int kcp_conn_local_addr(const kcp_conn_t *conn, struct sockaddr_storage *addr, socklen_t *addr_len);
|
||||
|
||||
kcp_listener_t *kcp_listener_listen(const char *listen_addr, const char *bind_device, kcp_packet_debug_logger_t *packet_logger, const char *node_role, const char *node_id);
|
||||
kcp_conn_t *kcp_listener_accept(kcp_listener_t *listener);
|
||||
int kcp_listener_close(kcp_listener_t *listener);
|
||||
void kcp_listener_free(kcp_listener_t *listener);
|
||||
|
||||
int kcp_session_stats_parse_interval_ms(const char *raw, int *out_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
30
c/include/transport_udp.h
Normal file
30
c/include/transport_udp.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef OMNI_TRANSPORT_UDP_H
|
||||
#define OMNI_TRANSPORT_UDP_H
|
||||
|
||||
#include "latencylog.h"
|
||||
#include "linux_timestamping.h"
|
||||
#include "tx_timestamp_debug.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct udp_conn udp_conn_t;
|
||||
|
||||
udp_conn_t *udp_conn_dial(const char *server_addr, const char *bind_ip, const char *bind_device, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger);
|
||||
udp_conn_t *udp_conn_bind(const char *listen_addr, const char *bind_device, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger);
|
||||
|
||||
int udp_conn_send(udp_conn_t *conn, const message_t *msg);
|
||||
int udp_conn_send_to(udp_conn_t *conn, const message_t *msg, const struct sockaddr *addr, socklen_t addr_len);
|
||||
int udp_conn_receive(udp_conn_t *conn, message_t *out_msg, struct sockaddr_storage *addr, socklen_t *addr_len);
|
||||
|
||||
int udp_conn_fd(const udp_conn_t *conn);
|
||||
int udp_conn_local_addr(const udp_conn_t *conn, struct sockaddr_storage *addr, socklen_t *addr_len);
|
||||
int udp_conn_close(udp_conn_t *conn);
|
||||
void udp_conn_free(udp_conn_t *conn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
51
c/include/tx_timestamp_debug.h
Normal file
51
c/include/tx_timestamp_debug.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef OMNI_TX_TIMESTAMP_DEBUG_H
|
||||
#define OMNI_TX_TIMESTAMP_DEBUG_H
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define TX_TIMESTAMP_DEBUG_RECORD_SEND_CHUNK "send_chunk"
|
||||
#define TX_TIMESTAMP_DEBUG_RECORD_ERRQUEUE_EVENT "errqueue_event"
|
||||
|
||||
typedef struct tx_timestamp_debug_record {
|
||||
char record_type[32];
|
||||
char node_role[OMNI_MAX_NODE_ROLE];
|
||||
char node_id[OMNI_MAX_PEER_ID];
|
||||
message_type_t message_type;
|
||||
uint64_t message_id;
|
||||
char from[OMNI_MAX_PEER_ID];
|
||||
char to[OMNI_MAX_PEER_ID];
|
||||
char file_name[OMNI_MAX_FILE_NAME];
|
||||
int body_size;
|
||||
char phase[32];
|
||||
int send_call_index;
|
||||
int frame_offset_start;
|
||||
int frame_offset_end;
|
||||
int bytes_written;
|
||||
uint32_t expected_tx_id;
|
||||
int read_index;
|
||||
char event_name[OMNI_MAX_EVENT_NAME];
|
||||
int64_t ts_unix_nano;
|
||||
uint32_t ee_info;
|
||||
uint32_t ee_data;
|
||||
int matched_send_call_index;
|
||||
int selected_for_latency;
|
||||
} tx_timestamp_debug_record_t;
|
||||
|
||||
typedef struct tx_timestamp_debug_logger {
|
||||
omni_file_logger_t file_logger;
|
||||
int enabled;
|
||||
} tx_timestamp_debug_logger_t;
|
||||
|
||||
tx_timestamp_debug_logger_t *tx_timestamp_debug_open_jsonl(const char *path);
|
||||
void tx_timestamp_debug_close(tx_timestamp_debug_logger_t *logger);
|
||||
int tx_timestamp_debug_log(tx_timestamp_debug_logger_t *logger, const tx_timestamp_debug_record_t *record);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
77
c/src/interactive.c
Normal file
77
c/src/interactive.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "interactive.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
static void interactive_skip_spaces(const char **cursor) {
|
||||
while (**cursor != '\0' && isspace((unsigned char) **cursor)) {
|
||||
(*cursor)++;
|
||||
}
|
||||
}
|
||||
|
||||
int interactive_parse_command(const char *line, interactive_command_t *command, char *err, size_t err_len) {
|
||||
const char *cursor = line;
|
||||
char action[16];
|
||||
size_t action_len = 0;
|
||||
size_t to_len = 0;
|
||||
size_t value_len;
|
||||
|
||||
if (line == NULL || command == NULL) {
|
||||
snprintf(err, err_len, "interactive: invalid command");
|
||||
return -1;
|
||||
}
|
||||
memset(command, 0, sizeof(*command));
|
||||
interactive_skip_spaces(&cursor);
|
||||
while (*cursor != '\0' && !isspace((unsigned char) *cursor) && action_len + 1 < sizeof(action)) {
|
||||
action[action_len++] = *cursor++;
|
||||
}
|
||||
action[action_len] = '\0';
|
||||
if (action_len == 0) {
|
||||
snprintf(err, err_len, "interactive: empty command");
|
||||
return -1;
|
||||
}
|
||||
if (strcmp(action, "help") == 0) {
|
||||
command->type = INTERACTIVE_CMD_HELP;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(action, "quit") == 0) {
|
||||
command->type = INTERACTIVE_CMD_QUIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
interactive_skip_spaces(&cursor);
|
||||
while (*cursor != '\0' && !isspace((unsigned char) *cursor) && to_len + 1 < sizeof(command->to)) {
|
||||
command->to[to_len++] = *cursor++;
|
||||
}
|
||||
command->to[to_len] = '\0';
|
||||
interactive_skip_spaces(&cursor);
|
||||
if (command->to[0] == '\0' || *cursor == '\0') {
|
||||
snprintf(err, err_len, "interactive: missing target or value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
value_len = strlen(cursor);
|
||||
if (value_len >= sizeof(command->value)) {
|
||||
snprintf(err, err_len, "interactive: value too long");
|
||||
return -1;
|
||||
}
|
||||
snprintf(command->value, sizeof(command->value), "%s", cursor);
|
||||
|
||||
if (strcmp(action, "text") == 0) {
|
||||
command->type = INTERACTIVE_CMD_TEXT;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(action, "file") == 0) {
|
||||
command->type = INTERACTIVE_CMD_FILE;
|
||||
return 0;
|
||||
}
|
||||
snprintf(err, err_len, "interactive: unknown command %s", action);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void interactive_print_help(FILE *out, const char *transport_name) {
|
||||
fprintf(out, "interactive mode commands (%s):\n", transport_name);
|
||||
fprintf(out, " help show this help\n");
|
||||
fprintf(out, " text <peer> <message> send one text message\n");
|
||||
fprintf(out, " file <peer> <path> send one file\n");
|
||||
fprintf(out, " quit exit this process\n");
|
||||
}
|
||||
166
c/src/kcp_packet_debug.c
Normal file
166
c/src/kcp_packet_debug.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "kcp_packet_debug.h"
|
||||
|
||||
kcp_packet_debug_logger_t *kcp_packet_debug_open_jsonl(const char *path) {
|
||||
kcp_packet_debug_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (kcp_packet_debug_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init(&logger->file_logger, file);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void kcp_packet_debug_close(kcp_packet_debug_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
void kcp_packet_debug_record_clear(kcp_packet_debug_record_t *record) {
|
||||
if (record == NULL) {
|
||||
return;
|
||||
}
|
||||
free(record->segments);
|
||||
memset(record, 0, sizeof(*record));
|
||||
}
|
||||
|
||||
int kcp_packet_debug_log(kcp_packet_debug_logger_t *logger, const kcp_packet_debug_record_t *record) {
|
||||
char *event = NULL;
|
||||
char *node_role = NULL;
|
||||
char *node_id = NULL;
|
||||
char *local_addr = NULL;
|
||||
char *remote_addr = NULL;
|
||||
char *segments_json = NULL;
|
||||
char *tx_id_text = NULL;
|
||||
char *conv_text = NULL;
|
||||
char *line = NULL;
|
||||
size_t i;
|
||||
size_t cap = 128U;
|
||||
size_t len = 0U;
|
||||
|
||||
if (logger == NULL || record == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
event = omni_json_escape(record->event);
|
||||
node_role = omni_json_escape(record->node_role);
|
||||
node_id = omni_json_escape(record->node_id);
|
||||
local_addr = omni_json_escape(record->local_addr);
|
||||
remote_addr = omni_json_escape(record->remote_addr);
|
||||
if (event == NULL || node_role == NULL || node_id == NULL || local_addr == NULL || remote_addr == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
segments_json = (char *) malloc(cap);
|
||||
if (segments_json == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
return -1;
|
||||
}
|
||||
segments_json[len++] = '[';
|
||||
for (i = 0; i < record->segment_count; ++i) {
|
||||
int written;
|
||||
while (len + 96U > cap) {
|
||||
char *next = (char *) realloc(segments_json, cap * 2U);
|
||||
if (next == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(segments_json);
|
||||
return -1;
|
||||
}
|
||||
segments_json = next;
|
||||
cap *= 2U;
|
||||
}
|
||||
written = snprintf(
|
||||
segments_json + len,
|
||||
cap - len,
|
||||
"%s{\"cmd\":%u,\"sn\":%u,\"una\":%u,\"frg\":%u,\"wnd\":%u,\"len\":%u}",
|
||||
i == 0 ? "" : ",",
|
||||
record->segments[i].cmd,
|
||||
record->segments[i].sn,
|
||||
record->segments[i].una,
|
||||
record->segments[i].frg,
|
||||
record->segments[i].wnd,
|
||||
record->segments[i].len
|
||||
);
|
||||
len += (size_t) written;
|
||||
}
|
||||
segments_json[len++] = ']';
|
||||
segments_json[len] = '\0';
|
||||
|
||||
tx_id_text = record->has_udp_tx_id ? omni_strdup_printf("%u", record->udp_tx_id) : omni_strdup("null");
|
||||
conv_text = record->has_kcp_conv ? omni_strdup_printf("%u", record->kcp_conv) : omni_strdup("null");
|
||||
if (tx_id_text == NULL || conv_text == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(segments_json);
|
||||
free(tx_id_text);
|
||||
free(conv_text);
|
||||
return -1;
|
||||
}
|
||||
|
||||
line = omni_strdup_printf(
|
||||
"{\"event\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"local_addr\":\"%s\",\"remote_addr\":\"%s\",\"packet_bytes\":%d,\"udp_tx_id\":%s,\"kcp_conv\":%s,\"segments\":%s,\"ts_unix_nano\":%" PRId64 "}",
|
||||
event,
|
||||
node_role,
|
||||
node_id,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
record->packet_bytes,
|
||||
tx_id_text,
|
||||
conv_text,
|
||||
segments_json,
|
||||
record->ts_unix_nano
|
||||
);
|
||||
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(segments_json);
|
||||
free(tx_id_text);
|
||||
free(conv_text);
|
||||
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
}
|
||||
258
c/src/kcp_session_stats.c
Normal file
258
c/src/kcp_session_stats.c
Normal file
@@ -0,0 +1,258 @@
|
||||
#include "kcp_session_stats.h"
|
||||
|
||||
static int kcp_session_stats_append(char **line, size_t *len, const char *suffix) {
|
||||
size_t suffix_len;
|
||||
char *next;
|
||||
|
||||
if (line == NULL || len == NULL || suffix == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
suffix_len = strlen(suffix);
|
||||
next = (char *) realloc(*line, *len + suffix_len + 1U);
|
||||
if (next == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(next + *len, suffix, suffix_len + 1U);
|
||||
*line = next;
|
||||
*len += suffix_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_session_stats_appendf(char **line, size_t *len, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_list copy;
|
||||
int needed;
|
||||
char *buffer;
|
||||
|
||||
if (line == NULL || len == NULL || fmt == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
va_copy(copy, args);
|
||||
needed = vsnprintf(NULL, 0, fmt, copy);
|
||||
va_end(copy);
|
||||
if (needed < 0) {
|
||||
va_end(args);
|
||||
return -1;
|
||||
}
|
||||
|
||||
buffer = (char *) malloc((size_t) needed + 1U);
|
||||
if (buffer == NULL) {
|
||||
va_end(args);
|
||||
return -1;
|
||||
}
|
||||
vsnprintf(buffer, (size_t) needed + 1U, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (kcp_session_stats_append(line, len, buffer) != 0) {
|
||||
free(buffer);
|
||||
return -1;
|
||||
}
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
kcp_session_stats_logger_t *kcp_session_stats_open_jsonl(const char *path) {
|
||||
kcp_session_stats_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (kcp_session_stats_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init(&logger->file_logger, file);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void kcp_session_stats_close(kcp_session_stats_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
int kcp_session_stats_log(kcp_session_stats_logger_t *logger, const kcp_session_stats_record_t *record) {
|
||||
char *record_type = NULL;
|
||||
char *node_role = NULL;
|
||||
char *node_id = NULL;
|
||||
char *local_addr = NULL;
|
||||
char *remote_addr = NULL;
|
||||
char *sample_reason = NULL;
|
||||
char *line = NULL;
|
||||
size_t line_len = 0;
|
||||
|
||||
if (logger == NULL || record == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
record_type = omni_json_escape(record->record_type);
|
||||
node_role = omni_json_escape(record->node_role);
|
||||
node_id = omni_json_escape(record->node_id);
|
||||
local_addr = omni_json_escape(record->local_addr);
|
||||
remote_addr = omni_json_escape(record->remote_addr);
|
||||
sample_reason = omni_json_escape(record->sample_reason);
|
||||
if (record_type == NULL || node_role == NULL || node_id == NULL || local_addr == NULL || remote_addr == NULL || sample_reason == NULL) {
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
return -1;
|
||||
}
|
||||
line = omni_strdup("");
|
||||
if (line == NULL) {
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (kcp_session_stats_appendf(&line, &line_len, "{\"record_type\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"ts_unix_nano\":%" PRId64 ",\"sample_reason\":\"%s\"",
|
||||
record_type,
|
||||
node_role,
|
||||
node_id,
|
||||
record->ts_unix_nano,
|
||||
sample_reason) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->local_addr[0] != '\0' &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"local_addr\":\"%s\"", local_addr) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->remote_addr[0] != '\0' &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"remote_addr\":\"%s\"", remote_addr) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_conv &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"conv\":%u", record->conv) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_rto_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"rto_ms\":%u", record->rto_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_srtt_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"srtt_ms\":%d", record->srtt_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_srttvar_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"srttvar_ms\":%d", record->srttvar_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_bytes_sent &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"bytes_sent\":%" PRIu64, record->bytes_sent) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_bytes_received &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"bytes_received\":%" PRIu64, record->bytes_received) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_in_pkts &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"in_pkts\":%" PRIu64, record->in_pkts) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_out_pkts &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"out_pkts\":%" PRIu64, record->out_pkts) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_in_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"in_segs\":%" PRIu64, record->in_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_out_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"out_segs\":%" PRIu64, record->out_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_retrans_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"retrans_segs\":%" PRIu64, record->retrans_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_fast_retrans_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"fast_retrans_segs\":%" PRIu64, record->fast_retrans_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_early_retrans_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"early_retrans_segs\":%" PRIu64, record->early_retrans_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_lost_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"lost_segs\":%" PRIu64, record->lost_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_repeat_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"repeat_segs\":%" PRIu64, record->repeat_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_in_errs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"in_errs\":%" PRIu64, record->in_errs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_kcp_in_errs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"kcp_in_errs\":%" PRIu64, record->kcp_in_errs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_ring_buffer_snd_queue &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_snd_queue\":%" PRIu64, record->ring_buffer_snd_queue) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_ring_buffer_rcv_queue &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_rcv_queue\":%" PRIu64, record->ring_buffer_rcv_queue) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_ring_buffer_snd_buffer &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_snd_buffer\":%" PRIu64, record->ring_buffer_snd_buffer) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_curr_estab &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"curr_estab\":%" PRIu64, record->curr_estab) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (kcp_session_stats_append(&line, &line_len, "}") != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
130
c/src/latencylog.c
Normal file
130
c/src/latencylog.c
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "latencylog.h"
|
||||
|
||||
static void latencylog_fill_event(latency_event_t *event, const char *node_role, const char *node_id, const char *event_name, int64_t ts_unix_nano, const message_t *msg) {
|
||||
memset(event, 0, sizeof(*event));
|
||||
event->ts_unix_nano = ts_unix_nano;
|
||||
snprintf(event->node_role, sizeof(event->node_role), "%s", node_role == NULL ? "" : node_role);
|
||||
snprintf(event->node_id, sizeof(event->node_id), "%s", node_id == NULL ? "" : node_id);
|
||||
snprintf(event->event, sizeof(event->event), "%s", event_name == NULL ? "" : event_name);
|
||||
event->message_type = msg->type;
|
||||
event->message_id = msg->id;
|
||||
snprintf(event->from, sizeof(event->from), "%s", msg->from);
|
||||
snprintf(event->to, sizeof(event->to), "%s", msg->to);
|
||||
snprintf(event->file_name, sizeof(event->file_name), "%s", msg->file_name);
|
||||
event->body_size = (int) msg->body_len;
|
||||
}
|
||||
|
||||
latency_logger_t *latencylog_open_jsonl(const char *path) {
|
||||
latency_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (latency_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init(&logger->file_logger, file);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void latencylog_close(latency_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
int latencylog_log_event(latency_logger_t *logger, const latency_event_t *event) {
|
||||
char *node_role = NULL;
|
||||
char *node_id = NULL;
|
||||
char *event_name = NULL;
|
||||
char *from = NULL;
|
||||
char *to = NULL;
|
||||
char *file_name = NULL;
|
||||
char *line = NULL;
|
||||
|
||||
if (logger == NULL || event == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
node_role = omni_json_escape(event->node_role);
|
||||
node_id = omni_json_escape(event->node_id);
|
||||
event_name = omni_json_escape(event->event);
|
||||
from = omni_json_escape(event->from);
|
||||
to = omni_json_escape(event->to);
|
||||
file_name = omni_json_escape(event->file_name);
|
||||
if (node_role == NULL || node_id == NULL || event_name == NULL || from == NULL || to == NULL || file_name == NULL) {
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(event_name);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
line = omni_strdup_printf(
|
||||
"{\"ts_unix_nano\":%" PRId64 ",\"node_role\":\"%s\",\"node_id\":\"%s\",\"event\":\"%s\",\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"file_name\":\"%s\",\"body_size\":%d}",
|
||||
event->ts_unix_nano,
|
||||
node_role,
|
||||
node_id,
|
||||
event_name,
|
||||
protocol_message_type_name(event->message_type),
|
||||
event->message_id,
|
||||
from,
|
||||
to,
|
||||
file_name,
|
||||
event->body_size
|
||||
);
|
||||
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(event_name);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int latencylog_is_business_message(const message_t *msg) {
|
||||
if (msg == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return msg->type == MSG_TYPE_TEXT || msg->type == MSG_TYPE_FILE;
|
||||
}
|
||||
|
||||
void latencylog_log_message_event(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, const message_t *msg) {
|
||||
latencylog_log_message_event_at(logger, node_role, node_id, event_name, omni_now_unix_nano(), msg);
|
||||
}
|
||||
|
||||
void latencylog_log_message_event_at(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, int64_t ts_unix_nano, const message_t *msg) {
|
||||
latency_event_t event;
|
||||
if (!latencylog_is_business_message(msg)) {
|
||||
return;
|
||||
}
|
||||
latencylog_fill_event(&event, node_role, node_id, event_name, ts_unix_nano, msg);
|
||||
(void) latencylog_log_event(logger, &event);
|
||||
}
|
||||
103
c/src/linux_timestamping.c
Normal file
103
c/src/linux_timestamping.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "linux_timestamping.h"
|
||||
#include "latencylog.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/net_tstamp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
static int64_t linux_timespec_to_ns(const struct timespec *ts) {
|
||||
if (ts == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (int64_t) ts->tv_sec * 1000000000LL + ts->tv_nsec;
|
||||
}
|
||||
|
||||
int linux_timestamping_enable_udp_socket(int fd, int enable_rx) {
|
||||
int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY;
|
||||
if (enable_rx) {
|
||||
flags |= SOF_TIMESTAMPING_RX_SOFTWARE;
|
||||
}
|
||||
return setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
|
||||
}
|
||||
|
||||
int64_t linux_timestamping_parse_rx_timestamp(const struct msghdr *msg) {
|
||||
struct cmsghdr *cmsg;
|
||||
const struct scm_timestamping *timestamps;
|
||||
if (msg == NULL) {
|
||||
return 0;
|
||||
}
|
||||
for (cmsg = CMSG_FIRSTHDR((struct msghdr *) msg); cmsg != NULL; cmsg = CMSG_NXTHDR((struct msghdr *) msg, cmsg)) {
|
||||
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
|
||||
timestamps = (const struct scm_timestamping *) CMSG_DATA(cmsg);
|
||||
if (timestamps->ts[0].tv_sec != 0 || timestamps->ts[0].tv_nsec != 0) {
|
||||
return linux_timespec_to_ns(×tamps->ts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int linux_timestamping_parse_tx_timestamp(const struct msghdr *msg, omni_tx_timestamp_event_t *out_event) {
|
||||
struct cmsghdr *cmsg;
|
||||
const struct scm_timestamping *timestamps = NULL;
|
||||
const struct sock_extended_err *sock_err = NULL;
|
||||
int64_t timestamp_ns = 0;
|
||||
|
||||
if (msg == NULL || out_event == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(out_event, 0, sizeof(*out_event));
|
||||
|
||||
for (cmsg = CMSG_FIRSTHDR((struct msghdr *) msg); cmsg != NULL; cmsg = CMSG_NXTHDR((struct msghdr *) msg, cmsg)) {
|
||||
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
|
||||
timestamps = (const struct scm_timestamping *) CMSG_DATA(cmsg);
|
||||
} else if ((cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) ||
|
||||
(cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_RECVERR)) {
|
||||
sock_err = (const struct sock_extended_err *) CMSG_DATA(cmsg);
|
||||
}
|
||||
}
|
||||
if (timestamps == NULL || sock_err == NULL) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
if (timestamps->ts[0].tv_sec != 0 || timestamps->ts[0].tv_nsec != 0) {
|
||||
timestamp_ns = linux_timespec_to_ns(×tamps->ts[0]);
|
||||
snprintf(out_event->event_name, sizeof(out_event->event_name), "%s", EVENT_A_TX_SOFTWARE);
|
||||
} else if (timestamps->ts[1].tv_sec != 0 || timestamps->ts[1].tv_nsec != 0) {
|
||||
timestamp_ns = linux_timespec_to_ns(×tamps->ts[1]);
|
||||
snprintf(out_event->event_name, sizeof(out_event->event_name), "%s", EVENT_A_TX_SCHED);
|
||||
} else {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
out_event->ts_unix_nano = timestamp_ns;
|
||||
out_event->ee_info = sock_err->ee_info;
|
||||
out_event->ee_data = sock_err->ee_data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int linux_timestamping_enable_udp_socket(int fd, int enable_rx) {
|
||||
(void) fd;
|
||||
(void) enable_rx;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t linux_timestamping_parse_rx_timestamp(const struct msghdr *msg) {
|
||||
(void) msg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int linux_timestamping_parse_tx_timestamp(const struct msghdr *msg, omni_tx_timestamp_event_t *out_event) {
|
||||
(void) msg;
|
||||
(void) out_event;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
568
c/src/omni_common.c
Normal file
568
c/src/omni_common.c
Normal file
@@ -0,0 +1,568 @@
|
||||
#include "omni_common.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int64_t omni_now_unix_nano(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
return (int64_t) ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
uint32_t omni_now_millis32(void) {
|
||||
struct timespec ts;
|
||||
uint64_t ms;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
ms = (uint64_t) ts.tv_sec * 1000ULL + (uint64_t) (ts.tv_nsec / 1000000L);
|
||||
return (uint32_t) (ms & 0xffffffffu);
|
||||
}
|
||||
|
||||
int omni_set_nonblocking(int fd, int enabled) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (enabled) {
|
||||
flags |= O_NONBLOCK;
|
||||
} else {
|
||||
flags &= ~O_NONBLOCK;
|
||||
}
|
||||
return fcntl(fd, F_SETFL, flags);
|
||||
}
|
||||
|
||||
int omni_parse_sockaddr(const char *raw, int passive, struct sockaddr_storage *addr, socklen_t *addr_len, int *family_out) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result = NULL;
|
||||
char host_copy[OMNI_MAX_ADDR_TEXT];
|
||||
char port_copy[32];
|
||||
const char *host = NULL;
|
||||
const char *service = NULL;
|
||||
const char *last_colon;
|
||||
size_t host_len;
|
||||
|
||||
if (raw == NULL || addr == NULL || addr_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = passive ? AI_PASSIVE : 0;
|
||||
|
||||
last_colon = strrchr(raw, ':');
|
||||
if (last_colon == NULL) {
|
||||
host = passive ? NULL : raw;
|
||||
service = passive ? raw : "0";
|
||||
} else {
|
||||
host_len = (size_t) (last_colon - raw);
|
||||
if (host_len >= sizeof(host_copy)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
memcpy(host_copy, raw, host_len);
|
||||
host_copy[host_len] = '\0';
|
||||
snprintf(port_copy, sizeof(port_copy), "%s", last_colon + 1);
|
||||
host = host_len == 0 ? NULL : host_copy;
|
||||
service = port_copy;
|
||||
}
|
||||
|
||||
if (getaddrinfo(host, service, &hints, &result) != 0 || result == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memcpy(addr, result->ai_addr, result->ai_addrlen);
|
||||
*addr_len = (socklen_t) result->ai_addrlen;
|
||||
if (family_out != NULL) {
|
||||
*family_out = result->ai_family;
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omni_clone_sockaddr(const struct sockaddr *src, socklen_t src_len, struct sockaddr_storage *dst, socklen_t *dst_len) {
|
||||
if (src == NULL || dst == NULL || dst_len == NULL || src_len > sizeof(*dst)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(dst, 0, sizeof(*dst));
|
||||
memcpy(dst, src, src_len);
|
||||
*dst_len = src_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *omni_sockaddr_to_string(const struct sockaddr *addr, socklen_t addr_len, char *buffer, size_t buffer_len) {
|
||||
char host[NI_MAXHOST];
|
||||
char service[NI_MAXSERV];
|
||||
|
||||
if (buffer == NULL || buffer_len == 0) {
|
||||
return "";
|
||||
}
|
||||
if (addr == NULL) {
|
||||
snprintf(buffer, buffer_len, "<nil>");
|
||||
return buffer;
|
||||
}
|
||||
if (getnameinfo(addr, addr_len, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
|
||||
snprintf(buffer, buffer_len, "<addr>");
|
||||
return buffer;
|
||||
}
|
||||
if (addr->sa_family == AF_INET6) {
|
||||
snprintf(buffer, buffer_len, "[%s]:%s", host, service);
|
||||
} else {
|
||||
snprintf(buffer, buffer_len, "%s:%s", host, service);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int omni_bind_device(int fd, const char *device) {
|
||||
#ifdef __linux__
|
||||
if (device == NULL || device[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
return setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, (socklen_t) strlen(device));
|
||||
#else
|
||||
(void) fd;
|
||||
(void) device;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int omni_mkdir_single(const char *path) {
|
||||
if (mkdir(path, 0755) == 0 || errno == EEXIST) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int omni_ensure_dir(const char *path) {
|
||||
char tmp[PATH_MAX];
|
||||
size_t i;
|
||||
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
if (strlen(path) >= sizeof(tmp)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
for (i = 1; tmp[i] != '\0'; ++i) {
|
||||
if (tmp[i] == '/') {
|
||||
tmp[i] = '\0';
|
||||
if (tmp[0] != '\0' && omni_mkdir_single(tmp) != 0) {
|
||||
return -1;
|
||||
}
|
||||
tmp[i] = '/';
|
||||
}
|
||||
}
|
||||
return omni_mkdir_single(tmp);
|
||||
}
|
||||
|
||||
int omni_ensure_parent_dir(const char *path) {
|
||||
char tmp[PATH_MAX];
|
||||
char *slash;
|
||||
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
if (strlen(path) >= sizeof(tmp)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
slash = strrchr(tmp, '/');
|
||||
if (slash == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (slash == tmp) {
|
||||
return omni_mkdir_single("/");
|
||||
}
|
||||
*slash = '\0';
|
||||
return omni_ensure_dir(tmp);
|
||||
}
|
||||
|
||||
int omni_read_file(const char *path, uint8_t **out, size_t *out_len) {
|
||||
FILE *file;
|
||||
long size;
|
||||
uint8_t *buffer;
|
||||
if (out == NULL || out_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out = NULL;
|
||||
*out_len = 0;
|
||||
file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
size = ftell(file);
|
||||
if (size < 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
buffer = (uint8_t *) malloc((size_t) size);
|
||||
if (size > 0 && buffer == NULL) {
|
||||
fclose(file);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
if ((size_t) size > 0 && fread(buffer, 1, (size_t) size, file) != (size_t) size) {
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
fclose(file);
|
||||
*out = buffer;
|
||||
*out_len = (size_t) size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omni_write_full_fd(int fd, const uint8_t *data, size_t len) {
|
||||
ssize_t written;
|
||||
while (len > 0) {
|
||||
written = write(fd, data, len);
|
||||
if (written < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (written == 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
data += written;
|
||||
len -= (size_t) written;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omni_write_file_internal(const char *path, const uint8_t *data, size_t len, const char *mode) {
|
||||
FILE *file;
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return -1;
|
||||
}
|
||||
file = fopen(path, mode);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (len > 0 && fwrite(data, 1, len, file) != len) {
|
||||
fclose(file);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omni_append_file(const char *path, const uint8_t *data, size_t len) {
|
||||
return omni_write_file_internal(path, data, len, "ab");
|
||||
}
|
||||
|
||||
int omni_write_file(const char *path, const uint8_t *data, size_t len) {
|
||||
return omni_write_file_internal(path, data, len, "wb");
|
||||
}
|
||||
|
||||
int omni_random_u32(uint32_t *out) {
|
||||
uint8_t *cursor;
|
||||
size_t remaining;
|
||||
int fd;
|
||||
|
||||
if (out == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cursor = (uint8_t *) out;
|
||||
remaining = sizeof(*out);
|
||||
while (remaining > 0) {
|
||||
ssize_t n = read(fd, cursor, remaining);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
if (n == 0) {
|
||||
close(fd);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
cursor += n;
|
||||
remaining -= (size_t) n;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (*out == 0) {
|
||||
*out = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *omni_strdup(const char *src) {
|
||||
size_t len;
|
||||
char *dst;
|
||||
if (src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
len = strlen(src);
|
||||
dst = (char *) malloc(len + 1U);
|
||||
if (dst == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(dst, src, len + 1U);
|
||||
return dst;
|
||||
}
|
||||
|
||||
char *omni_strdup_printf(const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_list copy;
|
||||
int needed;
|
||||
char *buffer;
|
||||
va_start(args, fmt);
|
||||
va_copy(copy, args);
|
||||
needed = vsnprintf(NULL, 0, fmt, copy);
|
||||
va_end(copy);
|
||||
if (needed < 0) {
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
buffer = (char *) malloc((size_t) needed + 1U);
|
||||
if (buffer == NULL) {
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
vsnprintf(buffer, (size_t) needed + 1U, fmt, args);
|
||||
va_end(args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
char *omni_json_escape_bytes(const uint8_t *src, size_t len) {
|
||||
size_t i;
|
||||
size_t out_len = 0;
|
||||
char *out;
|
||||
char *cursor;
|
||||
|
||||
if (src == NULL) {
|
||||
if (len == 0) {
|
||||
return omni_strdup("");
|
||||
}
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
switch (src[i]) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
out_len += 2;
|
||||
break;
|
||||
default:
|
||||
out_len += src[i] < 0x20 ? 6U : 1U;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out = (char *) malloc(out_len + 1U);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
cursor = out;
|
||||
for (i = 0; i < len; ++i) {
|
||||
switch (src[i]) {
|
||||
case '\\':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = '\\';
|
||||
break;
|
||||
case '"':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = '"';
|
||||
break;
|
||||
case '\b':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'b';
|
||||
break;
|
||||
case '\f':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'f';
|
||||
break;
|
||||
case '\n':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 't';
|
||||
break;
|
||||
default:
|
||||
if (src[i] < 0x20) {
|
||||
snprintf(cursor, 7, "\\u%04x", src[i]);
|
||||
cursor += 6;
|
||||
} else {
|
||||
*cursor++ = (char) src[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
*cursor = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
char *omni_json_escape(const char *src) {
|
||||
if (src == NULL) {
|
||||
return omni_strdup("");
|
||||
}
|
||||
return omni_json_escape_bytes((const uint8_t *) src, strlen(src));
|
||||
}
|
||||
|
||||
int omni_utf8_valid(const uint8_t *data, size_t len) {
|
||||
size_t i = 0;
|
||||
uint8_t c;
|
||||
while (i < len) {
|
||||
c = data[i];
|
||||
if (c <= 0x7f) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0xe0) == 0xc0) {
|
||||
if (i + 1 >= len || (data[i + 1] & 0xc0) != 0x80 || c < 0xc2) {
|
||||
return 0;
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0xf0) == 0xe0) {
|
||||
if (i + 2 >= len || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80) {
|
||||
return 0;
|
||||
}
|
||||
if (c == 0xe0 && data[i + 1] < 0xa0) {
|
||||
return 0;
|
||||
}
|
||||
if (c == 0xed && data[i + 1] >= 0xa0) {
|
||||
return 0;
|
||||
}
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0xf8) == 0xf0) {
|
||||
if (i + 3 >= len || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80 || (data[i + 3] & 0xc0) != 0x80) {
|
||||
return 0;
|
||||
}
|
||||
if (c == 0xf0 && data[i + 1] < 0x90) {
|
||||
return 0;
|
||||
}
|
||||
if (c > 0xf4 || (c == 0xf4 && data[i + 1] >= 0x90)) {
|
||||
return 0;
|
||||
}
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void omni_trim_newline(char *line) {
|
||||
size_t len;
|
||||
if (line == NULL) {
|
||||
return;
|
||||
}
|
||||
len = strlen(line);
|
||||
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
|
||||
line[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
int omni_parse_duration_ms(const char *raw, int default_ms, int *out_ms) {
|
||||
char *endptr;
|
||||
long value;
|
||||
if (out_ms == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (raw == NULL || raw[0] == '\0') {
|
||||
*out_ms = default_ms;
|
||||
return 0;
|
||||
}
|
||||
value = strtol(raw, &endptr, 10);
|
||||
if (endptr == raw || value <= 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (*endptr == '\0' || strcmp(endptr, "ms") == 0) {
|
||||
*out_ms = (int) value;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(endptr, "s") == 0) {
|
||||
*out_ms = (int) (value * 1000L);
|
||||
return 0;
|
||||
}
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
double omni_duration_ms_to_ns(double ms) {
|
||||
return ms * 1000000.0;
|
||||
}
|
||||
|
||||
const char *omni_path_base_name(const char *path) {
|
||||
const char *slash;
|
||||
|
||||
if (path == NULL) {
|
||||
return "";
|
||||
}
|
||||
slash = strrchr(path, '/');
|
||||
return slash == NULL ? path : slash + 1;
|
||||
}
|
||||
|
||||
void omni_file_logger_init(omni_file_logger_t *logger, FILE *file) {
|
||||
logger->file = file;
|
||||
pthread_mutex_init(&logger->mutex, NULL);
|
||||
}
|
||||
|
||||
void omni_file_logger_destroy(omni_file_logger_t *logger) {
|
||||
pthread_mutex_destroy(&logger->mutex);
|
||||
}
|
||||
|
||||
int omni_file_logger_write_line(omni_file_logger_t *logger, const char *line) {
|
||||
int rc = 0;
|
||||
if (logger == NULL || logger->file == NULL || line == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&logger->mutex);
|
||||
if (fputs(line, logger->file) == EOF || fputc('\n', logger->file) == EOF || fflush(logger->file) != 0) {
|
||||
rc = -1;
|
||||
}
|
||||
pthread_mutex_unlock(&logger->mutex);
|
||||
return rc;
|
||||
}
|
||||
198
c/src/peer_kcp_client.c
Normal file
198
c/src/peer_kcp_client.c
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "peer_kcp_client.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct kcp_client {
|
||||
char id[OMNI_MAX_PEER_ID];
|
||||
char server_addr[OMNI_MAX_ADDR_TEXT];
|
||||
kcp_conn_t *conn;
|
||||
latency_logger_t *logger;
|
||||
pthread_mutex_t id_mu;
|
||||
uint64_t next_message_id;
|
||||
};
|
||||
|
||||
static int kcp_client_next_message_id(kcp_client_t *client, uint64_t *out_id) {
|
||||
pthread_mutex_lock(&client->id_mu);
|
||||
*out_id = ++client->next_message_id;
|
||||
pthread_mutex_unlock(&client->id_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_client_persist_message_to_disk(const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
char path[512];
|
||||
|
||||
if (omni_ensure_dir(inbox_dir) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (msg->type == MSG_TYPE_TEXT) {
|
||||
char *body = omni_json_escape_bytes(msg->body, msg->body_len);
|
||||
char *from = omni_json_escape(msg->from);
|
||||
char *to = omni_json_escape(msg->to);
|
||||
char *line;
|
||||
|
||||
if (body == NULL || from == NULL || to == NULL) {
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
return -1;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/messages.log", inbox_dir);
|
||||
line = omni_strdup_printf(
|
||||
"{\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"body\":\"%s\"}\n",
|
||||
protocol_message_type_name(msg->type),
|
||||
msg->id,
|
||||
from,
|
||||
to,
|
||||
body
|
||||
);
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_append_file(path, (const uint8_t *) line, strlen(line)) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
} else if (msg->type == MSG_TYPE_FILE) {
|
||||
const char *file_name = omni_path_base_name(msg->file_name);
|
||||
if (file_name[0] == '\0') {
|
||||
file_name = "unnamed";
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s-%" PRIu64 "-%s", inbox_dir, msg->from, msg->id, file_name);
|
||||
if (omni_write_file(path, msg->body, msg->body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out_path != NULL && out_path_len > 0) {
|
||||
snprintf(out_path, out_path_len, "%s", path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
kcp_client_t *kcp_client_dial(const char *server_addr, const char *dial_addr, const char *peer_id, const char *bind_ip, const char *bind_device, latency_logger_t *logger, kcp_packet_debug_logger_t *packet_logger, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms) {
|
||||
kcp_client_t *client;
|
||||
const char *actual_dial_addr = (dial_addr != NULL && dial_addr[0] != '\0') ? dial_addr : server_addr;
|
||||
message_t register_msg;
|
||||
|
||||
client = (kcp_client_t *) calloc(1, sizeof(*client));
|
||||
if (client == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
snprintf(client->id, sizeof(client->id), "%s", peer_id);
|
||||
snprintf(client->server_addr, sizeof(client->server_addr), "%s", server_addr == NULL ? "" : server_addr);
|
||||
pthread_mutex_init(&client->id_mu, NULL);
|
||||
client->logger = logger;
|
||||
client->conn = kcp_conn_dial(actual_dial_addr, bind_ip, bind_device, packet_logger, logger, OMNI_NODE_ROLE_PEER, peer_id, stats_logger, stats_interval_ms);
|
||||
if (client->conn == NULL) {
|
||||
kcp_client_free(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
protocol_message_init(®ister_msg);
|
||||
register_msg.type = MSG_TYPE_REGISTER;
|
||||
register_msg.id = 0;
|
||||
snprintf(register_msg.from, sizeof(register_msg.from), "%s", peer_id);
|
||||
snprintf(register_msg.to, sizeof(register_msg.to), "%s", SERVER_PEER_ID);
|
||||
if (kcp_conn_send(client->conn, ®ister_msg) != 0) {
|
||||
kcp_client_free(client);
|
||||
return NULL;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
const char *kcp_client_id(const kcp_client_t *client) {
|
||||
return client == NULL ? "" : client->id;
|
||||
}
|
||||
|
||||
int kcp_client_send_text(kcp_client_t *client, const char *to, const char *text) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
kcp_client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_TEXT;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
msg.body = (uint8_t *) omni_strdup(text);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
msg.body_len = strlen((const char *) msg.body);
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (kcp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_send_file_path(kcp_client_t *client, const char *to, const char *path) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
uint8_t *body = NULL;
|
||||
size_t body_len = 0;
|
||||
const char *base_name = strrchr(path, '/');
|
||||
|
||||
if (omni_read_file(path, &body, &body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
protocol_message_init(&msg);
|
||||
kcp_client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_FILE;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
snprintf(msg.file_name, sizeof(msg.file_name), "%s", base_name == NULL ? path : base_name + 1);
|
||||
msg.body = body;
|
||||
msg.body_len = body_len;
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (kcp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_receive(kcp_client_t *client, message_t *out_msg) {
|
||||
if (kcp_conn_receive(client->conn, out_msg) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_APP_RECV, out_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_persist_message(kcp_client_t *client, const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
if (!latencylog_is_business_message(msg)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_BEGIN, msg);
|
||||
if (kcp_client_persist_message_to_disk(msg, inbox_dir, out_path, out_path_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_END, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_close(kcp_client_t *client) {
|
||||
return client == NULL ? 0 : kcp_conn_close(client->conn);
|
||||
}
|
||||
|
||||
void kcp_client_free(kcp_client_t *client) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
kcp_conn_free(client->conn);
|
||||
pthread_mutex_destroy(&client->id_mu);
|
||||
free(client);
|
||||
}
|
||||
181
c/src/peer_udp_client.c
Normal file
181
c/src/peer_udp_client.c
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "peer_udp_client.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct udp_client {
|
||||
char id[OMNI_MAX_PEER_ID];
|
||||
udp_conn_t *conn;
|
||||
latency_logger_t *logger;
|
||||
pthread_mutex_t id_mu;
|
||||
uint64_t next_message_id;
|
||||
};
|
||||
|
||||
static int client_next_message_id(udp_client_t *client, uint64_t *out_id) {
|
||||
pthread_mutex_lock(&client->id_mu);
|
||||
*out_id = ++client->next_message_id;
|
||||
pthread_mutex_unlock(&client->id_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_persist_message_to_disk(const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
char path[512];
|
||||
if (omni_ensure_dir(inbox_dir) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (msg->type == MSG_TYPE_TEXT) {
|
||||
char *body = omni_json_escape_bytes(msg->body, msg->body_len);
|
||||
char *from = omni_json_escape(msg->from);
|
||||
char *to = omni_json_escape(msg->to);
|
||||
char *line;
|
||||
if (body == NULL || from == NULL || to == NULL) {
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
return -1;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/messages.log", inbox_dir);
|
||||
line = omni_strdup_printf("{\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"body\":\"%s\"}\n", protocol_message_type_name(msg->type), msg->id, from, to, body);
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_append_file(path, (const uint8_t *) line, strlen(line)) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
} else if (msg->type == MSG_TYPE_FILE) {
|
||||
const char *file_name = omni_path_base_name(msg->file_name);
|
||||
if (file_name[0] == '\0') {
|
||||
file_name = "unnamed";
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s-%" PRIu64 "-%s", inbox_dir, msg->from, msg->id, file_name);
|
||||
if (omni_write_file(path, msg->body, msg->body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (out_path != NULL && out_path_len > 0) {
|
||||
snprintf(out_path, out_path_len, "%s", path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_client_t *udp_client_dial(const char *server_addr, const char *peer_id, const char *bind_ip, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping) {
|
||||
udp_client_t *client;
|
||||
message_t register_msg;
|
||||
client = (udp_client_t *) calloc(1, sizeof(*client));
|
||||
if (client == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
snprintf(client->id, sizeof(client->id), "%s", peer_id);
|
||||
pthread_mutex_init(&client->id_mu, NULL);
|
||||
client->logger = logger;
|
||||
client->conn = udp_conn_dial(server_addr, bind_ip, NULL, enable_timestamping, logger, OMNI_NODE_ROLE_PEER, peer_id, debug_logger);
|
||||
if (client->conn == NULL) {
|
||||
udp_client_free(client);
|
||||
return NULL;
|
||||
}
|
||||
protocol_message_init(®ister_msg);
|
||||
register_msg.type = MSG_TYPE_REGISTER;
|
||||
register_msg.id = 0;
|
||||
snprintf(register_msg.from, sizeof(register_msg.from), "%s", peer_id);
|
||||
snprintf(register_msg.to, sizeof(register_msg.to), "%s", SERVER_PEER_ID);
|
||||
if (udp_conn_send(client->conn, ®ister_msg) != 0) {
|
||||
udp_client_free(client);
|
||||
return NULL;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
const char *udp_client_id(const udp_client_t *client) {
|
||||
return client == NULL ? "" : client->id;
|
||||
}
|
||||
|
||||
int udp_client_send_text(udp_client_t *client, const char *to, const char *text) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
protocol_message_init(&msg);
|
||||
client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_TEXT;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
msg.body = (uint8_t *) omni_strdup(text);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
msg.body_len = strlen((const char *) msg.body);
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (udp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_send_file_path(udp_client_t *client, const char *to, const char *path) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
uint8_t *body = NULL;
|
||||
size_t body_len = 0;
|
||||
const char *base_name = strrchr(path, '/');
|
||||
if (omni_read_file(path, &body, &body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
protocol_message_init(&msg);
|
||||
client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_FILE;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
snprintf(msg.file_name, sizeof(msg.file_name), "%s", base_name == NULL ? path : base_name + 1);
|
||||
msg.body = body;
|
||||
msg.body_len = body_len;
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (udp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_receive(udp_client_t *client, message_t *out_msg) {
|
||||
if (udp_conn_receive(client->conn, out_msg, NULL, NULL) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_APP_RECV, out_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_persist_message(udp_client_t *client, const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
if (!latencylog_is_business_message(msg)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_BEGIN, msg);
|
||||
if (client_persist_message_to_disk(msg, inbox_dir, out_path, out_path_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_END, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_close(udp_client_t *client) {
|
||||
return client == NULL ? 0 : udp_conn_close(client->conn);
|
||||
}
|
||||
|
||||
void udp_client_free(udp_client_t *client) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_conn_free(client->conn);
|
||||
pthread_mutex_destroy(&client->id_mu);
|
||||
free(client);
|
||||
}
|
||||
409
c/src/protocol.c
Normal file
409
c/src/protocol.c
Normal file
@@ -0,0 +1,409 @@
|
||||
#include "protocol.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
static const char *protocol_message_type_table[] = {
|
||||
"text",
|
||||
"file",
|
||||
"register",
|
||||
"error"
|
||||
};
|
||||
|
||||
const char *protocol_message_type_name(message_type_t type) {
|
||||
if ((int) type < 0 || type >= MSG_TYPE_INVALID) {
|
||||
return "invalid";
|
||||
}
|
||||
return protocol_message_type_table[type];
|
||||
}
|
||||
|
||||
int protocol_message_type_from_name(const char *raw, message_type_t *out) {
|
||||
size_t i;
|
||||
if (raw == NULL || out == NULL) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < OMNI_ARRAY_LEN(protocol_message_type_table); ++i) {
|
||||
if (strcmp(raw, protocol_message_type_table[i]) == 0) {
|
||||
*out = (message_type_t) i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void protocol_message_init(message_t *msg) {
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
msg->type = MSG_TYPE_INVALID;
|
||||
}
|
||||
|
||||
void protocol_message_clear(message_t *msg) {
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
free(msg->body);
|
||||
protocol_message_init(msg);
|
||||
}
|
||||
|
||||
int protocol_message_copy(message_t *dst, const message_t *src) {
|
||||
if (dst == NULL || src == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(dst);
|
||||
memcpy(dst, src, sizeof(*dst));
|
||||
dst->body = NULL;
|
||||
if (src->body_len > 0) {
|
||||
dst->body = (uint8_t *) malloc(src->body_len);
|
||||
if (dst->body == NULL) {
|
||||
protocol_message_init(dst);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst->body, src->body, src->body_len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_set_err(char *err, size_t err_len, const char *fmt, ...) {
|
||||
va_list args;
|
||||
if (err != NULL && err_len > 0) {
|
||||
va_start(args, fmt);
|
||||
vsnprintf(err, err_len, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int protocol_validate_message(const message_t *msg, char *err, size_t err_len) {
|
||||
if (msg == NULL) {
|
||||
return protocol_set_err(err, err_len, "protocol: nil message");
|
||||
}
|
||||
if (msg->from[0] == '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: missing from");
|
||||
}
|
||||
if (msg->to[0] == '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: missing to");
|
||||
}
|
||||
switch (msg->type) {
|
||||
case MSG_TYPE_TEXT:
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
if (!omni_utf8_valid(msg->body, msg->body_len)) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid text body");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_FILE:
|
||||
if (msg->file_name[0] == '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: missing file name");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_REGISTER:
|
||||
if (strcmp(msg->to, SERVER_PEER_ID) != 0) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid register target");
|
||||
}
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
if (msg->body_len != 0) {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected body");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_ERROR:
|
||||
if (strcmp(msg->from, SERVER_PEER_ID) != 0) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid error source");
|
||||
}
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
if (!omni_utf8_valid(msg->body, msg->body_len)) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid text body");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return protocol_set_err(err, err_len, "protocol: invalid message type");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_build_header_json(const message_t *msg, char **out_json, size_t *out_len) {
|
||||
cJSON *root;
|
||||
char *json;
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddStringToObject(root, "type", protocol_message_type_name(msg->type));
|
||||
cJSON_AddNumberToObject(root, "id", (double) msg->id);
|
||||
cJSON_AddStringToObject(root, "from", msg->from);
|
||||
cJSON_AddStringToObject(root, "to", msg->to);
|
||||
if (msg->file_name[0] != '\0') {
|
||||
cJSON_AddStringToObject(root, "file_name", msg->file_name);
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "content_length", (double) msg->body_len);
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (json == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
*out_len = strlen(json);
|
||||
*out_json = json;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_encode_message_datagram(const message_t *msg, uint8_t **out, size_t *out_len) {
|
||||
uint8_t *buffer;
|
||||
char *header_json;
|
||||
size_t header_len;
|
||||
uint32_t net_header_len;
|
||||
char err[128];
|
||||
|
||||
if (out == NULL || out_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out = NULL;
|
||||
*out_len = 0;
|
||||
if (protocol_validate_message(msg, err, sizeof(err)) != 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (protocol_build_header_json(msg, &header_json, &header_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (4U + header_len + msg->body_len > OMNI_MAX_FRAME_SIZE) {
|
||||
cJSON_free(header_json);
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
buffer = (uint8_t *) malloc(4U + header_len + msg->body_len);
|
||||
if (buffer == NULL) {
|
||||
cJSON_free(header_json);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
net_header_len = htonl((uint32_t) header_len);
|
||||
memcpy(buffer, &net_header_len, 4);
|
||||
memcpy(buffer + 4, header_json, header_len);
|
||||
if (msg->body_len > 0) {
|
||||
memcpy(buffer + 4 + header_len, msg->body, msg->body_len);
|
||||
}
|
||||
cJSON_free(header_json);
|
||||
*out = buffer;
|
||||
*out_len = 4U + header_len + msg->body_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_copy_string_field(char *dst, size_t dst_len, const cJSON *object, const char *field, int required, char *err, size_t err_len) {
|
||||
const cJSON *item = cJSON_GetObjectItemCaseSensitive(object, field);
|
||||
if (item == NULL) {
|
||||
if (required) {
|
||||
return protocol_set_err(err, err_len, "protocol: missing %s", field);
|
||||
}
|
||||
dst[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
if (!cJSON_IsString(item) || item->valuestring == NULL) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid %s", field);
|
||||
}
|
||||
snprintf(dst, dst_len, "%s", item->valuestring);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_copy_u64_field(uint64_t *dst, const cJSON *object, const char *field, int required, char *err, size_t err_len) {
|
||||
const cJSON *item = cJSON_GetObjectItemCaseSensitive(object, field);
|
||||
if (item == NULL) {
|
||||
if (required) {
|
||||
return protocol_set_err(err, err_len, "protocol: missing %s", field);
|
||||
}
|
||||
*dst = 0;
|
||||
return 0;
|
||||
}
|
||||
if (!cJSON_IsNumber(item)) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid %s", field);
|
||||
}
|
||||
*dst = (uint64_t) item->valuedouble;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_decode_message_datagram(const uint8_t *data, size_t data_len, message_t *out_msg, char *err, size_t err_len) {
|
||||
uint32_t net_header_len;
|
||||
uint32_t header_len;
|
||||
char *header_text = NULL;
|
||||
cJSON *header = NULL;
|
||||
const cJSON *type_item;
|
||||
uint64_t content_length = 0;
|
||||
|
||||
if (data == NULL || out_msg == NULL || data_len < 4U) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid datagram");
|
||||
}
|
||||
if (data_len > OMNI_MAX_FRAME_SIZE) {
|
||||
return protocol_set_err(err, err_len, "protocol: frame too large");
|
||||
}
|
||||
|
||||
protocol_message_clear(out_msg);
|
||||
|
||||
memcpy(&net_header_len, data, 4);
|
||||
header_len = ntohl(net_header_len);
|
||||
if (header_len == 0 || (size_t) header_len > data_len - 4U) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid header length");
|
||||
}
|
||||
header_text = (char *) malloc((size_t) header_len + 1U);
|
||||
if (header_text == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(header_text, data + 4, header_len);
|
||||
header_text[header_len] = '\0';
|
||||
header = cJSON_Parse(header_text);
|
||||
free(header_text);
|
||||
if (header == NULL || !cJSON_IsObject(header)) {
|
||||
if (header != NULL) {
|
||||
cJSON_Delete(header);
|
||||
}
|
||||
return protocol_set_err(err, err_len, "protocol: invalid header json");
|
||||
}
|
||||
type_item = cJSON_GetObjectItemCaseSensitive(header, "type");
|
||||
if (type_item == NULL || !cJSON_IsString(type_item) || protocol_message_type_from_name(type_item->valuestring, &out_msg->type) != 0) {
|
||||
cJSON_Delete(header);
|
||||
return protocol_set_err(err, err_len, "protocol: invalid message type");
|
||||
}
|
||||
if (protocol_copy_u64_field(&out_msg->id, header, "id", 1, err, err_len) != 0 ||
|
||||
protocol_copy_string_field(out_msg->from, sizeof(out_msg->from), header, "from", 1, err, err_len) != 0 ||
|
||||
protocol_copy_string_field(out_msg->to, sizeof(out_msg->to), header, "to", 1, err, err_len) != 0 ||
|
||||
protocol_copy_string_field(out_msg->file_name, sizeof(out_msg->file_name), header, "file_name", 0, err, err_len) != 0 ||
|
||||
protocol_copy_u64_field(&content_length, header, "content_length", 1, err, err_len) != 0) {
|
||||
cJSON_Delete(header);
|
||||
protocol_message_clear(out_msg);
|
||||
return -1;
|
||||
}
|
||||
cJSON_Delete(header);
|
||||
if ((size_t) content_length != data_len - 4U - (size_t) header_len) {
|
||||
protocol_message_clear(out_msg);
|
||||
return protocol_set_err(err, err_len, "protocol: invalid content length");
|
||||
}
|
||||
out_msg->body_len = (size_t) content_length;
|
||||
if (out_msg->body_len > 0) {
|
||||
out_msg->body = (uint8_t *) malloc(out_msg->body_len);
|
||||
if (out_msg->body == NULL) {
|
||||
protocol_message_clear(out_msg);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(out_msg->body, data + 4U + header_len, out_msg->body_len);
|
||||
}
|
||||
if (protocol_validate_message(out_msg, err, err_len) != 0) {
|
||||
protocol_message_clear(out_msg);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_encode_message_stream(const message_t *msg, uint8_t **out, size_t *out_len) {
|
||||
uint8_t *payload;
|
||||
uint8_t *buffer;
|
||||
size_t payload_len;
|
||||
uint32_t net_len;
|
||||
|
||||
if (protocol_encode_message_datagram(msg, &payload, &payload_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
buffer = (uint8_t *) malloc(payload_len + 4U);
|
||||
if (buffer == NULL) {
|
||||
free(payload);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
net_len = htonl((uint32_t) payload_len);
|
||||
memcpy(buffer, &net_len, 4);
|
||||
memcpy(buffer + 4, payload, payload_len);
|
||||
free(payload);
|
||||
*out = buffer;
|
||||
*out_len = payload_len + 4U;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_decode_message_stream_payload(const uint8_t *payload, size_t payload_len, message_t *out_msg, char *err, size_t err_len) {
|
||||
return protocol_decode_message_datagram(payload, payload_len, out_msg, err, err_len);
|
||||
}
|
||||
|
||||
void protocol_frame_decoder_init(protocol_frame_decoder_t *decoder) {
|
||||
memset(decoder, 0, sizeof(*decoder));
|
||||
}
|
||||
|
||||
void protocol_frame_decoder_reset(protocol_frame_decoder_t *decoder) {
|
||||
decoder->len = 0;
|
||||
}
|
||||
|
||||
void protocol_frame_decoder_destroy(protocol_frame_decoder_t *decoder) {
|
||||
free(decoder->buffer);
|
||||
memset(decoder, 0, sizeof(*decoder));
|
||||
}
|
||||
|
||||
int protocol_frame_decoder_feed(protocol_frame_decoder_t *decoder, const uint8_t *data, size_t data_len) {
|
||||
uint8_t *next_buffer;
|
||||
size_t next_cap;
|
||||
if (decoder->len + data_len > OMNI_MAX_FRAME_SIZE * 2U) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
if (decoder->len + data_len > decoder->cap) {
|
||||
next_cap = decoder->cap == 0 ? 4096U : decoder->cap;
|
||||
while (next_cap < decoder->len + data_len) {
|
||||
next_cap *= 2U;
|
||||
}
|
||||
next_buffer = (uint8_t *) realloc(decoder->buffer, next_cap);
|
||||
if (next_buffer == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
decoder->buffer = next_buffer;
|
||||
decoder->cap = next_cap;
|
||||
}
|
||||
memcpy(decoder->buffer + decoder->len, data, data_len);
|
||||
decoder->len += data_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_frame_decoder_next(protocol_frame_decoder_t *decoder, uint8_t **payload, size_t *payload_len) {
|
||||
uint32_t net_len;
|
||||
uint32_t frame_len;
|
||||
uint8_t *frame;
|
||||
|
||||
if (payload == NULL || payload_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*payload = NULL;
|
||||
*payload_len = 0;
|
||||
if (decoder->len < 4U) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(&net_len, decoder->buffer, 4);
|
||||
frame_len = ntohl(net_len);
|
||||
if (frame_len == 0 || frame_len > OMNI_MAX_FRAME_SIZE) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
if (decoder->len < 4U + frame_len) {
|
||||
return 0;
|
||||
}
|
||||
frame = (uint8_t *) malloc(frame_len);
|
||||
if (frame == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(frame, decoder->buffer + 4, frame_len);
|
||||
memmove(decoder->buffer, decoder->buffer + 4U + frame_len, decoder->len - 4U - frame_len);
|
||||
decoder->len -= 4U + frame_len;
|
||||
*payload = frame;
|
||||
*payload_len = frame_len;
|
||||
return 1;
|
||||
}
|
||||
562
c/src/server_kcp_hub.c
Normal file
562
c/src/server_kcp_hub.c
Normal file
@@ -0,0 +1,562 @@
|
||||
#include "server_kcp_hub.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#define KCP_RELAY_MAX_DATAGRAM_SIZE (60 * 1024)
|
||||
|
||||
typedef struct kcp_peer_entry {
|
||||
struct kcp_peer_entry *next;
|
||||
char peer_id[OMNI_MAX_PEER_ID];
|
||||
kcp_conn_t *conn;
|
||||
} kcp_peer_entry_t;
|
||||
|
||||
typedef struct kcp_session_thread_ctx {
|
||||
kcp_hub_t *hub;
|
||||
kcp_conn_t *conn;
|
||||
} kcp_session_thread_ctx_t;
|
||||
|
||||
struct kcp_hub {
|
||||
pthread_rwlock_t lock;
|
||||
kcp_peer_entry_t *peers;
|
||||
latency_logger_t *logger;
|
||||
kcp_session_stats_logger_t *stats_logger;
|
||||
int stats_interval_ms;
|
||||
int relay_fd;
|
||||
int relay_configured;
|
||||
int relay_learn_peer;
|
||||
struct sockaddr_storage relay_peer_addr;
|
||||
socklen_t relay_peer_addr_len;
|
||||
int closed;
|
||||
};
|
||||
|
||||
static void kcp_hub_unregister(kcp_hub_t *hub, const char *peer_id, kcp_conn_t *conn) {
|
||||
kcp_peer_entry_t *prev = NULL;
|
||||
kcp_peer_entry_t *entry;
|
||||
|
||||
if (hub == NULL || peer_id == NULL || peer_id[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_rwlock_wrlock(&hub->lock);
|
||||
for (entry = hub->peers; entry != NULL; entry = entry->next) {
|
||||
if (strcmp(entry->peer_id, peer_id) == 0 && entry->conn == conn) {
|
||||
if (prev == NULL) {
|
||||
hub->peers = entry->next;
|
||||
} else {
|
||||
prev->next = entry->next;
|
||||
}
|
||||
free(entry);
|
||||
break;
|
||||
}
|
||||
prev = entry;
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
}
|
||||
|
||||
static kcp_peer_entry_t *kcp_hub_find_peer(kcp_hub_t *hub, const char *peer_id) {
|
||||
kcp_peer_entry_t *entry;
|
||||
for (entry = hub->peers; entry != NULL; entry = entry->next) {
|
||||
if (strcmp(entry->peer_id, peer_id) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int kcp_hub_send_server_error(kcp_conn_t *conn, const char *to, const char *message) {
|
||||
message_t msg;
|
||||
protocol_message_init(&msg);
|
||||
msg.type = MSG_TYPE_ERROR;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", SERVER_PEER_ID);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", (to == NULL || to[0] == '\0') ? "unknown" : to);
|
||||
msg.body = (uint8_t *) omni_strdup(message == NULL ? "" : message);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
msg.body_len = strlen((const char *) msg.body);
|
||||
if (kcp_conn_send(conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_hub_sockaddr_equal(const struct sockaddr *left, socklen_t left_len, const struct sockaddr *right, socklen_t right_len) {
|
||||
char left_text[OMNI_MAX_ADDR_TEXT];
|
||||
char right_text[OMNI_MAX_ADDR_TEXT];
|
||||
|
||||
if (left == NULL || right == NULL) {
|
||||
return left == right;
|
||||
}
|
||||
return strcmp(
|
||||
omni_sockaddr_to_string(left, left_len, left_text, sizeof(left_text)),
|
||||
omni_sockaddr_to_string(right, right_len, right_text, sizeof(right_text))
|
||||
) == 0;
|
||||
}
|
||||
|
||||
static int kcp_hub_accept_relay_peer(kcp_hub_t *hub, const struct sockaddr *addr, socklen_t addr_len) {
|
||||
int accepted = 0;
|
||||
|
||||
pthread_rwlock_wrlock(&hub->lock);
|
||||
if (hub->relay_peer_addr_len == 0 && hub->relay_learn_peer) {
|
||||
omni_clone_sockaddr(addr, addr_len, &hub->relay_peer_addr, &hub->relay_peer_addr_len);
|
||||
accepted = 1;
|
||||
} else if (hub->relay_peer_addr_len == 0) {
|
||||
accepted = 1;
|
||||
} else {
|
||||
accepted = kcp_hub_sockaddr_equal((const struct sockaddr *) &hub->relay_peer_addr, hub->relay_peer_addr_len, addr, addr_len);
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
return accepted;
|
||||
}
|
||||
|
||||
static int kcp_hub_forward_to_relay(kcp_hub_t *hub, const message_t *msg, int *relay_status) {
|
||||
uint8_t *payload = NULL;
|
||||
size_t payload_len = 0;
|
||||
struct sockaddr_storage relay_addr;
|
||||
socklen_t relay_addr_len = 0;
|
||||
int relay_fd = -1;
|
||||
int relay_configured = 0;
|
||||
|
||||
if (relay_status != NULL) {
|
||||
*relay_status = 0;
|
||||
}
|
||||
if (protocol_encode_message_datagram(msg, &payload, &payload_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (payload_len > KCP_RELAY_MAX_DATAGRAM_SIZE) {
|
||||
free(payload);
|
||||
errno = EMSGSIZE;
|
||||
if (relay_status != NULL) {
|
||||
*relay_status = 3;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_rwlock_rdlock(&hub->lock);
|
||||
relay_fd = hub->relay_fd;
|
||||
relay_configured = hub->relay_configured;
|
||||
if (hub->relay_peer_addr_len > 0) {
|
||||
omni_clone_sockaddr((const struct sockaddr *) &hub->relay_peer_addr, hub->relay_peer_addr_len, &relay_addr, &relay_addr_len);
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
|
||||
if (!relay_configured || relay_fd < 0) {
|
||||
free(payload);
|
||||
errno = ENOTCONN;
|
||||
if (relay_status != NULL) {
|
||||
*relay_status = 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (relay_addr_len == 0) {
|
||||
free(payload);
|
||||
errno = EDESTADDRREQ;
|
||||
if (relay_status != NULL) {
|
||||
*relay_status = 2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (sendto(relay_fd, payload, payload_len, 0, (struct sockaddr *) &relay_addr, relay_addr_len) < 0) {
|
||||
free(payload);
|
||||
return -1;
|
||||
}
|
||||
free(payload);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_hub_forward_relay_server_error(kcp_hub_t *hub, const char *to, const char *message) {
|
||||
message_t msg;
|
||||
int rc;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
msg.type = MSG_TYPE_ERROR;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", SERVER_PEER_ID);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", (to == NULL || to[0] == '\0') ? "unknown" : to);
|
||||
msg.body = (uint8_t *) omni_strdup(message == NULL ? "" : message);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
msg.body_len = strlen((const char *) msg.body);
|
||||
rc = kcp_hub_forward_to_relay(hub, &msg, NULL);
|
||||
protocol_message_clear(&msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int kcp_hub_deliver_to_local_peer(kcp_hub_t *hub, const message_t *msg) {
|
||||
kcp_conn_t *target_conn = NULL;
|
||||
int rc;
|
||||
|
||||
pthread_rwlock_rdlock(&hub->lock);
|
||||
{
|
||||
kcp_peer_entry_t *entry = kcp_hub_find_peer(hub, msg->to);
|
||||
if (entry != NULL) {
|
||||
target_conn = entry->conn;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
|
||||
if (target_conn == NULL) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
rc = kcp_conn_send(target_conn, msg);
|
||||
if (rc != 0) {
|
||||
kcp_hub_unregister(hub, msg->to, target_conn);
|
||||
kcp_conn_close(target_conn);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_hub_deliver_relayed_message(kcp_hub_t *hub, const message_t *msg) {
|
||||
char *error_text;
|
||||
|
||||
if (kcp_hub_deliver_to_local_peer(hub, msg) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (errno != ENOENT) {
|
||||
if (msg->type == MSG_TYPE_ERROR) {
|
||||
return 0;
|
||||
}
|
||||
error_text = omni_strdup_printf("failed to forward to %s", msg->to);
|
||||
if (error_text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (kcp_hub_forward_relay_server_error(hub, msg->from, error_text) != 0) {
|
||||
free(error_text);
|
||||
return -1;
|
||||
}
|
||||
free(error_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msg->type == MSG_TYPE_ERROR) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
error_text = omni_strdup_printf("unknown target: %s", msg->to);
|
||||
if (error_text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (kcp_hub_forward_relay_server_error(hub, msg->from, error_text) != 0) {
|
||||
free(error_text);
|
||||
return -1;
|
||||
}
|
||||
free(error_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_hub_handle_peer_message(kcp_hub_t *hub, const char *peer_id, kcp_conn_t *conn, message_t *msg) {
|
||||
char *error_text = NULL;
|
||||
int relay_status = 0;
|
||||
|
||||
switch (msg->type) {
|
||||
case MSG_TYPE_TEXT:
|
||||
case MSG_TYPE_FILE:
|
||||
snprintf(msg->from, sizeof(msg->from), "%s", peer_id);
|
||||
if (kcp_hub_deliver_to_local_peer(hub, msg) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (errno != ENOENT) {
|
||||
error_text = omni_strdup_printf("failed to forward to %s", msg->to);
|
||||
if (error_text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (kcp_hub_send_server_error(conn, peer_id, error_text) != 0) {
|
||||
free(error_text);
|
||||
return -1;
|
||||
}
|
||||
free(error_text);
|
||||
return 0;
|
||||
}
|
||||
if (kcp_hub_forward_to_relay(hub, msg, &relay_status) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (relay_status == 1) {
|
||||
error_text = omni_strdup_printf("unknown target: %s", msg->to);
|
||||
} else if (relay_status == 2) {
|
||||
error_text = omni_strdup("failed to relay to remote peer");
|
||||
} else if (relay_status == 3) {
|
||||
error_text = omni_strdup("message too large for relay udp");
|
||||
} else {
|
||||
error_text = omni_strdup("failed to relay to remote peer");
|
||||
}
|
||||
if (error_text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (kcp_hub_send_server_error(conn, peer_id, error_text) != 0) {
|
||||
free(error_text);
|
||||
return -1;
|
||||
}
|
||||
free(error_text);
|
||||
return 0;
|
||||
case MSG_TYPE_REGISTER:
|
||||
case MSG_TYPE_ERROR:
|
||||
if (kcp_hub_send_server_error(conn, peer_id, "registered peers can only send text or file messages") != 0) {
|
||||
return -1;
|
||||
}
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
default:
|
||||
error_text = omni_strdup_printf("unsupported message type: %s", protocol_message_type_name(msg->type));
|
||||
if (error_text == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (kcp_hub_send_server_error(conn, peer_id, error_text) != 0) {
|
||||
free(error_text);
|
||||
return -1;
|
||||
}
|
||||
free(error_text);
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int kcp_hub_register_conn(kcp_hub_t *hub, kcp_conn_t *conn, char *peer_id, size_t peer_id_len) {
|
||||
message_t msg;
|
||||
kcp_peer_entry_t *entry;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_conn_receive(conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
if (msg.type != MSG_TYPE_REGISTER) {
|
||||
kcp_hub_send_server_error(conn, msg.from, "first message must be register");
|
||||
protocol_message_clear(&msg);
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_rwlock_wrlock(&hub->lock);
|
||||
entry = kcp_hub_find_peer(hub, msg.from);
|
||||
if (entry != NULL) {
|
||||
char *error_text;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
error_text = omni_strdup_printf("duplicate peer id: %s", msg.from);
|
||||
if (error_text != NULL) {
|
||||
(void) kcp_hub_send_server_error(conn, msg.from, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
errno = EEXIST;
|
||||
return -1;
|
||||
}
|
||||
|
||||
entry = (kcp_peer_entry_t *) calloc(1, sizeof(*entry));
|
||||
if (entry == NULL) {
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
snprintf(entry->peer_id, sizeof(entry->peer_id), "%s", msg.from);
|
||||
entry->conn = conn;
|
||||
entry->next = hub->peers;
|
||||
hub->peers = entry;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
|
||||
snprintf(peer_id, peer_id_len, "%s", msg.from);
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *kcp_hub_session_thread_main(void *arg) {
|
||||
kcp_session_thread_ctx_t *ctx = (kcp_session_thread_ctx_t *) arg;
|
||||
kcp_hub_serve_session(ctx->hub, ctx->conn);
|
||||
free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
kcp_hub_t *kcp_hub_new(latency_logger_t *logger, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms) {
|
||||
kcp_hub_t *hub = (kcp_hub_t *) calloc(1, sizeof(*hub));
|
||||
if (hub == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
pthread_rwlock_init(&hub->lock, NULL);
|
||||
hub->logger = logger;
|
||||
hub->stats_logger = stats_logger;
|
||||
hub->stats_interval_ms = stats_interval_ms > 0 ? stats_interval_ms : KCP_DEFAULT_STATS_INTERVAL_MS;
|
||||
hub->relay_fd = -1;
|
||||
return hub;
|
||||
}
|
||||
|
||||
int kcp_hub_serve_listener(kcp_hub_t *hub, kcp_listener_t *listener) {
|
||||
if (hub == NULL || listener == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
while (!hub->closed) {
|
||||
kcp_conn_t *conn = kcp_listener_accept(listener);
|
||||
kcp_session_thread_ctx_t *ctx;
|
||||
pthread_t thread;
|
||||
|
||||
if (conn == NULL) {
|
||||
if (hub->closed) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
ctx = (kcp_session_thread_ctx_t *) calloc(1, sizeof(*ctx));
|
||||
if (ctx == NULL) {
|
||||
kcp_conn_close(conn);
|
||||
kcp_conn_free(conn);
|
||||
return -1;
|
||||
}
|
||||
ctx->hub = hub;
|
||||
ctx->conn = conn;
|
||||
if (pthread_create(&thread, NULL, kcp_hub_session_thread_main, ctx) != 0) {
|
||||
free(ctx);
|
||||
kcp_conn_close(conn);
|
||||
kcp_conn_free(conn);
|
||||
return -1;
|
||||
}
|
||||
pthread_detach(thread);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_hub_serve_session(kcp_hub_t *hub, kcp_conn_t *conn) {
|
||||
char peer_id[OMNI_MAX_PEER_ID];
|
||||
int rc = 0;
|
||||
|
||||
if (hub == NULL || conn == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
peer_id[0] = '\0';
|
||||
if (kcp_conn_configure_runtime(conn, hub->logger, OMNI_NODE_ROLE_SERVER, "hub", hub->stats_logger, hub->stats_interval_ms) != 0) {
|
||||
kcp_conn_close(conn);
|
||||
kcp_conn_free(conn);
|
||||
return -1;
|
||||
}
|
||||
if (kcp_hub_register_conn(hub, conn, peer_id, sizeof(peer_id)) != 0) {
|
||||
kcp_conn_close(conn);
|
||||
kcp_conn_free(conn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_conn_receive(conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
if (kcp_hub_handle_peer_message(hub, peer_id, conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
|
||||
kcp_hub_unregister(hub, peer_id, conn);
|
||||
kcp_conn_close(conn);
|
||||
kcp_conn_free(conn);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int kcp_hub_set_relay(kcp_hub_t *hub, int relay_fd, const struct sockaddr *peer_addr, socklen_t peer_addr_len, int learn_peer) {
|
||||
if (hub == NULL || relay_fd < 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
pthread_rwlock_wrlock(&hub->lock);
|
||||
hub->relay_fd = relay_fd;
|
||||
hub->relay_configured = 1;
|
||||
hub->relay_learn_peer = learn_peer;
|
||||
hub->relay_peer_addr_len = 0;
|
||||
if (peer_addr != NULL && peer_addr_len > 0) {
|
||||
omni_clone_sockaddr(peer_addr, peer_addr_len, &hub->relay_peer_addr, &hub->relay_peer_addr_len);
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_hub_serve_relay(kcp_hub_t *hub) {
|
||||
uint8_t buffer[KCP_RELAY_MAX_DATAGRAM_SIZE];
|
||||
|
||||
if (hub == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
while (!hub->closed) {
|
||||
struct sockaddr_storage source;
|
||||
socklen_t source_len = sizeof(source);
|
||||
ssize_t n;
|
||||
message_t msg;
|
||||
char err[128];
|
||||
int relay_fd;
|
||||
|
||||
pthread_rwlock_rdlock(&hub->lock);
|
||||
relay_fd = hub->relay_fd;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
if (relay_fd < 0) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = recvfrom(relay_fd, buffer, sizeof(buffer), 0, (struct sockaddr *) &source, &source_len);
|
||||
if (n < 0) {
|
||||
if (hub->closed) {
|
||||
return 0;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!kcp_hub_accept_relay_peer(hub, (struct sockaddr *) &source, source_len)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (protocol_decode_message_datagram(buffer, (size_t) n, &msg, err, sizeof(err)) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
continue;
|
||||
}
|
||||
if (msg.type != MSG_TYPE_TEXT && msg.type != MSG_TYPE_FILE && msg.type != MSG_TYPE_ERROR) {
|
||||
protocol_message_clear(&msg);
|
||||
continue;
|
||||
}
|
||||
(void) kcp_hub_deliver_relayed_message(hub, &msg);
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_hub_close(kcp_hub_t *hub) {
|
||||
if (hub == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (!hub->closed) {
|
||||
hub->closed = 1;
|
||||
if (hub->relay_fd >= 0) {
|
||||
close(hub->relay_fd);
|
||||
hub->relay_fd = -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void kcp_hub_free(kcp_hub_t *hub) {
|
||||
kcp_peer_entry_t *entry;
|
||||
kcp_peer_entry_t *next;
|
||||
|
||||
if (hub == NULL) {
|
||||
return;
|
||||
}
|
||||
kcp_hub_close(hub);
|
||||
for (entry = hub->peers; entry != NULL; entry = next) {
|
||||
next = entry->next;
|
||||
if (entry->conn != NULL) {
|
||||
kcp_conn_close(entry->conn);
|
||||
}
|
||||
free(entry);
|
||||
}
|
||||
pthread_rwlock_destroy(&hub->lock);
|
||||
free(hub);
|
||||
}
|
||||
181
c/src/server_udp_hub.c
Normal file
181
c/src/server_udp_hub.c
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "server_udp_hub.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct udp_peer_entry {
|
||||
struct udp_peer_entry *next;
|
||||
char peer_id[OMNI_MAX_PEER_ID];
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len;
|
||||
} udp_peer_entry_t;
|
||||
|
||||
struct udp_hub {
|
||||
udp_conn_t *conn;
|
||||
pthread_rwlock_t lock;
|
||||
udp_peer_entry_t *peers;
|
||||
};
|
||||
|
||||
static int udp_addr_equal(const struct sockaddr_storage *a, socklen_t a_len, const struct sockaddr_storage *b, socklen_t b_len) {
|
||||
return a_len == b_len && memcmp(a, b, a_len) == 0;
|
||||
}
|
||||
|
||||
static udp_peer_entry_t *udp_hub_find_by_id(udp_hub_t *hub, const char *peer_id) {
|
||||
udp_peer_entry_t *entry;
|
||||
for (entry = hub->peers; entry != NULL; entry = entry->next) {
|
||||
if (strcmp(entry->peer_id, peer_id) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static udp_peer_entry_t *udp_hub_find_by_addr(udp_hub_t *hub, const struct sockaddr_storage *addr, socklen_t addr_len) {
|
||||
udp_peer_entry_t *entry;
|
||||
for (entry = hub->peers; entry != NULL; entry = entry->next) {
|
||||
if (udp_addr_equal(&entry->addr, entry->addr_len, addr, addr_len)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int udp_hub_send_error(udp_hub_t *hub, const struct sockaddr_storage *addr, socklen_t addr_len, const char *to, const char *message) {
|
||||
message_t msg;
|
||||
protocol_message_init(&msg);
|
||||
msg.type = MSG_TYPE_ERROR;
|
||||
msg.id = 0;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", SERVER_PEER_ID);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to == NULL || to[0] == '\0' ? "unknown" : to);
|
||||
msg.body = (uint8_t *) omni_strdup(message);
|
||||
msg.body_len = msg.body == NULL ? 0 : strlen((const char *) msg.body);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (udp_conn_send_to(hub->conn, &msg, (const struct sockaddr *) addr, addr_len) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_hub_t *udp_hub_open(const char *listen_addr, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping) {
|
||||
udp_hub_t *hub = (udp_hub_t *) calloc(1, sizeof(*hub));
|
||||
if (hub == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
hub->conn = udp_conn_bind(listen_addr, NULL, enable_timestamping, logger, OMNI_NODE_ROLE_SERVER, "hub", debug_logger);
|
||||
if (hub->conn == NULL) {
|
||||
free(hub);
|
||||
return NULL;
|
||||
}
|
||||
pthread_rwlock_init(&hub->lock, NULL);
|
||||
return hub;
|
||||
}
|
||||
|
||||
int udp_hub_serve(udp_hub_t *hub) {
|
||||
message_t msg;
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len;
|
||||
udp_peer_entry_t *sender;
|
||||
udp_peer_entry_t *target;
|
||||
udp_peer_entry_t *entry;
|
||||
|
||||
if (hub == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
for (;;) {
|
||||
protocol_message_clear(&msg);
|
||||
if (udp_conn_receive(hub->conn, &msg, &addr, &addr_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (msg.type == MSG_TYPE_REGISTER) {
|
||||
pthread_rwlock_wrlock(&hub->lock);
|
||||
entry = udp_hub_find_by_id(hub, msg.from);
|
||||
if (entry == NULL) {
|
||||
entry = (udp_peer_entry_t *) calloc(1, sizeof(*entry));
|
||||
if (entry == NULL) {
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
snprintf(entry->peer_id, sizeof(entry->peer_id), "%s", msg.from);
|
||||
entry->next = hub->peers;
|
||||
hub->peers = entry;
|
||||
}
|
||||
memcpy(&entry->addr, &addr, sizeof(addr));
|
||||
entry->addr_len = addr_len;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
continue;
|
||||
}
|
||||
if (msg.type != MSG_TYPE_TEXT && msg.type != MSG_TYPE_FILE) {
|
||||
if (msg.type == MSG_TYPE_ERROR) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, msg.from, "peers cannot send error messages");
|
||||
} else {
|
||||
char *error_text = omni_strdup_printf("unsupported message type: %s", protocol_message_type_name(msg.type));
|
||||
if (error_text != NULL) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, msg.from, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_rwlock_rdlock(&hub->lock);
|
||||
sender = udp_hub_find_by_addr(hub, &addr, addr_len);
|
||||
if (sender == NULL) {
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
udp_hub_send_error(hub, &addr, addr_len, msg.from, "not registered; send register first");
|
||||
continue;
|
||||
}
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", sender->peer_id);
|
||||
target = udp_hub_find_by_id(hub, msg.to);
|
||||
if (target == NULL) {
|
||||
char *error_text;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
error_text = omni_strdup_printf("unknown target: %s", msg.to);
|
||||
if (error_text != NULL) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, sender->peer_id, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (udp_conn_send_to(hub->conn, &msg, (const struct sockaddr *) &target->addr, target->addr_len) != 0) {
|
||||
char *error_text;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
error_text = omni_strdup_printf("failed to forward to %s", msg.to);
|
||||
if (error_text != NULL) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, sender->peer_id, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
}
|
||||
}
|
||||
|
||||
int udp_hub_close(udp_hub_t *hub) {
|
||||
if (hub == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return udp_conn_close(hub->conn);
|
||||
}
|
||||
|
||||
void udp_hub_free(udp_hub_t *hub) {
|
||||
udp_peer_entry_t *entry;
|
||||
udp_peer_entry_t *next;
|
||||
if (hub == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_conn_free(hub->conn);
|
||||
for (entry = hub->peers; entry != NULL; entry = next) {
|
||||
next = entry->next;
|
||||
free(entry);
|
||||
}
|
||||
pthread_rwlock_destroy(&hub->lock);
|
||||
free(hub);
|
||||
}
|
||||
311
c/src/server_udp_relay.c
Normal file
311
c/src/server_udp_relay.c
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "server_udp_relay.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define UDP_RELAY_BUF_SIZE (64U * 1024U)
|
||||
|
||||
struct udp_relay {
|
||||
int downstream_fd;
|
||||
int upstream_fd;
|
||||
struct sockaddr_storage upstream_addr;
|
||||
socklen_t upstream_addr_len;
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len;
|
||||
int has_client;
|
||||
pthread_mutex_t lock;
|
||||
pthread_mutex_t state_mu;
|
||||
pthread_cond_t state_cond;
|
||||
pthread_t downstream_thread;
|
||||
int downstream_thread_started;
|
||||
pthread_t upstream_thread;
|
||||
int upstream_thread_started;
|
||||
int worker_done;
|
||||
int worker_rc;
|
||||
int worker_errno;
|
||||
int closed;
|
||||
};
|
||||
|
||||
static int udp_relay_is_closed(udp_relay_t *relay) {
|
||||
int closed;
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
closed = relay->closed;
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
return closed;
|
||||
}
|
||||
|
||||
static void udp_relay_note_result(udp_relay_t *relay, int rc, int errnum) {
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
if (!relay->worker_done) {
|
||||
relay->worker_done = 1;
|
||||
relay->worker_rc = rc;
|
||||
relay->worker_errno = errnum;
|
||||
pthread_cond_signal(&relay->state_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
}
|
||||
|
||||
static void udp_relay_record_client(udp_relay_t *relay, const struct sockaddr_storage *addr, socklen_t addr_len) {
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
memcpy(&relay->client_addr, addr, sizeof(*addr));
|
||||
relay->client_addr_len = addr_len;
|
||||
relay->has_client = 1;
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
}
|
||||
|
||||
static int udp_relay_copy_client(udp_relay_t *relay, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
int has_client;
|
||||
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
has_client = relay->has_client;
|
||||
if (has_client) {
|
||||
memcpy(addr, &relay->client_addr, sizeof(*addr));
|
||||
*addr_len = relay->client_addr_len;
|
||||
}
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return has_client;
|
||||
}
|
||||
|
||||
static void *udp_relay_forward_downstream_to_upstream(void *arg) {
|
||||
udp_relay_t *relay = (udp_relay_t *) arg;
|
||||
uint8_t buffer[UDP_RELAY_BUF_SIZE];
|
||||
|
||||
for (;;) {
|
||||
struct sockaddr_storage source;
|
||||
socklen_t source_len = sizeof(source);
|
||||
ssize_t n = recvfrom(relay->downstream_fd, buffer, sizeof(buffer), 0, (struct sockaddr *) &source, &source_len);
|
||||
|
||||
if (n < 0) {
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
udp_relay_record_client(relay, &source, source_len);
|
||||
for (;;) {
|
||||
if (send(relay->upstream_fd, buffer, (size_t) n, 0) >= 0) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *udp_relay_forward_upstream_to_downstream(void *arg) {
|
||||
udp_relay_t *relay = (udp_relay_t *) arg;
|
||||
uint8_t buffer[UDP_RELAY_BUF_SIZE];
|
||||
|
||||
for (;;) {
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len = 0;
|
||||
ssize_t n = recv(relay->upstream_fd, buffer, sizeof(buffer), 0);
|
||||
|
||||
if (n < 0) {
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!udp_relay_copy_client(relay, &client_addr, &client_addr_len)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (sendto(relay->downstream_fd, buffer, (size_t) n, 0, (struct sockaddr *) &client_addr, client_addr_len) >= 0) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void udp_relay_join_threads(udp_relay_t *relay) {
|
||||
if (relay->downstream_thread_started) {
|
||||
pthread_join(relay->downstream_thread, NULL);
|
||||
relay->downstream_thread_started = 0;
|
||||
}
|
||||
if (relay->upstream_thread_started) {
|
||||
pthread_join(relay->upstream_thread, NULL);
|
||||
relay->upstream_thread_started = 0;
|
||||
}
|
||||
}
|
||||
|
||||
udp_relay_t *udp_relay_open(const char *listen_addr, const char *upstream_addr) {
|
||||
struct sockaddr_storage listen_ss;
|
||||
struct sockaddr_storage upstream_ss;
|
||||
socklen_t listen_len;
|
||||
socklen_t upstream_len;
|
||||
int family;
|
||||
int fd_listen = -1;
|
||||
int fd_upstream = -1;
|
||||
udp_relay_t *relay = NULL;
|
||||
|
||||
if (omni_parse_sockaddr(listen_addr, 1, &listen_ss, &listen_len, &family) != 0 ||
|
||||
omni_parse_sockaddr(upstream_addr, 0, &upstream_ss, &upstream_len, &family) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fd_listen = socket(listen_ss.ss_family, SOCK_DGRAM, 0);
|
||||
if (fd_listen < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (bind(fd_listen, (struct sockaddr *) &listen_ss, listen_len) != 0) {
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
fd_upstream = socket(upstream_ss.ss_family, SOCK_DGRAM, 0);
|
||||
if (fd_upstream < 0) {
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
if (connect(fd_upstream, (struct sockaddr *) &upstream_ss, upstream_len) != 0) {
|
||||
close(fd_upstream);
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
relay = (udp_relay_t *) calloc(1, sizeof(*relay));
|
||||
if (relay == NULL) {
|
||||
close(fd_upstream);
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
relay->downstream_fd = fd_listen;
|
||||
relay->upstream_fd = fd_upstream;
|
||||
memcpy(&relay->upstream_addr, &upstream_ss, sizeof(upstream_ss));
|
||||
relay->upstream_addr_len = upstream_len;
|
||||
pthread_mutex_init(&relay->lock, NULL);
|
||||
pthread_mutex_init(&relay->state_mu, NULL);
|
||||
pthread_cond_init(&relay->state_cond, NULL);
|
||||
return relay;
|
||||
}
|
||||
|
||||
int udp_relay_serve(udp_relay_t *relay) {
|
||||
int thread_rc;
|
||||
int rc;
|
||||
int errnum;
|
||||
|
||||
if (relay == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
errno = ECANCELED;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
relay->worker_done = 0;
|
||||
relay->worker_rc = 0;
|
||||
relay->worker_errno = 0;
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
|
||||
thread_rc = pthread_create(&relay->downstream_thread, NULL, udp_relay_forward_downstream_to_upstream, relay);
|
||||
if (thread_rc != 0) {
|
||||
errno = thread_rc;
|
||||
return -1;
|
||||
}
|
||||
relay->downstream_thread_started = 1;
|
||||
|
||||
thread_rc = pthread_create(&relay->upstream_thread, NULL, udp_relay_forward_upstream_to_downstream, relay);
|
||||
if (thread_rc != 0) {
|
||||
errno = thread_rc;
|
||||
udp_relay_close(relay);
|
||||
udp_relay_join_threads(relay);
|
||||
return -1;
|
||||
}
|
||||
relay->upstream_thread_started = 1;
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
while (!relay->worker_done) {
|
||||
pthread_cond_wait(&relay->state_cond, &relay->state_mu);
|
||||
}
|
||||
rc = relay->worker_rc;
|
||||
errnum = relay->worker_errno;
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
|
||||
udp_relay_close(relay);
|
||||
udp_relay_join_threads(relay);
|
||||
|
||||
if (rc != 0 && errnum != 0) {
|
||||
errno = errnum;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int udp_relay_close(udp_relay_t *relay) {
|
||||
int downstream_fd;
|
||||
int upstream_fd;
|
||||
|
||||
if (relay == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
if (relay->closed) {
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
return 0;
|
||||
}
|
||||
relay->closed = 1;
|
||||
downstream_fd = relay->downstream_fd;
|
||||
upstream_fd = relay->upstream_fd;
|
||||
relay->downstream_fd = -1;
|
||||
relay->upstream_fd = -1;
|
||||
pthread_cond_broadcast(&relay->state_cond);
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
|
||||
if (downstream_fd >= 0) {
|
||||
close(downstream_fd);
|
||||
}
|
||||
if (upstream_fd >= 0) {
|
||||
close(upstream_fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void udp_relay_free(udp_relay_t *relay) {
|
||||
if (relay == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_relay_close(relay);
|
||||
udp_relay_join_threads(relay);
|
||||
pthread_mutex_destroy(&relay->lock);
|
||||
pthread_cond_destroy(&relay->state_cond);
|
||||
pthread_mutex_destroy(&relay->state_mu);
|
||||
free(relay);
|
||||
}
|
||||
1705
c/src/transport_kcp.c
Normal file
1705
c/src/transport_kcp.c
Normal file
File diff suppressed because it is too large
Load Diff
476
c/src/transport_udp.c
Normal file
476
c/src/transport_udp.c
Normal file
@@ -0,0 +1,476 @@
|
||||
#include "transport_udp.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct udp_pending_tx {
|
||||
struct udp_pending_tx *next;
|
||||
uint32_t tx_id;
|
||||
message_t msg;
|
||||
int bytes_written;
|
||||
int saw_sched;
|
||||
int saw_software;
|
||||
} udp_pending_tx_t;
|
||||
|
||||
struct udp_conn {
|
||||
int fd;
|
||||
int connected;
|
||||
int timestamping_enabled;
|
||||
latency_logger_t *logger;
|
||||
tx_timestamp_debug_logger_t *debug_logger;
|
||||
char node_role[OMNI_MAX_NODE_ROLE];
|
||||
char node_id[OMNI_MAX_PEER_ID];
|
||||
pthread_mutex_t write_mu;
|
||||
pthread_mutex_t pending_mu;
|
||||
pthread_t errqueue_thread;
|
||||
int errqueue_thread_started;
|
||||
uint32_t next_tx_id;
|
||||
udp_pending_tx_t *pending_head;
|
||||
uint8_t *recv_buffer;
|
||||
size_t recv_buffer_cap;
|
||||
int closed;
|
||||
};
|
||||
|
||||
static int udp_open_socket_for_addr(const struct sockaddr *addr, socklen_t addr_len, int bind_device, const char *device) {
|
||||
int fd;
|
||||
int reuse = 1;
|
||||
fd = socket(addr->sa_family, SOCK_DGRAM, 0);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
if (bind_device && omni_bind_device(fd, device) != 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
(void) addr_len;
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int udp_resolve_ip_only(const char *ip, int family, struct sockaddr_storage *out, socklen_t *out_len) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
if (getaddrinfo(ip, "0", &hints, &result) != 0 || result == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memcpy(out, result->ai_addr, result->ai_addrlen);
|
||||
*out_len = (socklen_t) result->ai_addrlen;
|
||||
freeaddrinfo(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static udp_conn_t *udp_conn_alloc(int fd, int connected, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger) {
|
||||
udp_conn_t *conn = (udp_conn_t *) calloc(1, sizeof(*conn));
|
||||
if (conn == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
conn->fd = fd;
|
||||
conn->connected = connected;
|
||||
conn->timestamping_enabled = enable_timestamping;
|
||||
conn->logger = logger;
|
||||
conn->debug_logger = debug_logger;
|
||||
snprintf(conn->node_role, sizeof(conn->node_role), "%s", node_role == NULL ? "" : node_role);
|
||||
snprintf(conn->node_id, sizeof(conn->node_id), "%s", node_id == NULL ? "" : node_id);
|
||||
conn->recv_buffer = (uint8_t *) malloc(OMNI_MAX_FRAME_SIZE);
|
||||
if (conn->recv_buffer == NULL) {
|
||||
free(conn);
|
||||
return NULL;
|
||||
}
|
||||
conn->recv_buffer_cap = OMNI_MAX_FRAME_SIZE;
|
||||
pthread_mutex_init(&conn->write_mu, NULL);
|
||||
pthread_mutex_init(&conn->pending_mu, NULL);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void udp_pending_destroy(udp_pending_tx_t *pending) {
|
||||
while (pending != NULL) {
|
||||
udp_pending_tx_t *next = pending->next;
|
||||
protocol_message_clear(&pending->msg);
|
||||
free(pending);
|
||||
pending = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int udp_debug_log_send_chunk(udp_conn_t *conn, const message_t *msg, int bytes_written, uint32_t tx_id) {
|
||||
tx_timestamp_debug_record_t record;
|
||||
if (conn->debug_logger == NULL) {
|
||||
return 0;
|
||||
}
|
||||
memset(&record, 0, sizeof(record));
|
||||
snprintf(record.record_type, sizeof(record.record_type), "%s", TX_TIMESTAMP_DEBUG_RECORD_SEND_CHUNK);
|
||||
snprintf(record.node_role, sizeof(record.node_role), "%s", conn->node_role);
|
||||
snprintf(record.node_id, sizeof(record.node_id), "%s", conn->node_id);
|
||||
record.message_type = msg->type;
|
||||
record.message_id = msg->id;
|
||||
snprintf(record.from, sizeof(record.from), "%s", msg->from);
|
||||
snprintf(record.to, sizeof(record.to), "%s", msg->to);
|
||||
snprintf(record.file_name, sizeof(record.file_name), "%s", msg->file_name);
|
||||
record.body_size = (int) msg->body_len;
|
||||
record.send_call_index = 0;
|
||||
record.frame_offset_start = 0;
|
||||
record.frame_offset_end = bytes_written > 0 ? bytes_written - 1 : 0;
|
||||
record.bytes_written = bytes_written;
|
||||
record.expected_tx_id = tx_id;
|
||||
return tx_timestamp_debug_log(conn->debug_logger, &record);
|
||||
}
|
||||
|
||||
static void udp_debug_log_errqueue_event(udp_conn_t *conn, const message_t *msg, const omni_tx_timestamp_event_t *event, uint32_t tx_id, int selected) {
|
||||
tx_timestamp_debug_record_t record;
|
||||
if (conn->debug_logger == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(&record, 0, sizeof(record));
|
||||
snprintf(record.record_type, sizeof(record.record_type), "%s", TX_TIMESTAMP_DEBUG_RECORD_ERRQUEUE_EVENT);
|
||||
snprintf(record.node_role, sizeof(record.node_role), "%s", conn->node_role);
|
||||
snprintf(record.node_id, sizeof(record.node_id), "%s", conn->node_id);
|
||||
record.message_type = msg->type;
|
||||
record.message_id = msg->id;
|
||||
snprintf(record.from, sizeof(record.from), "%s", msg->from);
|
||||
snprintf(record.to, sizeof(record.to), "%s", msg->to);
|
||||
snprintf(record.file_name, sizeof(record.file_name), "%s", msg->file_name);
|
||||
record.body_size = (int) msg->body_len;
|
||||
snprintf(record.phase, sizeof(record.phase), "%s", "background");
|
||||
record.read_index = 0;
|
||||
snprintf(record.event_name, sizeof(record.event_name), "%s", event->event_name);
|
||||
record.ts_unix_nano = event->ts_unix_nano;
|
||||
record.ee_info = event->ee_info;
|
||||
record.ee_data = event->ee_data;
|
||||
record.expected_tx_id = tx_id;
|
||||
record.selected_for_latency = selected;
|
||||
tx_timestamp_debug_log(conn->debug_logger, &record);
|
||||
}
|
||||
|
||||
static void *udp_errqueue_thread_main(void *arg) {
|
||||
udp_conn_t *conn = (udp_conn_t *) arg;
|
||||
uint8_t control[512];
|
||||
struct msghdr msg;
|
||||
struct iovec iov;
|
||||
uint8_t dummy;
|
||||
|
||||
while (!conn->closed) {
|
||||
ssize_t rc;
|
||||
omni_tx_timestamp_event_t event;
|
||||
udp_pending_tx_t *prev = NULL;
|
||||
udp_pending_tx_t *cur = NULL;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(control, 0, sizeof(control));
|
||||
dummy = 0;
|
||||
iov.iov_base = &dummy;
|
||||
iov.iov_len = sizeof(dummy);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
rc = recvmsg(conn->fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
if (conn->closed) {
|
||||
return NULL;
|
||||
}
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
if (linux_timestamping_parse_tx_timestamp(&msg, &event) != 0) {
|
||||
continue;
|
||||
}
|
||||
pthread_mutex_lock(&conn->pending_mu);
|
||||
cur = NULL;
|
||||
prev = NULL;
|
||||
{
|
||||
udp_pending_tx_t **head = &conn->pending_head;
|
||||
udp_pending_tx_t *iter = *head;
|
||||
while (iter != NULL) {
|
||||
if (iter->tx_id == event.ee_data) {
|
||||
cur = iter;
|
||||
break;
|
||||
}
|
||||
prev = iter;
|
||||
iter = iter->next;
|
||||
}
|
||||
if (cur != NULL) {
|
||||
if (strcmp(event.event_name, EVENT_A_TX_SCHED) == 0 && !cur->saw_sched) {
|
||||
cur->saw_sched = 1;
|
||||
latencylog_log_message_event_at(conn->logger, conn->node_role, conn->node_id, EVENT_A_TX_SCHED, event.ts_unix_nano, &cur->msg);
|
||||
} else if (strcmp(event.event_name, EVENT_A_TX_SOFTWARE) == 0 && !cur->saw_software) {
|
||||
cur->saw_software = 1;
|
||||
latencylog_log_message_event_at(conn->logger, conn->node_role, conn->node_id, EVENT_A_TX_SOFTWARE, event.ts_unix_nano, &cur->msg);
|
||||
}
|
||||
udp_debug_log_errqueue_event(conn, &cur->msg, &event, cur->tx_id, 1);
|
||||
if (cur->saw_sched && cur->saw_software) {
|
||||
if (prev == NULL) {
|
||||
*head = cur->next;
|
||||
} else {
|
||||
prev->next = cur->next;
|
||||
}
|
||||
protocol_message_clear(&cur->msg);
|
||||
free(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&conn->pending_mu);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int udp_conn_start_errqueue(udp_conn_t *conn) {
|
||||
if (!conn->timestamping_enabled) {
|
||||
return 0;
|
||||
}
|
||||
if (pthread_create(&conn->errqueue_thread, NULL, udp_errqueue_thread_main, conn) != 0) {
|
||||
return -1;
|
||||
}
|
||||
conn->errqueue_thread_started = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_conn_t *udp_conn_dial(const char *server_addr, const char *bind_ip, const char *bind_device, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
struct sockaddr_storage local_addr;
|
||||
socklen_t remote_len;
|
||||
socklen_t local_len;
|
||||
int family;
|
||||
int fd;
|
||||
udp_conn_t *conn;
|
||||
|
||||
if (omni_parse_sockaddr(server_addr, 0, &remote_addr, &remote_len, &family) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fd = udp_open_socket_for_addr((struct sockaddr *) &remote_addr, remote_len, bind_device != NULL && bind_device[0] != '\0', bind_device);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (bind_ip != NULL && bind_ip[0] != '\0') {
|
||||
if (udp_resolve_ip_only(bind_ip, family, &local_addr, &local_len) != 0 || bind(fd, (struct sockaddr *) &local_addr, local_len) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (connect(fd, (struct sockaddr *) &remote_addr, remote_len) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (enable_timestamping && linux_timestamping_enable_udp_socket(fd, 1) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
conn = udp_conn_alloc(fd, 1, enable_timestamping, logger, node_role, node_id, debug_logger);
|
||||
if (conn == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (udp_conn_start_errqueue(conn) != 0) {
|
||||
udp_conn_free(conn);
|
||||
return NULL;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
udp_conn_t *udp_conn_bind(const char *listen_addr, const char *bind_device, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger) {
|
||||
struct sockaddr_storage local_addr;
|
||||
socklen_t local_len;
|
||||
int family;
|
||||
int fd;
|
||||
udp_conn_t *conn;
|
||||
if (omni_parse_sockaddr(listen_addr, 1, &local_addr, &local_len, &family) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fd = udp_open_socket_for_addr((struct sockaddr *) &local_addr, local_len, bind_device != NULL && bind_device[0] != '\0', bind_device);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (bind(fd, (struct sockaddr *) &local_addr, local_len) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (enable_timestamping && linux_timestamping_enable_udp_socket(fd, 1) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
conn = udp_conn_alloc(fd, 0, enable_timestamping, logger, node_role, node_id, debug_logger);
|
||||
if (conn == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (udp_conn_start_errqueue(conn) != 0) {
|
||||
udp_conn_free(conn);
|
||||
return NULL;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
static int udp_conn_send_inner(udp_conn_t *conn, const message_t *msg, const struct sockaddr *addr, socklen_t addr_len) {
|
||||
uint8_t *payload = NULL;
|
||||
size_t payload_len = 0;
|
||||
ssize_t rc;
|
||||
udp_pending_tx_t *pending = NULL;
|
||||
uint32_t tx_id;
|
||||
|
||||
if (protocol_encode_message_datagram(msg, &payload, &payload_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&conn->write_mu);
|
||||
latencylog_log_message_event(conn->logger, conn->node_role, conn->node_id, EVENT_SEND_HANDOFF_BEGIN, msg);
|
||||
tx_id = conn->next_tx_id++;
|
||||
if (conn->timestamping_enabled) {
|
||||
pending = (udp_pending_tx_t *) calloc(1, sizeof(*pending));
|
||||
if (pending == NULL || protocol_message_copy(&pending->msg, msg) != 0) {
|
||||
free(payload);
|
||||
pthread_mutex_unlock(&conn->write_mu);
|
||||
free(pending);
|
||||
return -1;
|
||||
}
|
||||
pending->tx_id = tx_id;
|
||||
pending->bytes_written = (int) payload_len;
|
||||
pthread_mutex_lock(&conn->pending_mu);
|
||||
pending->next = conn->pending_head;
|
||||
conn->pending_head = pending;
|
||||
pthread_mutex_unlock(&conn->pending_mu);
|
||||
udp_debug_log_send_chunk(conn, msg, (int) payload_len, tx_id);
|
||||
}
|
||||
if (addr != NULL) {
|
||||
rc = sendto(conn->fd, payload, payload_len, 0, addr, addr_len);
|
||||
} else {
|
||||
rc = send(conn->fd, payload, payload_len, 0);
|
||||
}
|
||||
free(payload);
|
||||
if (rc < 0 || (size_t) rc != payload_len) {
|
||||
if (pending != NULL) {
|
||||
udp_pending_tx_t *prev = NULL;
|
||||
udp_pending_tx_t *cur;
|
||||
pthread_mutex_lock(&conn->pending_mu);
|
||||
for (cur = conn->pending_head; cur != NULL; cur = cur->next) {
|
||||
if (cur == pending) {
|
||||
if (prev == NULL) {
|
||||
conn->pending_head = cur->next;
|
||||
} else {
|
||||
prev->next = cur->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
pthread_mutex_unlock(&conn->pending_mu);
|
||||
protocol_message_clear(&pending->msg);
|
||||
free(pending);
|
||||
}
|
||||
pthread_mutex_unlock(&conn->write_mu);
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(conn->logger, conn->node_role, conn->node_id, EVENT_SEND_HANDOFF_END, msg);
|
||||
pthread_mutex_unlock(&conn->write_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_conn_send(udp_conn_t *conn, const message_t *msg) {
|
||||
if (conn == NULL || !conn->connected) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
return udp_conn_send_inner(conn, msg, NULL, 0);
|
||||
}
|
||||
|
||||
int udp_conn_send_to(udp_conn_t *conn, const message_t *msg, const struct sockaddr *addr, socklen_t addr_len) {
|
||||
if (conn == NULL || addr == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return udp_conn_send_inner(conn, msg, addr, addr_len);
|
||||
}
|
||||
|
||||
int udp_conn_receive(udp_conn_t *conn, message_t *out_msg, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
uint8_t control[512];
|
||||
struct sockaddr_storage source;
|
||||
struct iovec iov;
|
||||
struct msghdr msg;
|
||||
ssize_t n;
|
||||
int64_t rx_ts = 0;
|
||||
char err[128];
|
||||
|
||||
if (conn == NULL || out_msg == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(&source, 0, sizeof(source));
|
||||
iov.iov_base = conn->recv_buffer;
|
||||
iov.iov_len = conn->recv_buffer_cap;
|
||||
msg.msg_name = &source;
|
||||
msg.msg_namelen = sizeof(source);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
if (conn->timestamping_enabled) {
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
}
|
||||
n = recvmsg(conn->fd, &msg, 0);
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (conn->timestamping_enabled) {
|
||||
rx_ts = linux_timestamping_parse_rx_timestamp(&msg);
|
||||
}
|
||||
if (protocol_decode_message_datagram(conn->recv_buffer, (size_t) n, out_msg, err, sizeof(err)) != 0) {
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
if (addr != NULL && addr_len != NULL) {
|
||||
omni_clone_sockaddr((struct sockaddr *) &source, msg.msg_namelen, addr, addr_len);
|
||||
}
|
||||
if (rx_ts > 0) {
|
||||
latencylog_log_message_event_at(conn->logger, conn->node_role, conn->node_id, EVENT_B_RX_SOFTWARE, rx_ts, out_msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_conn_fd(const udp_conn_t *conn) {
|
||||
return conn == NULL ? -1 : conn->fd;
|
||||
}
|
||||
|
||||
int udp_conn_local_addr(const udp_conn_t *conn, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
socklen_t len = sizeof(*addr);
|
||||
if (conn == NULL || addr == NULL || addr_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (getsockname(conn->fd, (struct sockaddr *) addr, &len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
*addr_len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_conn_close(udp_conn_t *conn) {
|
||||
if (conn == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (!conn->closed) {
|
||||
conn->closed = 1;
|
||||
close(conn->fd);
|
||||
if (conn->errqueue_thread_started) {
|
||||
pthread_join(conn->errqueue_thread, NULL);
|
||||
conn->errqueue_thread_started = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void udp_conn_free(udp_conn_t *conn) {
|
||||
if (conn == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_conn_close(conn);
|
||||
udp_pending_destroy(conn->pending_head);
|
||||
free(conn->recv_buffer);
|
||||
pthread_mutex_destroy(&conn->write_mu);
|
||||
pthread_mutex_destroy(&conn->pending_mu);
|
||||
free(conn);
|
||||
}
|
||||
108
c/src/tx_timestamp_debug.c
Normal file
108
c/src/tx_timestamp_debug.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "tx_timestamp_debug.h"
|
||||
|
||||
tx_timestamp_debug_logger_t *tx_timestamp_debug_open_jsonl(const char *path) {
|
||||
tx_timestamp_debug_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (tx_timestamp_debug_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init(&logger->file_logger, file);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void tx_timestamp_debug_close(tx_timestamp_debug_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
int tx_timestamp_debug_log(tx_timestamp_debug_logger_t *logger, const tx_timestamp_debug_record_t *record) {
|
||||
char *line;
|
||||
char *node_role;
|
||||
char *node_id;
|
||||
char *from;
|
||||
char *to;
|
||||
char *file_name;
|
||||
char *phase;
|
||||
char *event_name;
|
||||
|
||||
if (logger == NULL || record == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
node_role = omni_json_escape(record->node_role);
|
||||
node_id = omni_json_escape(record->node_id);
|
||||
from = omni_json_escape(record->from);
|
||||
to = omni_json_escape(record->to);
|
||||
file_name = omni_json_escape(record->file_name);
|
||||
phase = omni_json_escape(record->phase);
|
||||
event_name = omni_json_escape(record->event_name);
|
||||
if (node_role == NULL || node_id == NULL || from == NULL || to == NULL || file_name == NULL || phase == NULL || event_name == NULL) {
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
free(phase);
|
||||
free(event_name);
|
||||
return -1;
|
||||
}
|
||||
line = omni_strdup_printf(
|
||||
"{\"record_type\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"file_name\":\"%s\",\"body_size\":%d,\"phase\":\"%s\",\"send_call_index\":%d,\"frame_offset_start\":%d,\"frame_offset_end\":%d,\"bytes_written\":%d,\"expected_tx_id\":%u,\"read_index\":%d,\"event_name\":\"%s\",\"ts_unix_nano\":%" PRId64 ",\"ee_info\":%u,\"ee_data\":%u,\"matched_send_call_index\":%d,\"selected_for_latency\":%d}",
|
||||
record->record_type,
|
||||
node_role,
|
||||
node_id,
|
||||
protocol_message_type_name(record->message_type),
|
||||
record->message_id,
|
||||
from,
|
||||
to,
|
||||
file_name,
|
||||
record->body_size,
|
||||
phase,
|
||||
record->send_call_index,
|
||||
record->frame_offset_start,
|
||||
record->frame_offset_end,
|
||||
record->bytes_written,
|
||||
record->expected_tx_id,
|
||||
record->read_index,
|
||||
event_name,
|
||||
record->ts_unix_nano,
|
||||
record->ee_info,
|
||||
record->ee_data,
|
||||
record->matched_send_call_index,
|
||||
record->selected_for_latency
|
||||
);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
free(phase);
|
||||
free(event_name);
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
}
|
||||
3302
c/third_party/cjson/cJSON.c
vendored
Normal file
3302
c/third_party/cjson/cJSON.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
381
c/third_party/cjson/cJSON.h
vendored
Normal file
381
c/third_party/cjson/cJSON.h
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef cJSON__h
|
||||
#define cJSON__h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
|
||||
#define __WINDOWS__
|
||||
#endif
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
|
||||
|
||||
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
|
||||
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
|
||||
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
|
||||
|
||||
For *nix builds that support visibility attribute, you can define similar behavior by
|
||||
|
||||
setting default visibility to hidden by adding
|
||||
-fvisibility=hidden (for gcc)
|
||||
or
|
||||
-xldscope=hidden (for sun cc)
|
||||
to CFLAGS
|
||||
|
||||
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
|
||||
|
||||
*/
|
||||
|
||||
#define CJSON_CDECL __cdecl
|
||||
#define CJSON_STDCALL __stdcall
|
||||
|
||||
/* export symbols by default, this is necessary for copy pasting the C and header file */
|
||||
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_EXPORT_SYMBOLS
|
||||
#endif
|
||||
|
||||
#if defined(CJSON_HIDE_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) type CJSON_STDCALL
|
||||
#elif defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
|
||||
#elif defined(CJSON_IMPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
|
||||
#endif
|
||||
#else /* !__WINDOWS__ */
|
||||
#define CJSON_CDECL
|
||||
#define CJSON_STDCALL
|
||||
|
||||
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined(__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
|
||||
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
|
||||
#else
|
||||
#define CJSON_PUBLIC(type) type
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* project version */
|
||||
#define CJSON_VERSION_MAJOR 1
|
||||
#define CJSON_VERSION_MINOR 7
|
||||
#define CJSON_VERSION_PATCH 19
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* cJSON Types: */
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7) /* raw json */
|
||||
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
|
||||
/* The cJSON structure: */
|
||||
typedef struct cJSON
|
||||
{
|
||||
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||
struct cJSON *next;
|
||||
struct cJSON *prev;
|
||||
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||
struct cJSON *child;
|
||||
|
||||
/* The type of the item, as above. */
|
||||
int type;
|
||||
|
||||
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
|
||||
char *valuestring;
|
||||
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
|
||||
int valueint;
|
||||
/* The item's number, if type==cJSON_Number */
|
||||
double valuedouble;
|
||||
|
||||
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
typedef struct cJSON_Hooks
|
||||
{
|
||||
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
|
||||
void *(CJSON_CDECL *malloc_fn)(size_t sz);
|
||||
void(CJSON_CDECL *free_fn)(void *ptr);
|
||||
} cJSON_Hooks;
|
||||
|
||||
typedef int cJSON_bool;
|
||||
|
||||
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
|
||||
* This is to prevent stack overflows. */
|
||||
#ifndef CJSON_NESTING_LIMIT
|
||||
#define CJSON_NESTING_LIMIT 1000
|
||||
#endif
|
||||
|
||||
/* Limits the length of circular references can be before cJSON rejects to parse them.
|
||||
* This is to prevent stack overflows. */
|
||||
#ifndef CJSON_CIRCULAR_LIMIT
|
||||
#define CJSON_CIRCULAR_LIMIT 10000
|
||||
#endif
|
||||
|
||||
/* returns the version of cJSON as a string */
|
||||
CJSON_PUBLIC(const char *)
|
||||
cJSON_Version(void);
|
||||
|
||||
/* Supply malloc, realloc and free functions to cJSON */
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_InitHooks(cJSON_Hooks *hooks);
|
||||
|
||||
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
|
||||
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_Parse(const char *value);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_ParseWithLength(const char *value, size_t buffer_length);
|
||||
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
|
||||
/* Render a cJSON entity to text for transfer/storage. */
|
||||
CJSON_PUBLIC(char *)
|
||||
cJSON_Print(const cJSON *item);
|
||||
/* Render a cJSON entity to text for transfer/storage without any formatting. */
|
||||
CJSON_PUBLIC(char *)
|
||||
cJSON_PrintUnformatted(const cJSON *item);
|
||||
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
|
||||
CJSON_PUBLIC(char *)
|
||||
cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
|
||||
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
|
||||
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
|
||||
/* Delete a cJSON entity and all subentities. */
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_Delete(cJSON *item);
|
||||
|
||||
/* Returns the number of items in an array (or object). */
|
||||
CJSON_PUBLIC(int)
|
||||
cJSON_GetArraySize(const cJSON *array);
|
||||
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_GetArrayItem(const cJSON *array, int index);
|
||||
/* Get item "string" from object. Case insensitive. */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_GetObjectItem(const cJSON *const object, const char *const string);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_GetObjectItemCaseSensitive(const cJSON *const object, const char *const string);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||
CJSON_PUBLIC(const char *)
|
||||
cJSON_GetErrorPtr(void);
|
||||
|
||||
/* Check item type and return its value */
|
||||
CJSON_PUBLIC(char *)
|
||||
cJSON_GetStringValue(const cJSON *const item);
|
||||
CJSON_PUBLIC(double)
|
||||
cJSON_GetNumberValue(const cJSON *const item);
|
||||
|
||||
/* These functions check the type of an item */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsInvalid(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsFalse(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsTrue(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsBool(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsNull(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsNumber(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsString(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsArray(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsObject(const cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_IsRaw(const cJSON *const item);
|
||||
|
||||
/* These calls create a cJSON item of the appropriate type. */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateNull(void);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateTrue(void);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateFalse(void);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateBool(cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateNumber(double num);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateString(const char *string);
|
||||
/* raw json */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateRaw(const char *raw);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateArray(void);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateObject(void);
|
||||
|
||||
/* Create a string where valuestring references a string so
|
||||
* it will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateStringReference(const char *string);
|
||||
/* Create an object/array that only references it's elements so
|
||||
* they will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateObjectReference(const cJSON *child);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateArrayReference(const cJSON *child);
|
||||
|
||||
/* These utilities create an Array of count items.
|
||||
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateIntArray(const int *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateFloatArray(const float *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_CreateStringArray(const char *const *strings, int count);
|
||||
|
||||
/* Append item to the specified array/object. */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
|
||||
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
|
||||
* writing to `item->string` */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||
|
||||
/* Remove/Detach items from Arrays/Objects. */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_DetachItemViaPointer(cJSON *parent, cJSON *const item);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
|
||||
/* Update array items. */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_ReplaceItemViaPointer(cJSON *const parent, cJSON *const item, cJSON *replacement);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem);
|
||||
|
||||
/* Duplicate a cJSON item */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
|
||||
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||
* The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
|
||||
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
|
||||
CJSON_PUBLIC(cJSON_bool)
|
||||
cJSON_Compare(const cJSON *const a, const cJSON *const b, const cJSON_bool case_sensitive);
|
||||
|
||||
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
|
||||
* The input pointer json cannot point to a read-only address area, such as a string constant,
|
||||
* but should point to a readable and writable address area. */
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_Minify(char *json);
|
||||
|
||||
/* Helper functions for creating and adding items to an object at the same time.
|
||||
* They return the added item or NULL on failure. */
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddNullToObject(cJSON *const object, const char *const name);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddTrueToObject(cJSON *const object, const char *const name);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddFalseToObject(cJSON *const object, const char *const name);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddBoolToObject(cJSON *const object, const char *const name, const cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddNumberToObject(cJSON *const object, const char *const name, const double number);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddRawToObject(cJSON *const object, const char *const name, const char *const raw);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddObjectToObject(cJSON *const object, const char *const name);
|
||||
CJSON_PUBLIC(cJSON *)
|
||||
cJSON_AddArrayToObject(cJSON *const object, const char *const name);
|
||||
|
||||
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
/* helper for the cJSON_SetNumberValue macro */
|
||||
CJSON_PUBLIC(double)
|
||||
cJSON_SetNumberHelper(cJSON *object, double number);
|
||||
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
|
||||
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
|
||||
CJSON_PUBLIC(char *)
|
||||
cJSON_SetValuestring(cJSON *object, const char *valuestring);
|
||||
|
||||
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
|
||||
#define cJSON_SetBoolValue(object, boolValue) ( \
|
||||
(object != NULL && ((object)->type & (cJSON_False | cJSON_True))) ? (object)->type = ((object)->type & (~(cJSON_False | cJSON_True))) | ((boolValue) ? cJSON_True : cJSON_False) : cJSON_Invalid)
|
||||
|
||||
/* Macro for iterating over an array or object */
|
||||
#define cJSON_ArrayForEach(element, array) for (element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||
|
||||
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
|
||||
CJSON_PUBLIC(void *)
|
||||
cJSON_malloc(size_t size);
|
||||
CJSON_PUBLIC(void)
|
||||
cJSON_free(void *object);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
1460
c/third_party/kcp/ikcp.c
vendored
Normal file
1460
c/third_party/kcp/ikcp.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
418
c/third_party/kcp/ikcp.h
vendored
Normal file
418
c/third_party/kcp/ikcp.h
vendored
Normal file
@@ -0,0 +1,418 @@
|
||||
//=====================================================================
|
||||
//
|
||||
// KCP - A Better ARQ Protocol Implementation
|
||||
// skywind3000 (at) gmail.com, 2010-2011
|
||||
//
|
||||
// Features:
|
||||
// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.
|
||||
// + Maximum RTT reduce three times vs tcp.
|
||||
// + Lightweight, distributed as a single source file.
|
||||
//
|
||||
//=====================================================================
|
||||
#ifndef __IKCP_H__
|
||||
#define __IKCP_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
//=====================================================================
|
||||
// 32BIT INTEGER DEFINITION
|
||||
//=====================================================================
|
||||
#ifndef __INTEGER_32_BITS__
|
||||
#define __INTEGER_32_BITS__
|
||||
#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \
|
||||
defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \
|
||||
defined(_M_AMD64)
|
||||
typedef unsigned int ISTDUINT32;
|
||||
typedef int ISTDINT32;
|
||||
#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \
|
||||
defined(__i386) || defined(_M_X86)
|
||||
typedef unsigned long ISTDUINT32;
|
||||
typedef long ISTDINT32;
|
||||
#elif defined(__MACOS__)
|
||||
typedef UInt32 ISTDUINT32;
|
||||
typedef SInt32 ISTDINT32;
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
#include <sys/types.h>
|
||||
typedef u_int32_t ISTDUINT32;
|
||||
typedef int32_t ISTDINT32;
|
||||
#elif defined(__BEOS__)
|
||||
#include <sys/inttypes.h>
|
||||
typedef u_int32_t ISTDUINT32;
|
||||
typedef int32_t ISTDINT32;
|
||||
#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__))
|
||||
typedef unsigned __int32 ISTDUINT32;
|
||||
typedef __int32 ISTDINT32;
|
||||
#elif defined(__GNUC__)
|
||||
#include <stdint.h>
|
||||
typedef uint32_t ISTDUINT32;
|
||||
typedef int32_t ISTDINT32;
|
||||
#else
|
||||
typedef unsigned long ISTDUINT32;
|
||||
typedef long ISTDINT32;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//=====================================================================
|
||||
// Integer Definition
|
||||
//=====================================================================
|
||||
#ifndef __IINT8_DEFINED
|
||||
#define __IINT8_DEFINED
|
||||
typedef char IINT8;
|
||||
#endif
|
||||
|
||||
#ifndef __IUINT8_DEFINED
|
||||
#define __IUINT8_DEFINED
|
||||
typedef unsigned char IUINT8;
|
||||
#endif
|
||||
|
||||
#ifndef __IUINT16_DEFINED
|
||||
#define __IUINT16_DEFINED
|
||||
typedef unsigned short IUINT16;
|
||||
#endif
|
||||
|
||||
#ifndef __IINT16_DEFINED
|
||||
#define __IINT16_DEFINED
|
||||
typedef short IINT16;
|
||||
#endif
|
||||
|
||||
#ifndef __IINT32_DEFINED
|
||||
#define __IINT32_DEFINED
|
||||
typedef ISTDINT32 IINT32;
|
||||
#endif
|
||||
|
||||
#ifndef __IUINT32_DEFINED
|
||||
#define __IUINT32_DEFINED
|
||||
typedef ISTDUINT32 IUINT32;
|
||||
#endif
|
||||
|
||||
#ifndef __IINT64_DEFINED
|
||||
#define __IINT64_DEFINED
|
||||
#if defined(_MSC_VER) || defined(__BORLANDC__)
|
||||
typedef __int64 IINT64;
|
||||
#else
|
||||
typedef long long IINT64;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef __IUINT64_DEFINED
|
||||
#define __IUINT64_DEFINED
|
||||
#if defined(_MSC_VER) || defined(__BORLANDC__)
|
||||
typedef unsigned __int64 IUINT64;
|
||||
#else
|
||||
typedef unsigned long long IUINT64;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef INLINE
|
||||
#if defined(__GNUC__)
|
||||
|
||||
#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
|
||||
#define INLINE __inline__ __attribute__((always_inline))
|
||||
#else
|
||||
#define INLINE __inline__
|
||||
#endif
|
||||
|
||||
#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__))
|
||||
#define INLINE __inline
|
||||
#else
|
||||
#define INLINE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (!defined(__cplusplus)) && (!defined(inline))
|
||||
#define inline INLINE
|
||||
#endif
|
||||
|
||||
//=====================================================================
|
||||
// QUEUE DEFINITION
|
||||
//=====================================================================
|
||||
#ifndef __IQUEUE_DEF__
|
||||
#define __IQUEUE_DEF__
|
||||
|
||||
struct IQUEUEHEAD
|
||||
{
|
||||
struct IQUEUEHEAD *next, *prev;
|
||||
};
|
||||
|
||||
typedef struct IQUEUEHEAD iqueue_head;
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// queue init
|
||||
//---------------------------------------------------------------------
|
||||
#define IQUEUE_HEAD_INIT(name) {&(name), &(name)}
|
||||
#define IQUEUE_HEAD(name) \
|
||||
struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name)
|
||||
|
||||
#define IQUEUE_INIT(ptr) ( \
|
||||
(ptr)->next = (ptr), (ptr)->prev = (ptr))
|
||||
|
||||
#define IOFFSETOF(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
|
||||
|
||||
#define ICONTAINEROF(ptr, type, member) ( \
|
||||
(type *)(((char *)((type *)ptr)) - IOFFSETOF(type, member)))
|
||||
|
||||
#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member)
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// queue operation
|
||||
//---------------------------------------------------------------------
|
||||
#define IQUEUE_ADD(node, head) ( \
|
||||
(node)->prev = (head), (node)->next = (head)->next, \
|
||||
(head)->next->prev = (node), (head)->next = (node))
|
||||
|
||||
#define IQUEUE_ADD_TAIL(node, head) ( \
|
||||
(node)->prev = (head)->prev, (node)->next = (head), \
|
||||
(head)->prev->next = (node), (head)->prev = (node))
|
||||
|
||||
#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n))
|
||||
|
||||
#define IQUEUE_DEL(entry) ( \
|
||||
(entry)->next->prev = (entry)->prev, \
|
||||
(entry)->prev->next = (entry)->next, \
|
||||
(entry)->next = 0, (entry)->prev = 0)
|
||||
|
||||
#define IQUEUE_DEL_INIT(entry) \
|
||||
do \
|
||||
{ \
|
||||
IQUEUE_DEL(entry); \
|
||||
IQUEUE_INIT(entry); \
|
||||
} while (0)
|
||||
|
||||
#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next)
|
||||
|
||||
#define iqueue_init IQUEUE_INIT
|
||||
#define iqueue_entry IQUEUE_ENTRY
|
||||
#define iqueue_add IQUEUE_ADD
|
||||
#define iqueue_add_tail IQUEUE_ADD_TAIL
|
||||
#define iqueue_del IQUEUE_DEL
|
||||
#define iqueue_del_init IQUEUE_DEL_INIT
|
||||
#define iqueue_is_empty IQUEUE_IS_EMPTY
|
||||
|
||||
#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \
|
||||
for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \
|
||||
&((iterator)->MEMBER) != (head); \
|
||||
(iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER))
|
||||
|
||||
#define iqueue_foreach(iterator, head, TYPE, MEMBER) \
|
||||
IQUEUE_FOREACH(iterator, head, TYPE, MEMBER)
|
||||
|
||||
#define iqueue_foreach_entry(pos, head) \
|
||||
for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next)
|
||||
|
||||
#define __iqueue_splice(list, head) \
|
||||
do \
|
||||
{ \
|
||||
iqueue_head *first = (list)->next, *last = (list)->prev; \
|
||||
iqueue_head *at = (head)->next; \
|
||||
(first)->prev = (head), (head)->next = (first); \
|
||||
(last)->next = (at), (at)->prev = (last); \
|
||||
} while (0)
|
||||
|
||||
#define iqueue_splice(list, head) \
|
||||
do \
|
||||
{ \
|
||||
if (!iqueue_is_empty(list)) \
|
||||
__iqueue_splice(list, head); \
|
||||
} while (0)
|
||||
|
||||
#define iqueue_splice_init(list, head) \
|
||||
do \
|
||||
{ \
|
||||
iqueue_splice(list, head); \
|
||||
iqueue_init(list); \
|
||||
} while (0)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4311)
|
||||
#pragma warning(disable : 4312)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// BYTE ORDER & ALIGNMENT
|
||||
//---------------------------------------------------------------------
|
||||
#ifndef IWORDS_BIG_ENDIAN
|
||||
#ifdef _BIG_ENDIAN_
|
||||
#if _BIG_ENDIAN_
|
||||
#define IWORDS_BIG_ENDIAN 1
|
||||
#endif
|
||||
#endif
|
||||
#ifndef IWORDS_BIG_ENDIAN
|
||||
#if defined(__hppa__) || \
|
||||
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
|
||||
(defined(__MIPS__) && defined(__MIPSEB__)) || \
|
||||
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
|
||||
defined(__sparc__) || defined(__powerpc__) || \
|
||||
defined(__mc68000__) || defined(__s390x__) || defined(__s390__)
|
||||
#define IWORDS_BIG_ENDIAN 1
|
||||
#endif
|
||||
#endif
|
||||
#ifndef IWORDS_BIG_ENDIAN
|
||||
#define IWORDS_BIG_ENDIAN 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef IWORDS_MUST_ALIGN
|
||||
#if defined(__i386__) || defined(__i386) || defined(_i386_)
|
||||
#define IWORDS_MUST_ALIGN 0
|
||||
#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__)
|
||||
#define IWORDS_MUST_ALIGN 0
|
||||
#elif defined(__amd64) || defined(__amd64__)
|
||||
#define IWORDS_MUST_ALIGN 0
|
||||
#else
|
||||
#define IWORDS_MUST_ALIGN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//=====================================================================
|
||||
// SEGMENT
|
||||
//=====================================================================
|
||||
struct IKCPSEG
|
||||
{
|
||||
struct IQUEUEHEAD node;
|
||||
IUINT32 conv;
|
||||
IUINT32 cmd;
|
||||
IUINT32 frg;
|
||||
IUINT32 wnd;
|
||||
IUINT32 ts;
|
||||
IUINT32 sn;
|
||||
IUINT32 una;
|
||||
IUINT32 len;
|
||||
IUINT32 resendts;
|
||||
IUINT32 rto;
|
||||
IUINT32 fastack;
|
||||
IUINT32 xmit;
|
||||
char data[1];
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// IKCPCB
|
||||
//---------------------------------------------------------------------
|
||||
struct IKCPCB
|
||||
{
|
||||
IUINT32 conv, mtu, mss, state;
|
||||
IUINT32 snd_una, snd_nxt, rcv_nxt;
|
||||
IUINT32 ts_recent, ts_lastack, ssthresh;
|
||||
IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
|
||||
IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
|
||||
IUINT32 current, interval, ts_flush, xmit;
|
||||
IUINT32 nrcv_buf, nsnd_buf;
|
||||
IUINT32 nrcv_que, nsnd_que;
|
||||
IUINT32 nodelay, updated;
|
||||
IUINT32 ts_probe, probe_wait;
|
||||
IUINT32 dead_link, incr;
|
||||
struct IQUEUEHEAD snd_queue;
|
||||
struct IQUEUEHEAD rcv_queue;
|
||||
struct IQUEUEHEAD snd_buf;
|
||||
struct IQUEUEHEAD rcv_buf;
|
||||
IUINT32 *acklist;
|
||||
IUINT32 ackcount;
|
||||
IUINT32 ackblock;
|
||||
void *user;
|
||||
char *buffer;
|
||||
int fastresend;
|
||||
int fastlimit;
|
||||
int nocwnd, stream;
|
||||
int logmask;
|
||||
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
|
||||
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
|
||||
};
|
||||
|
||||
typedef struct IKCPCB ikcpcb;
|
||||
|
||||
#define IKCP_LOG_OUTPUT 1
|
||||
#define IKCP_LOG_INPUT 2
|
||||
#define IKCP_LOG_SEND 4
|
||||
#define IKCP_LOG_RECV 8
|
||||
#define IKCP_LOG_IN_DATA 16
|
||||
#define IKCP_LOG_IN_ACK 32
|
||||
#define IKCP_LOG_IN_PROBE 64
|
||||
#define IKCP_LOG_IN_WINS 128
|
||||
#define IKCP_LOG_OUT_DATA 256
|
||||
#define IKCP_LOG_OUT_ACK 512
|
||||
#define IKCP_LOG_OUT_PROBE 1024
|
||||
#define IKCP_LOG_OUT_WINS 2048
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// interface
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// create a new kcp control object, 'conv' must equal in two endpoint
|
||||
// from the same connection. 'user' will be passed to the output callback
|
||||
// output callback can be setup like this: 'kcp->output = my_udp_output'
|
||||
ikcpcb *ikcp_create(IUINT32 conv, void *user);
|
||||
|
||||
// release kcp control object
|
||||
void ikcp_release(ikcpcb *kcp);
|
||||
|
||||
// set output callback, which will be invoked by kcp
|
||||
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len,
|
||||
ikcpcb *kcp, void *user));
|
||||
|
||||
// user/upper level recv: returns size, returns below zero for EAGAIN
|
||||
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
|
||||
|
||||
// user/upper level send, returns below zero for error
|
||||
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
|
||||
|
||||
// update state (call it repeatedly, every 10ms-100ms), or you can ask
|
||||
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
||||
// 'current' - current timestamp in millisec.
|
||||
void ikcp_update(ikcpcb *kcp, IUINT32 current);
|
||||
|
||||
// Determine when should you invoke ikcp_update:
|
||||
// returns when you should invoke ikcp_update in millisec, if there
|
||||
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
||||
// time, instead of call update repeatly.
|
||||
// Important to reduce unnacessary ikcp_update invoking. use it to
|
||||
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
|
||||
// or optimize ikcp_update when handling massive kcp connections)
|
||||
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
|
||||
|
||||
// when you received a low level packet (eg. UDP packet), call it
|
||||
int ikcp_input(ikcpcb *kcp, const char *data, long size);
|
||||
|
||||
// flush pending data
|
||||
void ikcp_flush(ikcpcb *kcp);
|
||||
|
||||
// check the size of next message in the recv queue
|
||||
int ikcp_peeksize(const ikcpcb *kcp);
|
||||
|
||||
// change MTU size, default is 1400
|
||||
int ikcp_setmtu(ikcpcb *kcp, int mtu);
|
||||
|
||||
// set maximum window size: sndwnd=32, rcvwnd=32 by default
|
||||
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
|
||||
|
||||
// get how many packet is waiting to be sent
|
||||
int ikcp_waitsnd(const ikcpcb *kcp);
|
||||
|
||||
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
|
||||
// nodelay: 0:disable(default), 1:enable
|
||||
// interval: internal update timer interval in millisec, default is 100ms
|
||||
// resend: 0:disable fast resend(default), 1:enable fast resend
|
||||
// nc: 0:normal congestion control(default), 1:disable congestion control
|
||||
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
|
||||
|
||||
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...);
|
||||
|
||||
// setup allocator
|
||||
void ikcp_allocator(void *(*new_malloc)(size_t), void (*new_free)(void *));
|
||||
|
||||
// read conv
|
||||
IUINT32 ikcp_getconv(const void *ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
465
change_to_c.md
Normal file
465
change_to_c.md
Normal file
@@ -0,0 +1,465 @@
|
||||
OmniSocketGo -> OmniSocketC 转换计划
|
||||
|
||||
Context
|
||||
|
||||
将现有的 Go 语言实现的 UDP/KCP 传输层项目 (OmniSocketGo) 转换为纯 C 语言项目,运行在 Linux 系统上。
|
||||
|
||||
原项目架构:A(Jetson) <-> C(relay cloud) <-> D(hub cloud) <-> B(host)
|
||||
- B <-> D:KCP 链路
|
||||
- D <-> C:UDP relay 转发
|
||||
- C <-> A:KCP 链路(A 通过 relay C 连接到 hub D)
|
||||
- 最终目的:B 和 A 之间双向传输数据
|
||||
|
||||
转换要求:
|
||||
- 只保留 UDP 和 KCP,不需要 TCP
|
||||
- 不需要写测试
|
||||
- 完整实现协议层、传输层、日志事件系统
|
||||
- Linux only
|
||||
|
||||
项目位置
|
||||
|
||||
OmniSocketGo/c/ — 作为当前 Go 项目的子目录
|
||||
|
||||
项目结构
|
||||
|
||||
c/
|
||||
├── Makefile
|
||||
├── README.md
|
||||
├── include/
|
||||
│ ├── protocol.h # 协议消息定义 + 编解码
|
||||
│ ├── transport_kcp.h # KCP 连接封装
|
||||
│ ├── transport_udp.h # UDP 连接封装(含 Linux timestamping)
|
||||
│ ├── linux_timestamping.h # Linux SO_TIMESTAMPING 底层实现
|
||||
│ ├── kcp_packet_debug.h # KCP packet-level kernel timestamp debug logger
|
||||
│ ├── kcp_session_stats.h # KCP session stats (RTO/SRTT) logger
|
||||
│ ├── tx_timestamp_debug.h # TX errqueue timestamp debug logger
|
||||
│ ├── server_kcp_hub.h # KCP Hub (D 节点)
|
||||
│ ├── server_udp_relay.h # UDP Relay (C 节点)
|
||||
│ ├── peer_kcp_client.h # KCP Peer Client (A/B 节点)
|
||||
│ ├── latencylog.h # 延迟日志事件系统
|
||||
│ ├── interactive.h # 交互式命令行
|
||||
│ └── cJSON.h # JSON 库 (第三方轻量级)
|
||||
├── src/
|
||||
│ ├── protocol.c
|
||||
│ ├── transport_kcp.c
|
||||
│ ├── transport_udp.c
|
||||
│ ├── linux_timestamping.c
|
||||
│ ├── kcp_packet_debug.c
|
||||
│ ├── kcp_session_stats.c
|
||||
│ ├── tx_timestamp_debug.c
|
||||
│ ├── server_kcp_hub.c
|
||||
│ ├── server_udp_relay.c
|
||||
│ ├── peer_kcp_client.c
|
||||
│ ├── latencylog.c
|
||||
│ ├── interactive.c
|
||||
│ └── cJSON.c
|
||||
├── cmd/
|
||||
│ ├── kcpserver.c # 主程序: KCP Hub 或 UDP Relay
|
||||
│ ├── kcppeer.c # 主程序: KCP Peer (A/B)
|
||||
│ └── kcpping.c # 主程序: KCP Ping 工具
|
||||
└── third_party/
|
||||
└── kcp/
|
||||
├── ikcp.h # KCP 协议核心实现 (github.com/skywind3000/kcp)
|
||||
└── ikcp.c
|
||||
|
||||
依赖说明
|
||||
|
||||
- KCP: 使用 skywind3000/kcp 的原始 C 实现 (ikcp.h/ikcp.c),替代 Go 的 xtaci/kcp-go/v5
|
||||
- JSON: 使用 cJSON (DaveGamble/cJSON) 替代 Go 的 encoding/json
|
||||
- 线程: 使用 pthread 替代 Go goroutine
|
||||
- 同步: 使用 pthread_mutex/pthread_rwlock 替代 Go sync.Mutex/sync.RWMutex
|
||||
|
||||
模块实现计划
|
||||
|
||||
1. 第三方库集成
|
||||
|
||||
- 下载 ikcp.h/ikcp.c (skywind3000/kcp)
|
||||
- 下载 cJSON.h/cJSON.c (DaveGamble/cJSON)
|
||||
|
||||
2. protocol.h / protocol.c
|
||||
|
||||
对应 Go: cmd/internal/protocol/message.go + codec.go
|
||||
|
||||
// 消息类型
|
||||
typedef enum {
|
||||
MSG_TYPE_TEXT = 0,
|
||||
MSG_TYPE_FILE = 1,
|
||||
MSG_TYPE_REGISTER = 2,
|
||||
MSG_TYPE_ERROR = 3,
|
||||
} message_type_t;
|
||||
|
||||
// 消息结构
|
||||
typedef struct {
|
||||
message_type_t type;
|
||||
uint64_t id;
|
||||
char from[64];
|
||||
char to[64];
|
||||
char file_name[256];
|
||||
uint8_t *body;
|
||||
int body_len;
|
||||
} message_t;
|
||||
|
||||
#define MAX_FRAME_SIZE (8 * 1024 * 1024)
|
||||
#define SERVER_PEER_ID "server"
|
||||
|
||||
核心函数:
|
||||
- int protocol_encode_message(const message_t *msg, uint8_t **out, int *out_len) — 编码消息为 [4B headerLen][header JSON][body]
|
||||
- int protocol_decode_message(const uint8_t *data, int data_len, message_t *msg) — 解码
|
||||
- int protocol_write_frame(int fd, const uint8_t *payload, int payload_len) — 写带长度前缀的帧 (用于 KCP stream)
|
||||
- int protocol_read_frame(int fd, uint8_t **payload, int *payload_len) — 读帧
|
||||
- int protocol_write_message(int fd, const message_t *msg) — 完整编码+写帧
|
||||
- int protocol_read_message(int fd, message_t *msg) — 读帧+解码
|
||||
- int protocol_validate_message(const message_t *msg) — 校验
|
||||
- void message_free(message_t *msg) — 释放 body 内存
|
||||
|
||||
注意: KCP session 在 stream 模式下行为类似 TCP,需要 [4B frameLen] 前缀来分帧。
|
||||
|
||||
3. latencylog.h / latencylog.c
|
||||
|
||||
对应 Go: cmd/internal/latencylog/logger.go
|
||||
|
||||
// 事件名常量
|
||||
#define EVENT_A_APP_PREP_BEGIN "A_APP_PREP_BEGIN"
|
||||
#define EVENT_SEND_HANDOFF_BEGIN "send_handoff_begin"
|
||||
#define EVENT_SEND_HANDOFF_END "send_handoff_end"
|
||||
#define EVENT_B_APP_RECV "B_APP_RECV"
|
||||
#define EVENT_B_PERSIST_BEGIN "B_PERSIST_BEGIN"
|
||||
#define EVENT_B_PERSIST_END "B_PERSIST_END"
|
||||
// ... 其他事件
|
||||
|
||||
typedef struct {
|
||||
int64_t ts_unix_nano;
|
||||
char node_role[16];
|
||||
char node_id[64];
|
||||
char event[32];
|
||||
message_type_t message_type;
|
||||
uint64_t message_id;
|
||||
char from[64];
|
||||
char to[64];
|
||||
char file_name[256];
|
||||
int body_size;
|
||||
} latency_event_t;
|
||||
|
||||
typedef struct latency_logger latency_logger_t;
|
||||
|
||||
核心函数:
|
||||
- latency_logger_t *latencylog_new_jsonl(const char *path) — 创建 JSONL 文件日志器
|
||||
- void latencylog_log_event(latency_logger_t *logger, const latency_event_t *event) — 写事件
|
||||
- void latencylog_log_message_event(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, const message_t
|
||||
*msg) — 为业务消息记事件
|
||||
- void latencylog_close(latency_logger_t *logger) — 关闭
|
||||
- int latencylog_is_business_message(const message_t *msg) — 判断是否业务消息
|
||||
|
||||
4. transport_kcp.h / transport_kcp.c
|
||||
|
||||
对应 Go: cmd/internal/transport/kcp.go + kcp_packet_conn.go
|
||||
|
||||
KCP 连接封装,底层用 raw ikcp + UDP socket:
|
||||
|
||||
typedef struct kcp_conn {
|
||||
ikcpcb *kcp;
|
||||
int udp_fd;
|
||||
struct sockaddr_in remote_addr;
|
||||
pthread_mutex_t write_mu;
|
||||
pthread_t recv_thread; // 底层 UDP -> ikcp_input 的线程
|
||||
latency_logger_t *logger;
|
||||
char node_role[16];
|
||||
char node_id[64];
|
||||
int closed;
|
||||
} kcp_conn_t;
|
||||
|
||||
核心函数:
|
||||
- kcp_conn_t *kcp_conn_dial(const char *server_addr, const char *bind_ip, const char *bind_device) — 客户端拨号
|
||||
- kcp_conn_t *kcp_conn_accept(int udp_fd, struct sockaddr_in *remote, uint32_t conv) — 服务端接受
|
||||
- int kcp_conn_send(kcp_conn_t *conn, const message_t *msg) — 发送消息
|
||||
- int kcp_conn_receive(kcp_conn_t *conn, message_t *msg) — 接收消息
|
||||
- void kcp_conn_close(kcp_conn_t *conn) — 关闭
|
||||
|
||||
KCP 配置参数(与 Go 版一致):
|
||||
#define KCP_NODELAY 1
|
||||
#define KCP_INTERVAL 10
|
||||
#define KCP_RESEND 2
|
||||
#define KCP_NC 1
|
||||
#define KCP_WND_SIZE 256
|
||||
#define KCP_MTU 1400
|
||||
|
||||
KCP 底层架构说明:
|
||||
Go 版使用 kcp-go 库,该库内部维护了一个 Listener 来多路复用一个 UDP socket 上的多个 KCP session(通过 conv ID 区分)。在 C 中需要自行实现:
|
||||
- 服务端:一个 UDP socket 监听,一个接收线程读取所有 UDP 包,根据 conv ID 分发到对应的 ikcpcb
|
||||
- 客户端:一个 UDP socket,一个 ikcpcb,一个后台线程负责 UDP recv -> ikcp_input
|
||||
|
||||
5. transport_udp.h / transport_udp.c
|
||||
|
||||
对应 Go: cmd/internal/transport/udp.go + udp_linux.go
|
||||
|
||||
typedef struct udp_conn {
|
||||
int fd;
|
||||
struct sockaddr_in peer_addr;
|
||||
syscall_rawconn_t raw; // syscall.RawConn 等价
|
||||
int linux_timestamping_enabled;
|
||||
latency_logger_t *logger;
|
||||
tx_timestamp_debug_logger_t *tx_debug_logger;
|
||||
uint32_t tx_packet_seq;
|
||||
// pending TX records for errqueue correlation
|
||||
struct udp_tx_pending *pending_tx;
|
||||
char node_role[16];
|
||||
char node_id[64];
|
||||
pthread_mutex_t write_mu;
|
||||
} udp_conn_t;
|
||||
|
||||
完整实现 Linux SO_TIMESTAMPING:
|
||||
- TX: SOF_TIMESTAMPING_TX_SCHED + SOF_TIMESTAMPING_TX_SOFTWARE + OPT_ID
|
||||
- RX: SOF_TIMESTAMPING_RX_SOFTWARE
|
||||
- errqueue 采集: recvmsg(MSG_ERRQUEUE) 读取 SCM_TIMESTAMPING 控制消息
|
||||
- TX timestamp debug logger: 记录 send_chunk / errqueue_event 到 JSONL
|
||||
- 对应 Go 文件: udp_linux.go, tx_timestamp_debug.go
|
||||
|
||||
同时为 KCP packet conn 实现类似的 timestamping:
|
||||
- 对应 Go 文件: kcp_packet_conn_linux.go, kcp_packet_debug.go
|
||||
- KCP 底层 UDP 包的 TX/RX kernel timestamp 记录
|
||||
|
||||
KCP session stats 完整实现:
|
||||
- session-level: conv, RTO, SRTT, SRTTVar 周期采样
|
||||
- 对应 Go 文件: kcp_session_stats.go
|
||||
|
||||
6. server_kcp_hub.h / server_kcp_hub.c
|
||||
|
||||
对应 Go: cmd/internal/server/kcp_hub.go
|
||||
|
||||
typedef struct {
|
||||
pthread_rwlock_t lock;
|
||||
// peer_id -> kcp_conn_t* 的哈希表
|
||||
struct peer_entry *peers; // 简单链表或哈希表
|
||||
int peer_count;
|
||||
latency_logger_t *logger;
|
||||
// relay 相关
|
||||
int relay_udp_fd;
|
||||
struct sockaddr_in relay_peer_addr;
|
||||
int relay_peer_known;
|
||||
} kcp_hub_t;
|
||||
|
||||
核心函数:
|
||||
- kcp_hub_t *kcp_hub_new(latency_logger_t *logger) — 创建 hub
|
||||
- int kcp_hub_serve_session(kcp_hub_t *hub, kcp_conn_t *conn) — 处理新会话(注册 + 转发循环)
|
||||
- void kcp_hub_set_relay(kcp_hub_t *hub, int udp_fd, struct sockaddr_in *peer_addr) — 配置 relay
|
||||
- int kcp_hub_serve_relay(kcp_hub_t *hub) — relay 接收循环
|
||||
- void kcp_hub_free(kcp_hub_t *hub) — 释放
|
||||
|
||||
服务端 KCP listener 实现:
|
||||
- 主 UDP socket 监听
|
||||
- 收到新 conv ID 时创建新 ikcpcb
|
||||
- 用 pthread 为每个 session 创建处理线程
|
||||
|
||||
7. server_udp_relay.h / server_udp_relay.c
|
||||
|
||||
对应 Go: cmd/internal/server/udp_relay.go
|
||||
|
||||
typedef struct {
|
||||
int downstream_fd; // 监听端
|
||||
int upstream_fd; // 连接到 hub D 的 UDP
|
||||
struct sockaddr_in upstream_addr;
|
||||
struct sockaddr_in client_addr;
|
||||
int client_known;
|
||||
pthread_mutex_t lock;
|
||||
} udp_relay_t;
|
||||
|
||||
核心函数:
|
||||
- udp_relay_t *udp_relay_new(int listen_fd, struct sockaddr_in *upstream_addr) — 创建
|
||||
- int udp_relay_serve(udp_relay_t *relay) — 双向转发循环(两个线程)
|
||||
- void udp_relay_close(udp_relay_t *relay) — 关闭
|
||||
|
||||
8. peer_kcp_client.h / peer_kcp_client.c
|
||||
|
||||
对应 Go: cmd/internal/peer/kcp_client.go + persist.go
|
||||
|
||||
typedef struct {
|
||||
char id[64];
|
||||
kcp_conn_t *conn;
|
||||
latency_logger_t *logger;
|
||||
uint64_t next_msg_id; // atomic
|
||||
pthread_mutex_t id_mu;
|
||||
} kcp_client_t;
|
||||
|
||||
核心函数:
|
||||
- kcp_client_t *kcp_client_dial(const char *server_addr, const char *peer_id, ...) — 连接并注册
|
||||
- int kcp_client_send_text(kcp_client_t *c, const char *to, const char *text) — 发文本
|
||||
- int kcp_client_send_file(kcp_client_t *c, const char *to, const char *path) — 发文件
|
||||
- int kcp_client_receive(kcp_client_t *c, message_t *msg) — 接收
|
||||
- int kcp_client_persist_message(kcp_client_t *c, const message_t *msg, const char *inbox_dir) — 持久化
|
||||
- void kcp_client_close(kcp_client_t *c) — 关闭
|
||||
|
||||
9. interactive.h / interactive.c
|
||||
|
||||
对应 Go: cmd/kcppeer/interactive.go
|
||||
|
||||
交互式命令行 REPL:
|
||||
- help / text <peer> <message> / file <peer> <path> / quit
|
||||
- int run_interactive_shell(kcp_client_t *client) — 运行交互循环
|
||||
|
||||
10. cmd/kcpserver.c
|
||||
|
||||
对应 Go: cmd/kcpserver/main.go
|
||||
|
||||
用法:
|
||||
kcpserver -listen 0.0.0.0:10909 # hub 模式
|
||||
kcpserver -mode relay -listen 0.0.0.0:10909 -relay-remote 172.21.32.15:10909 # relay 模式
|
||||
|
||||
- 解析命令行参数 (getopt)
|
||||
- hub 模式:创建 KCP listener -> 接受连接 -> kcp_hub_serve_session
|
||||
- relay 模式:创建 UDP relay -> udp_relay_serve
|
||||
|
||||
11. cmd/kcppeer.c
|
||||
|
||||
对应 Go: cmd/kcppeer/main.go
|
||||
|
||||
用法:
|
||||
kcppeer -id peer-a -server 172.21.32.15:10909 -relay-via 106.55.173.235:10909 -inbox-dir inbox/a
|
||||
kcppeer -id peer-b -server 81.70.156.140:10909 -inbox-dir inbox/b
|
||||
|
||||
- 连接到 KCP server
|
||||
- 启动接收线程
|
||||
- 运行交互式 shell 或单次发送
|
||||
|
||||
12. cmd/kcpping.c
|
||||
|
||||
对应 Go: cmd/kcpping/main.go + platform_linux.go
|
||||
|
||||
KCP ping 工具:
|
||||
- ping 模式: 发 JSON payload, 计算 RTT
|
||||
- echo 模式: 回弹文本消息
|
||||
- 统计: min/avg/max/p50/p95/p99/stddev
|
||||
|
||||
KCP session 多路复用实现(核心难点)
|
||||
|
||||
Go 版的 kcp-go 库在一个 UDP socket 上透明地多路复用多个 KCP session。C 版需要手动实现:
|
||||
|
||||
typedef struct kcp_listener {
|
||||
int udp_fd;
|
||||
pthread_t recv_thread;
|
||||
pthread_mutex_t sessions_lock;
|
||||
// conv -> kcp_session 的哈希表
|
||||
struct kcp_session_entry *sessions;
|
||||
// 新会话通知队列
|
||||
kcp_conn_t **accept_queue;
|
||||
int accept_queue_head, accept_queue_tail, accept_queue_cap;
|
||||
pthread_mutex_t accept_lock;
|
||||
pthread_cond_t accept_cond;
|
||||
} kcp_listener_t;
|
||||
|
||||
- kcp_listener_t *kcp_listen(const char *addr, const char *bind_device) — 创建 listener
|
||||
- kcp_conn_t *kcp_accept(kcp_listener_t *listener) — 阻塞等待新会话
|
||||
- 内部 recv_thread 循环读 UDP 包,解析前 4 字节 conv ID,分发到对应 ikcpcb
|
||||
- 未知 conv ID 时创建新 session 并放入 accept_queue
|
||||
|
||||
编译
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -pthread -D_GNU_SOURCE
|
||||
LDFLAGS = -lpthread
|
||||
|
||||
SRCS = src/protocol.c src/transport_kcp.c src/transport_udp.c \
|
||||
src/server_kcp_hub.c src/server_udp_relay.c \
|
||||
src/peer_kcp_client.c src/latencylog.c src/interactive.c \
|
||||
src/cJSON.c third_party/kcp/ikcp.c
|
||||
|
||||
all: kcpserver kcppeer kcpping
|
||||
|
||||
kcpserver: cmd/kcpserver.c $(SRCS)
|
||||
$(CC) $(CFLAGS) -Iinclude -Ithird_party/kcp -o $@ $^ $(LDFLAGS
|
||||
|
||||
kcppeer: cmd/kcppeer.c $(SRCS)
|
||||
$(CC) $(CFLAGS) -Iinclude -Ithird_party/kcp -o $@ $^ $(LDFLAGS
|
||||
|
||||
kcpping: cmd/kcpping.c $(SRCS)
|
||||
$(CC) $(CFLAGS) -Iinclude -Ithird_party/kcp -o $@ $^ $(LDFLAGS
|
||||
|
||||
验证方法
|
||||
|
||||
1. 编译: make all 无错误无警告
|
||||
2. 单机测试:
|
||||
- 启动 hub: ./kcpserver -listen 0.0.0.0:10909
|
||||
- 启动 peer-a: ./kcppeer -id peer-a -server 127.0.0.1:10909 -inbox-dir inbox/a
|
||||
- 启动 peer-b: ./kcppeer -id peer-b -server 127.0.0.1:10909 -inbox-dir inbox/b
|
||||
- peer-b shell 中: text peer-a hello
|
||||
- 验证 peer-a 收到消息并落盘到 inbox/a/
|
||||
3. 跨机器 relay 测试:
|
||||
- D 机器: ./kcpserver -listen 0.0.0.0:10909
|
||||
- C 机器: ./kcpserver -mode relay -listen 0.0.0.0:10909 -relay-remote <D_IP>:10909
|
||||
- A 机器: ./kcppeer -id peer-a -server <D_IP>:10909 -relay-via <C_IP>:10909 -inbox-dir inbox/a
|
||||
- B 机器: ./kcppeer -id peer-b -server <D_IP>:10909 -inbox-dir inbox/b
|
||||
4. kcpping 测试:
|
||||
- echo 端: ./kcpping -id peer-a -server <IP>:10909 -echo
|
||||
- ping 端: ./kcpping -id peer-b -server <IP>:10909 -to peer-a -count 20 -interval 100
|
||||
|
||||
实现顺序
|
||||
|
||||
1. 集成第三方库 (ikcp, cJSON)
|
||||
2. protocol 模块 (消息编解码)
|
||||
3. latencylog 模块 (日志事件)
|
||||
4. transport_kcp 模块 (KCP 连接 + listener 多路复用)
|
||||
5. transport_udp 模块 (UDP 连接,简化 timestamping)
|
||||
6. server_udp_relay 模块 (C 节点 relay)
|
||||
7. server_kcp_hub 模块 (D 节点 hub)
|
||||
8. peer_kcp_client 模块 (A/B 节点 peer + persist)
|
||||
9. interactive 模块 (交互 shell)
|
||||
10. cmd/kcpserver.c 主程序
|
||||
11. cmd/kcppeer.c 主程序
|
||||
12. cmd/kcpping.c 主程序
|
||||
13. Makefile + README
|
||||
14. 编译测试
|
||||
|
||||
简化决策
|
||||
|
||||
- 不实现 TCP 传输: 去除 transport/tcp.go, server/hub.go(TCP版), peer/client.go(TCP版) 等 TCP 相关代码
|
||||
- 不写测试: 去除所有 _test.go 对应的测试代码
|
||||
- 完整实现 Linux timestamping: 完整移植 SO_TIMESTAMPING 的 TX/RX timestamp 采集,包括 errqueue TX sched/software timestamp 和 RX software
|
||||
timestamp,以及对应的 debug logger (KCPPacketDebugLogger, TXTimestampDebugLogger)
|
||||
- 完整实现 KCP session stats: 包括 session-level RTO/SRTT 采样和 JSONL 记录
|
||||
- 不实现 latency summary/chart: 不实现 latencysummary 工具和 HTML chart 生成(这是离线分析工具,不属于核心传输功能)
|
||||
- peer 哈希表: 使用简单链表实现,hub 连接数不多时性能足够
|
||||
|
||||
|
||||
# OmniSocketGo -> OmniSocketC 全量 UDP/KCP 迁移计划
|
||||
|
||||
## Summary
|
||||
- 在仓库新增 `c/` 子项目,作为 Linux-only、C11、`make` 驱动的独立实现;现有 Go 项目保留不动,作为行为对照。
|
||||
- 迁移范围按“全量 Go 对齐,但去掉 TCP 和离线 summary/chart”执行:保留 UDP/KCP 协议、纯 UDP 程序族、KCP 程序族、运行时 JSONL 日志、Linux timestamping、KCP packet debug、KCP session stats、以及 KCP hub-to-hub 内部 relay 能力。
|
||||
- 你当前草案需要修正的关键点有 5 个:`protocol_*frame(int fd, ...)` 不适合 KCP;KCP 必须补齐 `ikcp_update/check` 调度与 conv 多路复用;纯 UDP 程序族不能省略;`latencysummary`/HTML chart 本次不迁移;Makefile 需要修正链接目标并统一输出到 `c/bin/`。
|
||||
|
||||
## Public Interfaces
|
||||
- 新增二进制:`kcpserver`、`kcppeer`、`kcpping`、`udpserver`、`udppeer`、`udpping`、`udprelay`。
|
||||
- `kcpserver` 保留当前 Go 旗标语义:`-mode=hub|relay`、`-listen`、`-bind-device`、`-relay-remote`、deprecated relay aliases、`-latency-log`、`-kcp-ts-debug-log`、`-kcp-session-stats-log`、`-kcp-session-stats-interval`。
|
||||
- `kcppeer` 保留当前 Go 旗标语义:`-id`、`-server`、`-relay-via`、`-to`、`-text`、`-file`、`-bind-ip`、`-bind-device`、`-inbox-dir`、`-interactive`、`-latency-log`、`-kcp-ts-debug-log`、`-kcp-session-stats-log`、`-kcp-session-stats-interval`。
|
||||
- `kcpping`、`udpserver`、`udppeer`、`udpping`、`udprelay` 的参数与输出行为对齐当前 Go 入口;`udpserver` 默认不开 Linux timestamping,只有设置 `-tx-ts-debug-log` 时才启用。
|
||||
- 协议层改为内存接口,不再设计 fd 风格 API:`message_t`、datagram 编解码、stream frame 编解码、增量 frame feed。
|
||||
- 运行时日志层保留当前 JSON 字段和事件名;server/hub 继续作为 black-box relay,不新增端到端业务事件。
|
||||
- 内部网络 API 包括:`udp_conn_t`、`kcp_conn_t`、`kcp_listener_t`、`udp_hub_t`、`kcp_hub_t`、`udp_relay_t`、`udp_client_t`、`kcp_client_t`;KCP hub-to-hub relay 只做库级能力,不新增额外 CLI。
|
||||
|
||||
## Implementation Changes
|
||||
- 目录固定为 `c/include`、`c/src`、`c/cmd`、`c/third_party/{ikcp,cjson}`、`c/bin`、`c/README.md`、`c/Makefile`。
|
||||
- 第三方依赖直接 vendoring 到仓库:`ikcp` 用于 KCP 核心,`cJSON` 同时用于协议头、ping payload、运行时日志。
|
||||
- 协议规则完全保留:`text/file/register/error`、`ServerPeerID`、`8 MiB` 限制、UTF-8 校验、`file_name` 约束、`register/error` 来源与目标约束。
|
||||
- 线上 wire format 完全保留:UDP datagram 为 `[4B headerLen][header JSON][body]`;KCP stream 为 `[4B frameLen][4B headerLen][header JSON][body]`。
|
||||
- inbox 持久化完全保留:文本追加写 `messages.log` JSONL;文件落盘为 `<from>-<messageID>-<baseFileName>`。
|
||||
- UDP 传输层实现 connected/unconnected 两种发送模式,保留 register/forward 消息收发、Linux SO_TIMESTAMPING、TX errqueue 关联、JSONL debug 记录。
|
||||
- KCP 客户端连接采用“一连接一 UDP socket + 一 `ikcpcb` + 一接收线程 + 一 update 线程 + 一阻塞接收缓冲区/条件变量”模型。
|
||||
- KCP 服务端监听采用“单 listener UDP socket + 单 listener RX 线程 + conv->session 表 + accept 队列”模型;每个 session 拥有自己的 `ikcpcb`、update 线程、接收缓冲区和关闭状态,发送通过 listener 共享 socket 和写锁完成。
|
||||
- `kcpserver` 的 relay 模式保持为原始 UDP 端口转发,不解码协议;`udprelay` 同样保持透明字节转发。
|
||||
- 纯 UDP hub、KCP hub、双 peer、双 ping 工具、两套 interactive shell 全部对齐现有 Go 行为。
|
||||
- KCP hub 保留“先本地投递,再尝试 relay”的策略;未知目标、重复注册、已注册 peer 再发 `register/error`、过大 relay 消息等错误路径全部保留。
|
||||
- Linux 观测能力完整迁移:业务事件 JSONL、UDP TX debug、KCP packet debug、KCP session/process stats;不迁移 `latencysummary` 与 HTML chart。
|
||||
|
||||
## Acceptance
|
||||
- 在 Linux 上执行 `make` 能无缺失符号地构建 7 个二进制,并输出到 `c/bin/`。
|
||||
- 纯 UDP 冒烟通过:`udpserver` + 两个 `udppeer` 可双向收发文本和文件,`udpping` 的 echo/ping 正常。
|
||||
- 单 hub KCP 冒烟通过:`kcpserver` + 两个 `kcppeer` 可双向收发文本和文件,`kcpping` 的 echo/ping 正常。
|
||||
- README 目标拓扑通过:D 跑 `kcpserver -mode=hub`,C 跑 `kcpserver -mode=relay`,A 用 `-relay-via C` 连 D,B 直连 D,A/B 双向传输正常。
|
||||
- 全量 Go 对齐场景通过:两个 KCP hub 通过内部 raw UDP relay API 互通,跨 hub 文本、文件、错误回送行为与当前 Go 一致。
|
||||
- 负路径通过:重复注册被拒、未注册 UDP sender 被拒、未知目标返回 `error`、已注册 peer 发送 `register/error` 被拒、oversize relayed message 在实际 `WriteTo` 前被拒、`bind-ip`/`bind-device` 非法值在启动时失败。
|
||||
- 打开任一日志旗标后,生成的 JSONL 记录字段名、事件名、时间戳语义与现有运行时日志一致,并在 Linux 支持的情况下出现非零 kernel timestamps。
|
||||
|
||||
## Assumptions
|
||||
- 默认编译器为 `gcc`/`clang`,编译参数基线为 `-std=c11 -Wall -Wextra -O2 -pthread -D_GNU_SOURCE`。
|
||||
- 本次不迁移任何 Go 测试文件,也不为 C 版编写自动化测试;验证仅靠 Linux 构建和手工场景回归。
|
||||
- 本次不迁移 TCP 入口,也不迁移 `latencysummary`/HTML chart。
|
||||
- hub-to-hub relay 在 C 版中实现为内部库能力,保持与当前 Go 仓库一致的范围,不额外扩展新的公共命令。
|
||||
Reference in New Issue
Block a user