summary refs log tree commit diff
diff options
context:
space:
mode:
authorNakidai <nakidai@disroot.org>2025-04-17 22:07:39 +0300
committerNakidai <nakidai@disroot.org>2025-04-17 22:07:39 +0300
commitf26e500b97d18313ef68d92337d4d612099ef9ea (patch)
tree77ec72c434b29f356220a5cc8f22b46a72918145
downloadttb-f26e500b97d18313ef68d92337d4d612099ef9ea.tar.gz
ttb-f26e500b97d18313ef68d92337d4d612099ef9ea.zip
Add files v1.0.0
-rw-r--r--.gitignore1
-rw-r--r--LICENSE10
-rw-r--r--Makefile24
-rw-r--r--README20
-rw-r--r--ttb.131
-rw-r--r--ttb.c126
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);
+		}
+	}
+}