summary refs log tree commit diff
diff options
context:
space:
mode:
authorNakidai <nakidai@disroot.org>2025-07-31 17:12:27 +0300
committerNakidai <nakidai@disroot.org>2025-07-31 17:12:27 +0300
commitb4709429ed88563982412a5a027b92143c37e268 (patch)
treecd56dfeac7cafcfcb69925b12376d79b8776d3fd
downloadfatvpn-6149a454bd2699044d2303f585aea7c67819b351.tar.gz
fatvpn-6149a454bd2699044d2303f585aea7c67819b351.zip
Add files v1.0.0
-rw-r--r--Makefile13
-rw-r--r--common.h74
-rw-r--r--config.h10
-rw-r--r--fatvpn.c215
-rw-r--r--fatvpnd.c228
-rwxr-xr-xfvpn_launch.sh41
-rwxr-xr-xfvpn_stop.sh13
-rwxr-xr-xfvpnd_launch.sh17
-rwxr-xr-xfvpnd_stop.sh9
-rw-r--r--shorttypes.h36
-rw-r--r--sponge-bob.h296
11 files changed, 952 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e760d65
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+
+all: fatvpn fatvpnd
+
+fatvpn: fatvpn.c config.h common.h shorttypes.h sponge-bob.h
+	echo -e "\n"
+	$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
+
+fatvpnd: fatvpnd.c config.h common.h shorttypes.h sponge-bob.h
+	echo -e "\n"
+	$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
+
+clean:
+	rm -f fatvpn fatvpnd
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..9bb5912
--- /dev/null
+++ b/common.h
@@ -0,0 +1,74 @@
+/* common (not config, need code patch after change) */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <time.h>
+#include <net/if.h>
+#include <fcntl.h>
+
+#include <linux/if_tun.h>
+
+#include "shorttypes.h"
+#include "sponge-bob.h"
+
+#define ERRDIE(a, b) if ((a) == -1) perror((b)), exit(1)
+
+#define NONCE_SZ 16
+#define HMAC_SZ  16
+
+u8  key[32];
+u32 prng_state[14];
+
+
+static void send_pkt
+(s32 skt, void *buf, u16 sz, struct sockaddr_storage *addr){
+	ssize_t res;
+	u8 *nonce, *tag;
+
+	/* nonce, tag */
+	nonce = (u8*)buf + sz;
+	tag = nonce + NONCE_SZ;
+	duplex257_prng_rand16(prng_state, nonce);
+
+	/* encrypt */
+	duplex257_ae_encrypt(key, nonce, tag, buf, sz);
+	sz += NONCE_SZ + HMAC_SZ;
+
+	/* send */
+	res = sendto(skt, buf, sz, 0,(struct sockaddr*)addr, sizeof(*addr));
+	ERRDIE(res, "sendto");
+	return;
+}
+
+static s32 recv_pkt
+(s32 skt, void *buf, u16 sz, struct sockaddr_storage *addr){
+	socklen_t skl;
+	s32 res;
+	u8 *nonce, *rtag;
+	u8 tag[HMAC_SZ];
+
+	/* recv */
+	skl = sizeof(*addr);
+	res = recvfrom(skt, buf, sz, 0, (struct sockaddr*)addr, &skl);
+	ERRDIE(res, "recvfrom");
+
+	/* nonce, recieved tag */
+	rtag = buf + res - HMAC_SZ;
+	nonce  = rtag - NONCE_SZ;
+
+	/* decrypt */
+	res -= NONCE_SZ + HMAC_SZ;
+	duplex257_ae_decrypt(key, nonce, tag, buf, res);
+	if (0 != memcmp(tag, rtag, HMAC_SZ)) return -1;
+
+	return res;
+}
+
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..0c43c86
--- /dev/null
+++ b/config.h
@@ -0,0 +1,10 @@
+/* common config */
+
+#define PASSWORD_BUF_SZ 512 /* 256 russian utf8 chars */
+
+/* do not forget to set MTU on tap: MAX_PKT_SZ - HMAC_SZ - NONCE_SZ - 14 */
+#define MAX_PKT_SZ 1440
+
+/* hardcode the password here if you don't want the program to ask for it
+ * when it starts. */
+char password[PASSWORD_BUF_SZ];
diff --git a/fatvpn.c b/fatvpn.c
new file mode 100644
index 0000000..3a03067
--- /dev/null
+++ b/fatvpn.c
@@ -0,0 +1,215 @@
+/* fatvpn client by eeeee
+ * Public Domain */
+
+#include "config.h"
+#include "common.h"
+
+#include <netdb.h>
+
+#define LOGFILE "/tmp/fatvpn.log"
+#define TIMEOUT_P 10 /* ping timeout in seconds */
+
+int main(int argc, char *argv[]){
+	u8  *pkt;
+	s32 tmp, res, tap, fdmax, skt;
+	u32 lptime;
+	struct sockaddr_in  *s4;
+	struct sockaddr_in6 srv, sender;
+	struct addrinfo     *srvi;
+	struct timespec     t;
+	struct timeval      slp;
+	struct termios      attr;
+	struct ifreq        ifr;
+	fd_set rset;
+
+	/* arguments */
+	if (argc != 4){
+		fprintf(stderr, "Using:\n\t"
+			"%s <tap name> <address> <port>\n\n"
+		        "Example:\n\t"
+			"%s fvpn0 169.254.123.123 2222\n\t"
+			"%s tap0 vpn.example.org 5555\n\n",
+		        argv[0], argv[0], argv[0]);
+		exit(1);
+	}
+
+	if (strlen(argv[1]) + 1 > IFNAMSIZ)
+		fputs("tap name is too long\n", stderr), exit(1);
+
+	tmp = 0;
+	do { /* dns, ip/port str to num */
+		res = getaddrinfo(argv[2], argv[3], NULL, &srvi);
+		tmp++;
+		usleep(10000); /* 10 ms */
+	} while (res == EAI_AGAIN && tmp < 5);
+	if (res){
+		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res));
+		exit(1);
+	}
+
+	/* server address */
+	switch (srvi->ai_family){
+	case AF_INET:
+		s4 = (struct sockaddr_in*) srvi->ai_addr;
+		srv.sin6_family = AF_INET6;
+		srv.sin6_port = s4->sin_port;
+		memset(&srv.sin6_addr.s6_addr, 0, 16);
+		srv.sin6_addr.s6_addr[10] = 0xFF;
+		srv.sin6_addr.s6_addr[11] = 0xFF;
+		memcpy(&srv.sin6_addr.s6_addr[12], &s4->sin_addr.s_addr, 4);
+		break;
+	case AF_INET6:
+		memcpy(&srv, srvi->ai_addr, sizeof(srv));
+		break;
+	default:
+		fprintf(stderr, "unsupported addrfamily: %s\n", argv[2]);
+		exit(1);
+	}
+
+	if (password[0] == 0){
+		/* TODO: check std streams redirected to file */
+		/* get term attrs */
+		res = tcgetattr(STDOUT_FILENO, &attr);
+		ERRDIE(res, "tcgetattr");
+
+		/* echo off */
+		attr.c_lflag &= ~ECHO;
+		res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr);
+		ERRDIE(res, "tcsetattr");
+
+		/* get password */
+		fputs("Password: ", stdout), fflush(stdout);
+		pkt = fgets(password, PASSWORD_BUF_SZ, stdin);
+		fputs("\n", stdout);
+		if (pkt == NULL)
+			fputs("can't read password\n", stderr), exit(1);
+
+		/* echo on */
+		attr.c_lflag |= ECHO;
+		res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr);
+		ERRDIE(res, "tcsetattr");
+	}
+
+	/* password without '\r\n' */
+	res = 0;
+	while (password[res] != '\0'
+	    && password[res] != '\r'
+	    && password[res] != '\n') res++;
+
+	/* hash password */
+	sponge256_hash32(key, password, res);
+	memset(password, 0, PASSWORD_BUF_SZ);
+
+	/* feed prng with key and current time */
+	duplex257_prng_feed(prng_state, ((u32*)key)[0]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[1]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[2]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[3]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[4]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[5]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[6]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[7]);
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	duplex257_prng_feed(prng_state, (u32)t.tv_sec);
+	duplex257_prng_feed(prng_state, (u32)t.tv_nsec);
+
+	lptime = (u32)t.tv_sec;
+
+	/* daemonize: redirect std streams */
+	res = open("/dev/null", O_RDONLY); ERRDIE(res, "open");
+	close(STDIN_FILENO), dup(res), close(res);
+	res = open(LOGFILE, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
+	ERRDIE(res, "open");
+	close(STDOUT_FILENO);
+	close(STDERR_FILENO);
+	dup(res), dup(res), close(res);
+
+	/* daemonize: fork 1 */
+	res = fork(); ERRDIE(res, "fork 1");
+	if (res != 0) exit(0);
+
+	/* daemonize: become session leader */
+	setsid();
+
+	/* daemonize: fork 2 */
+	res = fork(); ERRDIE(res, "fork 2");
+	if (res != 0) exit(0); /* kill session leader */
+
+	/* daemonized! */
+
+	/* cur pkt buf */
+	pkt = malloc(MAX_PKT_SZ);
+	if (pkt == NULL) fputs("pkt = malloc() failed\n",stderr), exit(1);
+
+	/* socket, setsockopt */
+	skt = socket(PF_INET6, SOCK_DGRAM, 0); ERRDIE(skt, "socket");
+	res = 1;
+	res = setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res));
+	ERRDIE(res, "SO_REUSEADDR");
+
+	/* tap */
+	tap = open("/dev/net/tun", O_RDWR);
+	ERRDIE(tap, "/dev/net/tun");
+
+	memset(&ifr, 0, sizeof(ifr));
+	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+	strncpy(ifr.ifr_name, argv[1], IFNAMSIZ);
+	res = ioctl(tap, TUNSETIFF, &ifr);
+	ERRDIE(res, "TUNSETIFF");
+
+	fdmax = (tap > skt) ? tap+1 : skt+1;
+
+inf_loop:
+	/* select */
+	FD_ZERO(&rset);
+	FD_SET(skt, &rset);
+	FD_SET(tap, &rset);
+
+	slp.tv_sec = 0;
+	slp.tv_usec = 50000;
+	select(fdmax, &rset, NULL, NULL, &slp);
+
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	if (TIMEOUT_P <= (u32)t.tv_sec - lptime){
+		/* ping for NAT keepalive */
+		send_pkt(skt, pkt, 14, (struct sockaddr_storage*)&srv);
+		lptime = t.tv_sec;
+	}
+
+	/* read packet from tap */
+	if (FD_ISSET(tap, &rset)){
+		res = read(tap, pkt, MAX_PKT_SZ);
+		ERRDIE(res, "read(tap)");
+
+		/* TODO: check res+N > MAX_PKT_SZ */
+		send_pkt(skt, pkt, res, (struct sockaddr_storage*)&srv);
+	}
+
+	/* recv packet from server */
+	if (0 == FD_ISSET(skt, &rset)) goto inf_loop;
+
+	res = recv_pkt(skt, pkt, MAX_PKT_SZ,
+	                                 (struct sockaddr_storage*)&sender);
+	if (res < 14) goto inf_loop; /* ignore too small packets */
+
+	/* compare port and ip */
+	if (srv.sin6_port != sender.sin6_port) goto inf_loop;
+	tmp = memcmp(srv.sin6_addr.s6_addr, sender.sin6_addr.s6_addr, 16);
+	if (tmp != 0) goto inf_loop;
+
+	/* packet recv time for entropy and last packet time */
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	duplex257_prng_feed(prng_state, t.tv_nsec);
+	lptime = t.tv_sec;
+
+	tmp = write(tap, pkt, res);
+	if (tmp < res)
+		fprintf(stderr, "tmp < res, tmp=%d, res=%d\n", tmp, res),
+		exit(1);
+
+	goto inf_loop;
+
+	return 0;
+}
+
+
diff --git a/fatvpnd.c b/fatvpnd.c
new file mode 100644
index 0000000..767f305
--- /dev/null
+++ b/fatvpnd.c
@@ -0,0 +1,228 @@
+/* fatvpn daemon by eeeee
+ * Public Domain */
+
+#include "config.h"
+#include "common.h"
+
+#define LOGFILE "/tmp/fatvpnd.log"
+#define MAX_PEERS 16
+#define TIMEOUT_D  180 /* timeout (no packets) in seconds for disconnect */
+
+struct {
+	struct sockaddr_in6 a;
+	u32 lptime; /* time of last packet */
+	u8 mac[6];  /* internal mac */
+} peer[MAX_PEERS];
+
+u32 ccnt;
+
+int main(int argc, char *argv[]){
+	u8  *pkt;
+	s32 res, tap, fdmax, skt, i, psz;
+	struct sockaddr_in6 c;
+	struct timespec     t;
+	struct termios      attr;
+	struct ifreq        ifr;
+	fd_set rset;
+
+	/* arguments */
+	if (argc != 3 && argc != 4){
+		fprintf(stderr, "Using:\n\t"
+			"%s <tap name> [address] <port>\n\n"
+		        "Example:\n\t"
+			"%s tap0 2222\n\t%s tap0 127.0.0.1 2222\n\n",
+		        argv[0], argv[0], argv[0]);
+		exit(1);
+	}
+
+	memset(&c, 0, sizeof(c));
+	c.sin6_family = AF_INET6;
+	c.sin6_port = htons((u16)atoi(argv[argc-1]));
+	if (argc == 4){
+		res = inet_pton(AF_INET6, argv[2], c.sin6_addr.s6_addr);
+		if (res != 1){
+			res = inet_pton(AF_INET, argv[2], &psz);
+			if (res != 1){
+				fprintf(stderr, "wrong addr: %s\n",argv[2]);
+				exit(1);
+			}
+			c.sin6_addr.s6_addr[10] = 0xFF;
+			c.sin6_addr.s6_addr[11] = 0xFF;
+			memcpy(&c.sin6_addr.s6_addr[12], &psz, 4);
+		}
+	}
+
+	if (strlen(argv[1]) + 1 > IFNAMSIZ)
+		fputs("tap name is too long\n", stderr), exit(1);
+
+	if (password[0] == 0){
+		/* TODO: test std streams redirected to file */
+		/* get term attrs */
+		res = tcgetattr(STDOUT_FILENO, &attr);
+		ERRDIE(res, "tcgetattr");
+
+		/* echo off */
+		attr.c_lflag &= ~ECHO;
+		res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr);
+		ERRDIE(res, "tcsetattr");
+
+		/* get password */
+		fputs("Password: ", stdout), fflush(stdout);
+		pkt = fgets(password, PASSWORD_BUF_SZ, stdin);
+		fputs("\n", stdout);
+		if (pkt == NULL)
+			fputs("can't read password\n", stderr), exit(1);
+
+		/* echo on */
+		attr.c_lflag |= ECHO;
+		res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr);
+		ERRDIE(res, "tcsetattr");
+	}
+
+	/* password without '\r\n' */
+	res = 0;
+	while (password[res] != '\0'
+	    && password[res] != '\r'
+	    && password[res] != '\n') res++;
+
+	/* hash password */
+	sponge256_hash32(key, password, res);
+	memset(password, 0, PASSWORD_BUF_SZ);
+
+	/* feed prng with key and current time */
+	duplex257_prng_feed(prng_state, ((u32*)key)[0]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[1]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[2]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[3]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[4]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[5]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[6]);
+	duplex257_prng_feed(prng_state, ((u32*)key)[7]);
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	duplex257_prng_feed(prng_state, (u32)t.tv_sec);
+	duplex257_prng_feed(prng_state, (u32)t.tv_nsec);
+
+	/* daemonize: redirect std streams */
+	res = open("/dev/null", O_RDONLY); ERRDIE(res, "open(/dev/null)");
+	close(STDIN_FILENO), dup(res), close(res);
+	res = open(LOGFILE, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR);
+	ERRDIE(res, "open(logfile)");
+	close(STDOUT_FILENO);
+	close(STDERR_FILENO);
+	dup(res), dup(res), close(res);
+
+	/* daemonize: fork 1 */
+	res = fork(); ERRDIE(res, "fork 1");
+	if (res != 0) exit(0);
+
+	/* daemonize: become session leader */
+	setsid();
+
+	/* daemonize: fork 2 */
+	res = fork(); ERRDIE(res, "fork 2");
+	if (res != 0) exit(0); /* kill session leader */
+
+	/* daemonized! */
+
+	/* cur pkt buf */
+	pkt = malloc(MAX_PKT_SZ);
+	if (pkt == NULL) fputs("pkt = malloc() failed\n",stderr), exit(1);
+
+	/* socket, setsockopt */
+	skt = socket(PF_INET6, SOCK_DGRAM, 0); ERRDIE(skt, "socket");
+	res = 1;
+	res = setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res));
+	ERRDIE(res, "SO_REUSEADDR");
+
+	/* bind */
+	res = bind(skt, (struct sockaddr*) &c, sizeof(c));
+	ERRDIE(res, "bind");
+
+	/* tap */
+	tap = open("/dev/net/tun", O_RDWR);
+	ERRDIE(tap, "open(/dev/net/tun)");
+
+	memset(&ifr, 0, sizeof(ifr));
+	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+	strncpy(ifr.ifr_name, argv[1], IFNAMSIZ);
+	res = ioctl(tap, TUNSETIFF, &ifr);
+	ERRDIE(res, "TUNSETIFF");
+
+	fdmax = (tap > skt) ? tap+1 : skt+1;
+
+inf_loop:
+	/* "disconnect" peers with timeout */
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	for (i=0; i < ccnt; i++){
+		if (t.tv_sec - peer[i].lptime < TIMEOUT_D) continue;
+		if (i != ccnt-1)
+			memcpy(&peer[i], &peer[ccnt-1], sizeof(peer[0]));
+
+		ccnt--, i--;
+		memset(&peer[ccnt], 0, sizeof(peer[0]));
+	}
+
+	/* select */
+	FD_ZERO(&rset);
+	FD_SET(skt, &rset);
+	FD_SET(tap, &rset);
+
+	select(fdmax, &rset, NULL, NULL, NULL);
+
+	/* read packet from tap */
+	if (FD_ISSET(tap, &rset)){
+		psz = read(tap, pkt, MAX_PKT_SZ);
+		ERRDIE(psz, "read(tap)");
+
+		for (i=0; i < ccnt; i++){
+			if (0 == memcmp(peer[i].mac, pkt, 6)){
+				send_pkt(skt, pkt, psz,
+			             (struct sockaddr_storage*) &peer[i].a);
+				break;
+			}
+		}
+	}
+
+	/* recv packet from client */
+	if (0 == FD_ISSET(skt, &rset)) goto inf_loop;
+
+	psz = recv_pkt(skt, pkt, MAX_PKT_SZ, (struct sockaddr_storage*) &c);
+
+	for (i=0; i < ccnt; i++)
+		if (c.sin6_port == peer[i].a.sin6_port &&
+		    0 == memcmp(c.sin6_addr.s6_addr,
+		        peer[i].a.sin6_addr.s6_addr, 16)) break;
+
+	if (i == ccnt){
+		/* client not connected */
+		if (psz <= 14) /* ethernet header len == 14 */
+			goto inf_loop;
+
+		if (ccnt == MAX_PEERS) goto inf_loop;
+
+		/* add peer to array */
+		memcpy(&peer[i].a, &c, sizeof(c));
+		memcpy(&peer[i].mac, &pkt[6], 6);
+		ccnt++;
+	}
+
+	/* client connected */
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	duplex257_prng_feed(prng_state, t.tv_nsec);
+	peer[i].lptime = t.tv_sec;
+
+	/* if connected peer send small packet, it's a ping packet for NAT
+	 * keepalive, all i need to do is update lptime */
+	if (psz <= 14) goto inf_loop;
+
+	i = write(tap, pkt, psz);
+	if (i == -1) perror("write(tap)");
+	if (i < psz)
+		fprintf(stderr, "i < psz, i=%d, psz=%d\n", i, psz), exit(1);
+
+	goto inf_loop;
+
+	return 0;
+}
+
+
diff --git a/fvpn_launch.sh b/fvpn_launch.sh
new file mode 100755
index 0000000..3b158de
--- /dev/null
+++ b/fvpn_launch.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+set -e
+
+TAP=fvpn0
+INTERNAL_IP=10.43.43.2/24
+INTERNAL_SRV_IP=10.43.43.1
+SERVER=vpn.example.org
+PORT=12345
+MAX_PKT_SZ=1440
+MTU=$(( $MAX_PKT_SZ - 46 ))
+
+mac=$(awk 'BEGIN{
+               srand();
+               printf("ae:ae:ae:%02x:%02x:%02x",
+                       rand()*256, rand()*256, rand()*256);
+           }')
+
+ip tuntap add $TAP mode tap user root
+ip addr add $INTERNAL_IP dev $TAP
+ip link set $TAP mtu $MTU
+ip link set $TAP address $mac
+ip link set $TAP up
+
+./fatvpn $TAP $SERVER $PORT
+
+# route ALL traffic to vpn
+# do not forget to configure your server:
+# 1) ip forwarding on
+# 2) masquarade
+# 3) firewall forward rules configured/disabled
+if [ "$1" = "all" ]; then
+	srv_ip=$(dig +short $SERVER | head -1)
+	gw=$(ip route get $srv_ip | head -1 | awk '{print $3}')
+	ip route add $srv_ip/32 via $gw
+	ip route add   0.0.0.0/1 via $INTERNAL_SRV_IP
+	ip route add 128.0.0.0/1 via $INTERNAL_SRV_IP
+fi
+
+ping -c3 $INTERNAL_SRV_IP
+
diff --git a/fvpn_stop.sh b/fvpn_stop.sh
new file mode 100755
index 0000000..5149fba
--- /dev/null
+++ b/fvpn_stop.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+TAP=fvpn0
+SERVER=vpn.example.org
+
+ip route del   0.0.0.0/1
+ip route del 128.0.0.0/1
+ip route del "$(dig +short $SERVER | head -1)/32"
+
+killall fatvpn
+ip link set $TAP down
+ip tuntap del $TAP mode tap
+
diff --git a/fvpnd_launch.sh b/fvpnd_launch.sh
new file mode 100755
index 0000000..042121b
--- /dev/null
+++ b/fvpnd_launch.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+TAP=fvpn0
+INTERNAL_IP=10.43.43.1/24
+IP=0.0.0.0
+PORT=12345
+MAX_PKT_SZ=1440
+MTU=$(( $MAX_PKT_SZ - 46 ))
+
+ip tuntap add $TAP mode tap
+ip addr add $INTERNAL_IP dev $TAP
+ip link set $TAP mtu $MTU
+ip link set $TAP up
+
+./fatvpnd $TAP $IP $PORT
+
+
diff --git a/fvpnd_stop.sh b/fvpnd_stop.sh
new file mode 100755
index 0000000..bed0acc
--- /dev/null
+++ b/fvpnd_stop.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+TAP=fvpn0
+
+killall fatvpnd
+ip link set $TAP down
+ip tuntap del $TAP mode tap
+
+
diff --git a/shorttypes.h b/shorttypes.h
new file mode 100644
index 0000000..7b2df58
--- /dev/null
+++ b/shorttypes.h
@@ -0,0 +1,36 @@
+#ifndef SHORTTYPES_H
+#define SHORTTYPES_H
+#include <limits.h>
+
+#if CHAR_BIT != 8
+	#error "CHAR_BIT != 8"
+#endif
+
+#if USHRT_MAX != 65535
+	#error "USHRT_MAX != 65535"
+#endif
+
+#if UINT_MAX != 4294967295U
+	#error "UINT_MAX != 4294967295U"
+#endif
+
+typedef   signed char  s8;
+typedef   signed short s16;
+typedef   signed int   s32;
+typedef unsigned char  u8;
+typedef unsigned short u16;
+typedef unsigned int   u32;
+
+#ifdef ULLONG_MAX
+	#if ULLONG_MAX == 18446744073709551615ULL
+		typedef   signed long long s64;
+		typedef unsigned long long u64;
+	#else
+		#error "ULLONG_MAX != 18446744073709551615ULL"
+	#endif
+#else
+	#error "no long long type"
+#endif
+
+
+#endif /* SHORTTYPES_H */
diff --git a/sponge-bob.h b/sponge-bob.h
new file mode 100644
index 0000000..0f7db6b
--- /dev/null
+++ b/sponge-bob.h
@@ -0,0 +1,296 @@
+/* implementation of sponge based crypto by eeeee
+ * Public Domain
+ *
+ * Cryptographic sponge functions:
+ * https://keccak.team/files/CSF-0.1.pdf
+ * https://web.archive.org/web/20250620013520/https://keccak.team/files/CSF-0.1.pdf
+ *
+ * Sponge functions require permutation function. I use gimli:
+ * https://gimli.cr.yp.to/
+ * https://gimli.cr.yp.to/gimli-20170627.pdf
+ * https://web.archive.org/web/20250421113716/https://gimli.cr.yp.to/gimli-20170627.pdf
+ */
+#ifndef SPONGE_BOB_H
+#define SPONGE_BOB_H
+
+#include <arpa/inet.h>
+#include <string.h>
+#include "shorttypes.h"
+
+#define ROTL32(x, n) (((x)<<(n)) | ((x)>>(32-(n))))
+
+static void gimli(void *s){
+	u32 round, column;
+	u32 x, y, z;
+	u32 *state = s;
+
+	for (round = 24; round != 0; --round){
+		for (column = 0; column < 4; ++column){
+			x = ROTL32(state[    column], 24);
+			y = ROTL32(state[4 + column],  9);
+			z =        state[8 + column];
+
+			state[8 + column] = x ^ (z << 1) ^ ((y&z) << 2);
+			state[4 + column] = y ^ x        ^ ((x|z) << 1);
+			state[    column] = z ^ y        ^ ((x&y) << 3);
+		}
+
+		if ((round & 3) == 0) { // small swap: pattern s...s...s...
+			x = state[0];
+			state[0] = state[1];
+			state[1] = x;
+			x = state[2];
+			state[2] = state[3];
+			state[3] = x;
+		}
+
+		if ((round & 3) == 2) { // big swap: ..S...S...S. etc.
+			x = state[0];
+			state[0] = state[2];
+			state[2] = x;
+			x = state[1];
+			state[1] = state[3];
+			state[3] = x;
+		}
+
+		if ((round & 3) == 0) { // add constant: c...c...c... etc.
+			state[0] ^= (0x9e377900 | round);
+		}
+	}
+
+	return;
+}
+
+/* gimli, endian-agnostic version */
+static void gimli_ea(void *s){
+	u32 *state = s;
+
+	state[ 0] = ntohl(state[ 0]);
+	state[ 1] = ntohl(state[ 1]);
+	state[ 2] = ntohl(state[ 2]);
+	state[ 3] = ntohl(state[ 3]);
+	state[ 4] = ntohl(state[ 4]);
+	state[ 5] = ntohl(state[ 5]);
+	state[ 6] = ntohl(state[ 6]);
+	state[ 7] = ntohl(state[ 7]);
+	state[ 8] = ntohl(state[ 8]);
+	state[ 9] = ntohl(state[ 9]);
+	state[10] = ntohl(state[10]);
+	state[11] = ntohl(state[11]);
+
+	gimli(s);
+
+	state[ 0] = htonl(state[ 0]);
+	state[ 1] = htonl(state[ 1]);
+	state[ 2] = htonl(state[ 2]);
+	state[ 3] = htonl(state[ 3]);
+	state[ 4] = htonl(state[ 4]);
+	state[ 5] = htonl(state[ 5]);
+	state[ 6] = htonl(state[ 6]);
+	state[ 7] = htonl(state[ 7]);
+	state[ 8] = htonl(state[ 8]);
+	state[ 9] = htonl(state[ 9]);
+	state[10] = htonl(state[10]);
+	state[11] = htonl(state[11]);
+
+	return;
+}
+
+#ifndef FPERMUTE
+#define FPERMUTE gimli_ea
+#endif
+
+#define MEMXOR16(dst, src) \
+	((u32*)dst)[0] ^= ((u32*)src)[0]; \
+	((u32*)dst)[1] ^= ((u32*)src)[1]; \
+	((u32*)dst)[2] ^= ((u32*)src)[2]; \
+	((u32*)dst)[3] ^= ((u32*)src)[3]
+
+#define MEMXOR32(dst, src) \
+	((u32*)dst)[0] ^= ((u32*)src)[0]; \
+	((u32*)dst)[1] ^= ((u32*)src)[1]; \
+	((u32*)dst)[2] ^= ((u32*)src)[2]; \
+	((u32*)dst)[3] ^= ((u32*)src)[3]; \
+	((u32*)dst)[4] ^= ((u32*)src)[4]; \
+	((u32*)dst)[5] ^= ((u32*)src)[5]; \
+	((u32*)dst)[6] ^= ((u32*)src)[6]; \
+	((u32*)dst)[7] ^= ((u32*)src)[7]
+
+
+/* reseedable prng
+ * state[12]: input buffer index
+ * state[13]: output buffer index
+ */
+static void duplex257_prng_feed(u32 state[14], u32 food){
+	state[13] = 0;
+	state[state[12]] ^= food;
+	state[12]++;
+	if (state[12] == 8){
+		((u8*)state)[32] ^= 0x80;
+		state[12] = 0;
+		FPERMUTE(state);
+	}
+	return;
+}
+
+static u32 duplex257_prng_rand(u32 state[14]){
+	u32 res;
+	if (state[12]){
+		((u8*)state)[state[12]] ^= 0x80;
+		FPERMUTE(state);
+		state[12] = 0;
+		state[13] = 1;
+		return state[0];
+	}
+
+	res = state[state[13]];
+	state[13]++;
+	if (state[13] == 8){
+		state[13] = 0;
+		((u8*)state)[0] ^= 0x80;
+		FPERMUTE(state);
+	}
+
+	return res;
+}
+
+static void duplex257_prng_rand16(u32 state[14], u8 dst[16]){
+	if (state[12]){
+		((u8*)state)[state[12]] ^= 0x80;
+		FPERMUTE(state);
+		state[12] = 0;
+		state[13] = 4;
+		memcpy(dst, state, 16);
+		return;
+	}
+
+	if (state[13] == 0){
+		state[13] = 4;
+		memcpy(dst, state, 16);
+	} else if (state[13] < 5){
+		state[13] = 0;
+		memcpy(dst, &state[4], 16);
+		((u8*)state)[0] ^= 0x80;
+		FPERMUTE(state);
+	} else {
+		((u8*)state)[0] ^= 0x80;
+		FPERMUTE(state);
+		state[13] = 4;
+		memcpy(dst, state, 16);
+	}
+	return;
+}
+
+/* duplex257 AE */
+static void duplex257_ae_encrypt
+    (const u8 key[32], const u8 nonce[16], u8 tag[16], u8 *src_dst, u32 sz){
+	static u8 state[48];
+	u32 i, n;
+
+	/* duplex_mute(key) */
+	memcpy(state, key, 32);
+	state[32] = 0x80;
+	FPERMUTE(state);
+
+	/* duplex_mute(nonce) */
+	MEMXOR16(state, nonce);
+	state[16] ^= 0x80;
+	FPERMUTE(state);
+
+	/* 32 bytes steps */
+	n = sz >> 5;
+	while (n){
+		/* duplex(src_dst), encrypt */
+		MEMXOR32(src_dst, state);
+		memcpy(state, src_dst, 32);
+		state[32] ^= 0x80;
+		FPERMUTE(state);
+		src_dst += 32;
+		n--;
+	}
+
+	/* 1 byte steps */
+	n = sz & 0x1Fu;
+	/* duplex(src_dst), encrypt */
+	for (i = 0; i < n; i++) src_dst[i] ^= state[i];
+	memcpy(state, src_dst, n);
+	state[n] ^= 0x80;
+	FPERMUTE(state);
+
+	memcpy(tag, state, 16);
+	memset(state, 0, sizeof(state));
+	return;
+}
+
+static void duplex257_ae_decrypt
+    (const u8 key[32], const u8 nonce[16], u8 tag[16], u8 *src_dst, u32 sz){
+	static u8 state[48];
+	u32 i, n;
+
+	/* duplex_mute(key) */
+	memcpy(state, key, 32);
+	state[32] = 0x80;
+	FPERMUTE(state);
+
+	/* duplex_mute(nonce) */
+	MEMXOR16(state, nonce);
+	state[16] ^= 0x80;
+	FPERMUTE(state);
+
+	/* 32 bytes steps */
+	n = sz >> 5;
+	while (n){
+		/* duplex(src_dst), encrypt */
+		MEMXOR32(src_dst, state);
+		MEMXOR32(state, src_dst);
+		state[32] ^= 0x80;
+		FPERMUTE(state);
+		src_dst += 32;
+		n--;
+	}
+
+	/* 1 byte steps */
+	n = sz & 0x1Fu;
+	/* duplex(src_dst), encrypt */
+	for (i = 0; i < n; i++){
+		src_dst[i] ^= state[i];
+		state[i] ^= src_dst[i];
+	}
+	state[n] ^= 0x80;
+	FPERMUTE(state);
+
+	memcpy(tag, state, 16);
+	memset(state, 0, sizeof(state));
+	return;
+}
+
+/* sponge */
+static void sponge256_absorb(u8 state[48], const u8 *src, u32 sz){
+	u32 n, i;
+
+	n = sz >> 5;
+	while (n){ /* 32 byte steps */
+		MEMXOR32(state, src);
+		FPERMUTE(state);
+		src += 32;
+		n--;
+	}
+
+	/* 1 byte steps */
+	n = sz & 0x1Fu;
+	for (i=0; i < n; i++) state[i] ^= src[i];
+	state[i] ^= 0x80;
+	FPERMUTE(state);
+	return;
+}
+
+/* sponge hash */
+static void sponge256_hash32(u8 dst[32], u8 *src, u32 sz){
+	static u8 state[48];
+	sponge256_absorb(state, src, sz);
+	memcpy(dst, state, 32);
+	memset(state, 0, sizeof(state));
+	return;
+}
+
+#endif /* SPONGE_BOB_H */