diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a41d7c8 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +first: main.c + gcc -o tuninetd main.c -lpthread -lpcap diff --git a/README.md b/README.md index 3df1379..d21867f 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ -# tuninetd \ No newline at end of file +# Tuninetd + +**tuninetd** - is a simple daemon for tun/tap devices, similar to classic inetd by its logic, but for mentioned interfaceces, instead of a ports. + +#### How it works: +First, you create and configure tun/tap device, then run **tuninetd**. It start listening on that interface, until network packet will be received. +Next, interface will be released and certain command is executed. From now on, daemon in monitoring state. +After N seconds of interface idle, tuninetd send "stop" command by path, that you define, and start listening interface by its own again. + +Since, **tuninetd** based on **libpcap**, you can specify filter to trigging "start" event and monitoring iddle (i.e. cutoff unwanted traffic). +To test/debug filters rules - use tcpdump, because it built upon the same library. + +**tuninetd** allows you deploy "VPN by demand" or any other "by demand" services, which is the main idea of the project. + +#### Installation: +To build tuninetd, you need to have libpcap-dev library (Debian)
+Download all files and: +```sh +# cd /folder/with/sourcefiles +# make +``` + +#### Usage: + +tuninetd -i \ -c \ [-m \] [-f ] [-t \] [-d] + +**-i \**: interface to use (tun or tap). Must be up and configured.
+**-c \**: will be executed with 'start' and 'stop' parameter.
+**-m \**: 'tun' or 'tap' mode. By default 'tun', should be set properly.
+**-f \**: specify pcap filter, similar to tcpdump
+**-t \**: seconds of interface idle, before 'stop' command (default is 600).
+**-d**: demonize process
+**-h**: prints this help text + +#### Example: +```sh +# tuninetd -i tun0 -c /test/runtunnel.sh -f "! host 1.2.3.4" -t 3600 -d +``` + +### License: +MIT +### Author: +Paul aka root4root \
+**Any suggestions will be appreciated.** diff --git a/main.c b/main.c new file mode 100755 index 0000000..399fd6a --- /dev/null +++ b/main.c @@ -0,0 +1,162 @@ +//Author: root4root@gmail.com + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSIZE 2000 + +int debug = 0; +int status = 0; +long ts = 0; +long curts = 0; + +char progname[] = "tuninetd"; + +struct globcfg_t { + int isdaemon; + pid_t pid; + char *cmd_path; + char *cmd_path_start; + char *cmd_path_stop; + char *pcap_filter; + char *dev_name; + int dev_mode; + int ttl; +} globcfg; + +#include "utils.c" +#include "tun.c" +#include "pcap.c" + +int main(int argc, char *argv[]) +{ + int x, y, opt=0; + + static const char *optString = "i:t:c:f:m:dh"; + + curts = time(NULL); + + globcfg.isdaemon = 0; + globcfg.pid = 0; + globcfg.cmd_path = NULL; + globcfg.ttl = 600; + globcfg.dev_mode = IFF_TUN; + + opt = getopt( argc, argv, optString); + + while( opt != -1 ) { + switch( opt ) { + case 'i': + globcfg.dev_name = optarg; + break; + case 't': + globcfg.ttl = atoi(optarg); + break; + case 'c': + globcfg.cmd_path = optarg; + + globcfg.cmd_path_start = malloc(strlen(optarg) + 23); + strcpy(globcfg.cmd_path_start, optarg); + strcat(globcfg.cmd_path_start, " start > /dev/null 2>&1"); + + globcfg.cmd_path_stop = malloc(strlen(optarg) + 22); + strcpy(globcfg.cmd_path_stop, optarg); + strcat(globcfg.cmd_path_stop, " stop > /dev/null 2>&1"); + break; + + case 'f': + globcfg.pcap_filter = optarg; + break; + case 'm': + if (strcmp("tap", optarg)== 0) { + globcfg.dev_mode = IFF_TAP; + } + break; + case 'd': + globcfg.isdaemon = 1; + break; + case 'h': /* намеренный проход в следующий case-блок */ + case '?': + usage(); + break; + default: + exit(1); + break; + } + + opt = getopt( argc, argv, optString ); + } + + if (globcfg.dev_name == NULL) { + my_err("tun/tap device must be specified with proper type (-m by default tun)."); + usage(); + exit(1); + } + + if (globcfg.cmd_path == NULL) { + my_err("Executable path must be specified"); + usage(); + exit(1); + } + + if (globcfg.isdaemon == 1) { + globcfg.pid = fork(); + + if (globcfg.pid < 0) { + my_err("Can't fork process. Abort."); + exit(1); + } + + if (globcfg.pid > 0) { + my_info("---"); + my_info("Success! tuninetd has been started with pid: %i", globcfg.pid); + exit(0); + } + + chdir("/"); + + setsid(); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + pthread_t inc_x_thread; + pthread_t tun_x_thread; + + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create(&inc_x_thread, &attr, inc_x, &x); + pthread_create(&tun_x_thread, &attr, tun_x, &y); + + while (1) { + usleep(1000000); + curts = time(NULL); + + if (ts != 0 && status == 1 && ((curts - ts) >= globcfg.ttl) ) { + status = 0; + my_info("Executing STOP command... Binding again to interface %s", globcfg.dev_name); + + if (system(globcfg.cmd_path_stop) != 0) { + my_err("Warning! Executable command doesn't return 0 (%s)", globcfg.cmd_path_stop); + } + + pthread_create(&tun_x_thread, &attr, tun_x, &y); + } + } + return 0; +} diff --git a/pcap.c b/pcap.c new file mode 100755 index 0000000..7e44bef --- /dev/null +++ b/pcap.c @@ -0,0 +1,35 @@ +//Author: root4root@gmail.com + +void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) +{ + ts = header->ts.tv_sec; +} + + +void *inc_x(void *x_void_ptr) +{ + struct bpf_program filter; + + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_t *handle; + + handle = pcap_open_live(globcfg.dev_name, BUFSIZ, 0, 10, errbuf); + + if (handle == NULL) { + my_err("Pcap: unable to open interface. %s", errbuf); + exit(1); + } + + if (globcfg.pcap_filter != NULL) { + if (pcap_compile(handle, &filter, globcfg.pcap_filter, 0, PCAP_NETMASK_UNKNOWN) != 0) { + my_err("Wrong libpcap filter: \"%s\"", globcfg.pcap_filter); + exit(1); + } + + pcap_setfilter(handle, &filter); + } + + pcap_loop(handle, -1, got_packet, NULL); + + pcap_close(handle); +} diff --git a/runtunnel.sh b/runtunnel.sh new file mode 100755 index 0000000..288fe81 --- /dev/null +++ b/runtunnel.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +#This is just example of how executable script, which run from tuninetd, may looks like. +#Accepts 'start' or 'stop' parameter. +#Author: root4root@gmail.com + +controlFile='/var/run/ssh-myvpn-tunnel-control' +remoteHost='1.2.3.4' + +function up() +{ + if [ ! -S $controlFile ] + then + /usr/bin/ssh -S $controlFile -M -f -w 0:0 $remoteHost ifconfig tun0 10.10.10.1/30 pointopoint 10.10.10.2 + exit 0 + else + echo 'Tunnel already up!' + exit 1 + fi +} + +function down() +{ + if [ -S $controlFile ] + then + /usr/bin/ssh -S $controlFile -O exit $remoteHost + exit 0 + else + echo 'Tunnel already down!' + exit 1 + fi +} + +case $1 in +'start') + up + ;; +'stop' ) + down + ;; +*) + echo 'Usage: start|stop' + ;; +esac + +exit 1 diff --git a/tun.c b/tun.c new file mode 100755 index 0000000..7d7d051 --- /dev/null +++ b/tun.c @@ -0,0 +1,81 @@ +//Author: root4root@gmail.com + +int tun_alloc(char *dev, int flags) +{ + struct ifreq ifr; + int fd, err; + char *clonedev = "/dev/net/tun"; + + if ( (fd = open(clonedev, O_RDWR)) < 0 ) { + my_err("Unable to open clonable device %s", clonedev); + return fd; + } + + memset(&ifr, 0, sizeof(ifr)); + + ifr.ifr_flags = flags; + + if (*dev) { + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + } + + if ( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) { + close(fd); + return err; + } + + return fd; +} + +int cread(int fd, char *buf, int n) +{ + int nread; + + if ((nread = read(fd, buf, n)) < 0) { + my_err("Error, while reading data. Abort."); + exit(1); + } + + return nread; +} + +void *tun_x(void *x_void_ptr) +{ + int tap_fd; + int nread; + char buffer[BUFSIZE]; + + + if ( (tap_fd = tun_alloc(globcfg.dev_name, globcfg.dev_mode | IFF_NO_PI)) < 0 ) { + my_err("Error connecting to tun/tap interface %s. Abort.", globcfg.dev_name); + exit(1); + } + + while(1) { + nread = cread(tap_fd, buffer, BUFSIZE); + + if (globcfg.pcap_filter != NULL) { + usleep(15000); //Wait for libpcap time out. + if ( (curts - ts) < 2 ) { + do_debug("Read %d bytes from the tap interface\n", nread); + break; + } + } else { + break; + } + } + + ts = time(NULL); + + status = 1; + + close(tap_fd); + + my_info("Executing START command..."); + + if (system(globcfg.cmd_path_start) != 0) { + my_err("Warning! Executable command doesn't return 0 (%s)", globcfg.cmd_path_start); + } + + return 0; +} diff --git a/utils.c b/utils.c new file mode 100755 index 0000000..cdf85a7 --- /dev/null +++ b/utils.c @@ -0,0 +1,61 @@ +//Author: root4root@gmail.com + +void do_debug(char *msg, ...) +{ + if(debug) { + va_list argp; + va_start(argp, msg); + vfprintf(stderr, msg, argp); + va_end(argp); + } +} + +void my_err(char *msg, ...) +{ + va_list argp; + va_start(argp, msg); + + if (globcfg.isdaemon == 0) { + vfprintf(stderr, msg, argp); + vfprintf(stderr, "\n", NULL); + } else { + openlog("tuninetd", 0, LOG_USER); + vsyslog(LOG_ERR, msg, argp); + closelog(); + } + + va_end(argp); +} + +void my_info(char *msg, ...) +{ + va_list argp; + va_start(argp, msg); + + if (globcfg.isdaemon == 0) { + vfprintf(stderr, msg, argp); + vfprintf(stderr, "\n", NULL); + } else { + openlog("tuninetd", 0, LOG_USER); + vsyslog(LOG_INFO, msg, argp); + closelog(); + } + + va_end(argp); +} + +void usage(void) { + fprintf(stderr, "\nUsage:\n\n"); + fprintf(stderr, "%s -i -c [-m ] [-f ] [-t ] [-d]\n", progname); + fprintf(stderr, "\n\n"); + fprintf(stderr, "-i : interface to use (tun or tap). Must be up and configured.\n"); + fprintf(stderr, "-c : will be executed with 'start' and 'stop' parameter.\n"); + fprintf(stderr, "-m : 'tun' or 'tap' mode. By default 'tun', should be set properly. \n"); + fprintf(stderr, "-f : specify pcap filter, similar to tcpdump\n"); + fprintf(stderr, "-t : seconds of interface idle, before 'stop' command (default is 600).\n"); + fprintf(stderr, "-d: demonize process\n"); + fprintf(stderr, "-h: prints this help text\n\n"); + fprintf(stderr, "\nExample:\n\n"); + fprintf(stderr, "tuninetd -i tun0 -c /test/runtunnel.sh -f \"! host 1.2.3.4\" -t 3600 -d\n\n"); + exit(1); +}