diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | LICENSE | 10 | ||||
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | README | 20 | ||||
| -rw-r--r-- | ttb.1 | 31 | ||||
| -rw-r--r-- | ttb.c | 126 |
6 files changed, 212 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2f4cf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ttb diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7ae8865 --- /dev/null +++ b/LICENSE @@ -0,0 +1,10 @@ +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a4185d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +RM ?= rm -f +PREFIX ?= /usr/local/ +BINDIR ?= ${PREFIX}/bin +MANDIR ?= ${PREFIX}/man + + +.PHONY: all +all: ttb + +install: all + install -d ${BINDIR} ${MANDIR}/man1 + install -m755 ttb ${BINDIR} + install -m644 ttb.1 ${MANDIR}/man1 + +uninstall: + ${RM} ${BINDIR}/ttb + ${RM} ${MANDIR}/man1/ttb.1 + +.PHONY: clean +clean: + ${RM} ttb + +README: ttb.1 + mandoc -Ios=ttb -Tascii ttb.1 | col -b > README diff --git a/README b/README new file mode 100644 index 0000000..72ff7fc --- /dev/null +++ b/README @@ -0,0 +1,20 @@ +TTB(1) General Commands Manual TTB(1) + +NAME + ttb - tiny tcp broadcast + +SYNOPSIS + ttb [port [address]] + +DESCRIPTION + ttb is a server that broadcasts all incoming messages from any connection + to every other one. + + The arguments are as follows: + + port Port to listen. Default is 8604. + + address + Address to listen. Default is 0.0.0.0. + +ttb April 17, 2025 ttb diff --git a/ttb.1 b/ttb.1 new file mode 100644 index 0000000..1a1a501 --- /dev/null +++ b/ttb.1 @@ -0,0 +1,31 @@ +.Dd April 17, 2025 +.Dt TTB 1 +.Os +. +.Sh NAME +.Nm ttb +.Nd tiny tcp broadcast +. +.Sh SYNOPSIS +.Nm +.Op Ar port Op Ar address +. +.Sh DESCRIPTION +.Nm +is a server +that broadcasts all incoming messages +from any connection +to every other one. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Ar port +Port to listen. +Default is +.Dv 8604 . +.It Ar address +Address to listen. +Default is +.Dv 0.0.0.0 . +.El diff --git a/ttb.c b/ttb.c new file mode 100644 index 0000000..bbf20bf --- /dev/null +++ b/ttb.c @@ -0,0 +1,126 @@ +#include <errno.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include <err.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> + + +#define SOCKETS_MAX 128 +#define POLL_WAITMS 1000 +#define BUFFER_SIZE 512 + +#define lengthof(X) (sizeof(X) / sizeof(*(X))) + +int main(int argc, char **argv) +{ + int res; + + int peers[SOCKETS_MAX] = {0}; + struct pollfd ppeers[SOCKETS_MAX] = {0}; + char buf[BUFFER_SIZE] = {0}; + + int port = 8604; + if (argc >= 2) + port = atoi(argv[1]); + + struct in_addr inaddr = (struct in_addr) + { .s_addr = INADDR_ANY }; + if (argc >= 3) + { + res = inet_pton(AF_INET, argv[2], &inaddr); + if (res == -1) + err(1, "inet_pton()"); + } + + int sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (sock == -1) + err(1, "socket()"); + + res = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + if (res == -1) + err(1, "setsockopt()"); + + struct sockaddr_in addr = (struct sockaddr_in) + { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = inaddr, + }; + res = bind(sock, (void *)&addr, sizeof(addr)); + if (res == -1) + err(1, "bind()"); + + res = listen(sock, 16); + if (res == -1) + err(1, "listen()"); + + signal(SIGPIPE, SIG_IGN); + for (;;) + { + int client = accept(sock, NULL, NULL); + if (client > 0) + { + for (size_t i = 0; i < lengthof(peers); ++i) + if (!peers[i]) + { + res = fcntl(client, F_SETFL, fcntl(client, F_GETFL, 0) | O_NONBLOCK); + if (res == -1) + { + warn("fcntl()"); + goto accept_err; + } + peers[i] = client; + goto accept_end; + } + warnx("dropping %d as peer list is full", client); +accept_err: + close(client); + } +accept_end: + nfds_t written = 0; + for (size_t i = 0; i < lengthof(peers); ++i) + if (peers[i]) + ppeers[written++] = (struct pollfd) + { + .fd = peers[i], + .events = POLLIN, + }; + res = poll(ppeers, written, POLL_WAITMS); + if (res == -1) + err(1, "poll"); + if (!res) + continue; + + for (size_t i = 0; i < lengthof(peers); ++i) + { + if (!peers[i]) + continue; + + errno = 0; + ssize_t reads = read(peers[i], buf, sizeof(buf)); + if (reads == -1 || !reads) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + close(peers[i]); + peers[i] = 0; + } + continue; + } + + for (size_t j = 0; j < lengthof(peers); ++j) + if (peers[j] && i != j) + write(peers[j], buf, reads); + } + } +} |