diff options
Diffstat (limited to 'src/tools')
| -rw-r--r-- | src/tools/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | src/tools/irm/irm_ipcp_connect.c | 22 | ||||
| -rw-r--r-- | src/tools/ocbr/ocbr_client.c | 32 | ||||
| -rw-r--r-- | src/tools/oftp/oftp.c | 441 | ||||
| -rw-r--r-- | src/tools/oping/oping.c | 20 |
5 files changed, 499 insertions, 21 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 3cec8172..6b418838 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -63,6 +63,11 @@ target_include_directories(operf PRIVATE ${TOOLS_INCLUDE_DIRS}) target_link_libraries(operf PRIVATE ouroboros-dev) install(TARGETS operf RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +add_executable(oftp oftp/oftp.c) +target_include_directories(oftp PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(oftp PRIVATE ouroboros-dev) +install(TARGETS oftp RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_executable(ovpn ovpn/ovpn.c) target_include_directories(ovpn PRIVATE ${TOOLS_INCLUDE_DIRS}) diff --git a/src/tools/irm/irm_ipcp_connect.c b/src/tools/irm/irm_ipcp_connect.c index f88c36dc..fb21faec 100644 --- a/src/tools/irm/irm_ipcp_connect.c +++ b/src/tools/irm/irm_ipcp_connect.c @@ -100,16 +100,18 @@ int do_connect_ipcp(int argc, } if (qos != NULL) { - if (strcmp(qos, "best") == 0) - qs = qos_best_effort; - else if (strcmp(qos, "raw") == 0) + if (strcmp(qos, "raw") == 0) qs = qos_raw; - else if (strcmp(qos, "video") == 0) - qs = qos_video; - else if (strcmp(qos, "voice") == 0) - qs = qos_voice; - else if (strcmp(qos, "data") == 0) - qs = qos_data; + else if (strcmp(qos, "safe") == 0) + qs = qos_raw_safe; + else if (strcmp(qos, "rt") == 0) + qs = qos_rt; + else if (strcmp(qos, "rt-safe") == 0) + qs = qos_rt_safe; + else if (strcmp(qos, "msg") == 0) + qs = qos_msg; + else if (strcmp(qos, "stream") == 0) + qs = qos_stream; else printf("Unknown QoS cube, defaulting to raw.\n"); } @@ -126,7 +128,7 @@ int do_connect_ipcp(int argc, if (wildcard_match(comp, MGMT) == 0) { component = MGMT_COMP; - /* FIXME: move to qos_data when stable */ + /* FIXME: move to qos_msg when stable */ if (irm_connect_ipcp(pid, dst, component, qos_raw)) return -1; } diff --git a/src/tools/ocbr/ocbr_client.c b/src/tools/ocbr/ocbr_client.c index 3da3beb7..36c07d43 100644 --- a/src/tools/ocbr/ocbr_client.c +++ b/src/tools/ocbr/ocbr_client.c @@ -37,8 +37,11 @@ */ #include <ouroboros/dev.h> +#include <ouroboros/qos.h> #include <signal.h> +#include <stdlib.h> +#include <string.h> volatile bool stop; @@ -86,6 +89,11 @@ int client_main(char * server, struct timespec end; struct timespec intv = {(gap / BILLION), gap % BILLION}; int ms; + const char * qenv; + qosspec_t qs; + qosspec_t * qsp; + + qsp = NULL; stop = false; @@ -104,7 +112,29 @@ int client_main(char * server, printf("Client started, duration %d, rate %lu b/s, size %d B.\n", duration, rate, size); - fd = flow_alloc(server, NULL, NULL); + qenv = getenv("OCBR_QOS"); + if (qenv != NULL) { + if (strcmp(qenv, "raw") == 0) + qs = qos_raw; + else if (strcmp(qenv, "safe") == 0) + qs = qos_raw_safe; + else if (strcmp(qenv, "rt") == 0) + qs = qos_rt; + else if (strcmp(qenv, "rt_safe") == 0) + qs = qos_rt_safe; + else if (strcmp(qenv, "msg") == 0) + qs = qos_msg; + else if (strcmp(qenv, "stream") == 0) + qs = qos_stream; + else { + fprintf(stderr, + "Unknown OCBR_QOS='%s', using raw.\n", qenv); + qs = qos_raw; + } + qsp = &qs; + printf("OCBR_QOS=%s\n", qenv); + } + fd = flow_alloc(server, qsp, NULL); if (fd < 0) { printf("Failed to allocate flow.\n"); return 2; diff --git a/src/tools/oftp/oftp.c b/src/tools/oftp/oftp.c new file mode 100644 index 00000000..1ae99403 --- /dev/null +++ b/src/tools/oftp/oftp.c @@ -0,0 +1,441 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * A minimal file-transfer tool over an FRCT stream flow + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _POSIX_C_SOURCE 200809L + +#include <ouroboros/crc64.h> +#include <ouroboros/dev.h> +#include <ouroboros/errno.h> +#include <ouroboros/fccntl.h> +#include <ouroboros/qos.h> + +#include <fcntl.h> +#include <inttypes.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#define BUF_SIZE 16384 + +static volatile sig_atomic_t stop = 0; + +static void apply_rto_min_env(int fd) +{ + const char * env; + long v; + + env = getenv("OFTP_FRCT_RTO_MIN"); + if (env == NULL) + return; + v = strtol(env, NULL, 10); + if (v <= 0) + return; + if (fccntl(fd, FRCTSRTOMIN, (time_t) v) < 0) + fprintf(stderr, + "oftp: failed to set RTO_MIN=%ld ns\n", v); +} + +static void apply_stream_ring_sz_env(int fd) +{ + const char * env; + long v; + + env = getenv("OFTP_FRCT_STREAM_RING_SZ"); + if (env == NULL) + return; + v = strtol(env, NULL, 10); + if (v <= 0) + return; + if (fccntl(fd, FRCTSRRINGSZ, (size_t) v) < 0) + fprintf(stderr, + "oftp: failed to set STREAM_RING_SZ=%ld\n", v); +} + +static void on_signal(int signo) +{ + (void) signo; + stop = 1; +} + +static void usage(void) +{ + printf("Usage: oftp [OPTION]...\n" + "Stream-mode file transfer over an Ouroboros flow.\n\n" + " -l, --listen Run as the receiver (server)\n" + " -n, --name NAME Destination service name (client)\n" + " -i, --in FILE Read input from FILE (default stdin)\n" + " -o, --out FILE Write output to FILE (default stdout)\n" + " -N, --bytes SIZE Stop after SIZE bytes " + "(K/M/G suffix; client only)\n" + " --help Display this help text and exit\n"); +} + +static int parse_size(const char * s, size_t * out) +{ + char * end; + unsigned long v; + size_t mul; + + v = strtoul(s, &end, 0); + if (end == s) + return -1; + + mul = 1; + if (*end == 'k' || *end == 'K') + mul = 1024UL; + else if (*end == 'm' || *end == 'M') + mul = 1024UL * 1024UL; + else if (*end == 'g' || *end == 'G') + mul = 1024UL * 1024UL * 1024UL; + else if (*end != '\0') + return -1; + + *out = (size_t) v * mul; + return 0; +} + +static void report_xfer(const char * tag, + size_t total, + uint64_t crc, + const struct timespec * t0, + const struct timespec * t1) +{ + double elapsed_s; + double mib_per_s; + + elapsed_s = (t1->tv_sec - t0->tv_sec) + + (t1->tv_nsec - t0->tv_nsec) / 1e9; + if (elapsed_s <= 0.0) + elapsed_s = 1e-9; + + mib_per_s = ((double) total / (1024.0 * 1024.0)) / elapsed_s; + + fprintf(stderr, + "oftp: %s %zu bytes in %.3f s (%.2f MiB/s) " + "crc64=%016" PRIx64 "\n", + tag, total, elapsed_s, mib_per_s, crc); +} + +static int xfer_to_flow(int fd, FILE * in, size_t max_bytes) +{ + char buf[BUF_SIZE]; + size_t n; + size_t total; + size_t want; + size_t off; + ssize_t w; + uint64_t crc; + struct timespec t0; + struct timespec t1; + + total = 0; + crc = 0; + + clock_gettime(CLOCK_MONOTONIC, &t0); + + while (!stop) { + want = sizeof(buf); + if (max_bytes > 0 && max_bytes - total < want) + want = max_bytes - total; + if (want == 0) + break; + + n = fread(buf, 1, want, in); + if (n == 0) + break; + + crc64_nvme(&crc, buf, n); + + off = 0; + while (off < n) { + w = flow_write(fd, buf + off, n - off); + if (w < 0) { + fprintf(stderr, + "flow_write failed: %zd\n", w); + return 1; + } + off += (size_t) w; + total += (size_t) w; + } + } + + clock_gettime(CLOCK_MONOTONIC, &t1); + + if (ferror(in)) { + fprintf(stderr, "Input read error.\n"); + return 1; + } + + report_xfer("sent", total, crc, &t0, &t1); + return 0; +} + +static int xfer_from_flow(int fd, FILE * out) +{ + char buf[BUF_SIZE]; + size_t total; + ssize_t n; + uint64_t crc; + struct timespec timeout; + struct timespec t0; + struct timespec t1; + bool started; + + total = 0; + crc = 0; + started = false; + timeout.tv_sec = 1; + timeout.tv_nsec = 0; + + /* Short timeout so SIGTERM/SIGINT 'stop' is observed promptly. */ + fccntl(fd, FLOWSRCVTIMEO, &timeout); + + while (!stop) { + n = flow_read(fd, buf, sizeof(buf)); + if (n == 0) { + /* Clean EOF: peer sent EOS and we drained it. */ + clock_gettime(CLOCK_MONOTONIC, &t1); + fflush(out); + if (!started) + t0 = t1; + report_xfer("received", total, crc, &t0, &t1); + return 0; + } + if (n == -ETIMEDOUT) + continue; + if (n < 0) { + /* Peer aborted before EOS: partial transfer. */ + if (n == -EFLOWDOWN || n == -EFLOWPEER) { + fprintf(stderr, + "oftp: peer aborted at %zu B\n", + total); + return 2; + } + fprintf(stderr, + "flow_read failed: %zd\n", n); + return 1; + } + if (!started) { + clock_gettime(CLOCK_MONOTONIC, &t0); + started = true; + } + crc64_nvme(&crc, buf, (size_t) n); + if (fwrite(buf, 1, (size_t) n, out) != (size_t) n) { + fprintf(stderr, "Output write error.\n"); + return 1; + } + total += (size_t) n; + } + + /* Receiver was signalled (SIGINT/SIGTERM) before EOF. */ + fflush(out); + fprintf(stderr, "oftp: interrupted at %zu B\n", total); + return 2; +} + +static int server_main(const char * outpath) +{ + FILE * out = stdout; + int fd; + int ofd; + int rc; + qosspec_t qs; + + if (outpath != NULL) { + ofd = open(outpath, + O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, + 0600); + if (ofd < 0) { + perror("open"); + return 1; + } + out = fdopen(ofd, "wb"); + if (out == NULL) { + perror("fdopen"); + close(ofd); + unlink(outpath); + return 1; + } + } + + fprintf(stderr, "oftp: listening...\n"); + + fd = flow_accept(&qs, NULL); + if (fd < 0) { + fprintf(stderr, "flow_accept failed: %d\n", fd); + if (out != stdout) + fclose(out); + return 1; + } + + if (qs.service != SVC_STREAM) { + fprintf(stderr, + "oftp: rejecting non-stream flow (service=%u)\n", + qs.service); + flow_dealloc(fd); + if (out != stdout) { + fclose(out); + unlink(outpath); + } + return 1; + } + + apply_rto_min_env(fd); + apply_stream_ring_sz_env(fd); + + rc = xfer_from_flow(fd, out); + + flow_dealloc(fd); + + if (out != stdout) { + fclose(out); + /* Drop the half-written file on abort/interrupt. */ + if (rc != 0) + unlink(outpath); + } + + return rc; +} + +static int client_main(const char * name, + const char * inpath, + size_t max_bytes) +{ + FILE * in; + int fd; + int rc; + qosspec_t qs; + + in = stdin; + qs = qos_stream; + + if (inpath != NULL) { + in = fopen(inpath, "rb"); + if (in == NULL) { + perror("fopen"); + return 1; + } + } + + fd = flow_alloc(name, &qs, NULL); + if (fd < 0) { + fprintf(stderr, "flow_alloc failed: %d\n", fd); + if (in != stdin) + fclose(in); + return 2; + } + + apply_rto_min_env(fd); + apply_stream_ring_sz_env(fd); + + rc = xfer_to_flow(fd, in, max_bytes); + + flow_dealloc(fd); + + if (in != stdin) + fclose(in); + + return rc; +} + +int main(int argc, char ** argv) +{ + bool server; + const char * name; + const char * inpath; + const char * outpath; + size_t max_bytes; + struct sigaction sa; + + server = false; + name = NULL; + inpath = NULL; + outpath = NULL; + max_bytes = 0; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = on_signal; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + signal(SIGPIPE, SIG_IGN); + + argc--; argv++; + while (argc > 0) { + if (strcmp(*argv, "-l") == 0 || + strcmp(*argv, "--listen") == 0) { + server = true; + } else if ((strcmp(*argv, "-n") == 0 || + strcmp(*argv, "--name") == 0) && argc > 1) { + name = *(++argv); argc--; + } else if ((strcmp(*argv, "-i") == 0 || + strcmp(*argv, "--in") == 0) && argc > 1) { + inpath = *(++argv); argc--; + } else if ((strcmp(*argv, "-o") == 0 || + strcmp(*argv, "--out") == 0) && argc > 1) { + outpath = *(++argv); argc--; + } else if ((strcmp(*argv, "-N") == 0 || + strcmp(*argv, "--bytes") == 0) && argc > 1) { + if (parse_size(*(++argv), &max_bytes) < 0) { + fprintf(stderr, + "oftp: bad size '%s'\n", *argv); + return 1; + } + argc--; + } else if (strcmp(*argv, "--help") == 0) { + usage(); + return 0; + } else { + usage(); + return 1; + } + argc--; argv++; + } + + if (server) + return server_main(outpath); + + if (name == NULL) { + usage(); + return 1; + } + + return client_main(name, inpath, max_bytes); +} diff --git a/src/tools/oping/oping.c b/src/tools/oping/oping.c index 5c9d35a5..10e1e23c 100644 --- a/src/tools/oping/oping.c +++ b/src/tools/oping/oping.c @@ -81,7 +81,7 @@ " -F, --flood-busy Flood with busy-polling (lower latency)\n" \ " -i, --interval Interval (default 1000ms)\n" \ " -n, --server-name Name of the oping server\n" \ -" -q, --qos QoS (raw, best, video, voice, data)\n" \ +" -q, --qos QoS (raw, safe, rt, rt-safe, msg)\n" \ " -s, --size Payload size (B, default 64)\n" \ " -W, --timeout Per-packet recv timeout, ms (default 2000)\n" \ " -Q, --quiet Only print final statistics\n" \ @@ -269,16 +269,16 @@ int main(int argc, } if (qos != NULL) { - if (strcmp(qos, "best") == 0) - client.qs = qos_best_effort; - else if (strcmp(qos, "raw") == 0) + if (strcmp(qos, "raw") == 0) client.qs = qos_raw; - else if (strcmp(qos, "video") == 0) - client.qs = qos_video; - else if (strcmp(qos, "voice") == 0) - client.qs = qos_voice; - else if (strcmp(qos, "data") == 0) - client.qs = qos_data; + else if (strcmp(qos, "safe") == 0) + client.qs = qos_raw_safe; + else if (strcmp(qos, "rt") == 0) + client.qs = qos_rt; + else if (strcmp(qos, "rt-safe") == 0) + client.qs = qos_rt_safe; + else if (strcmp(qos, "msg") == 0) + client.qs = qos_msg; else printf("Unknown QoS cube, defaulting to raw.\n"); } |
