HOEVENSTEIN

source code of aorta.c

Rob van der Hoeven
Sat Oct 28 2017

The sourcecode below can be downloaded from the downloads page.

/*
AORTA: Another Onion Router Transproxy Application.
===================================================

Version 1.1

Copyright (C) 2017 Rob van der Hoeven


Usage:
======

Aorta transparently routes all TCP and DNS traffic from a program under its
control through the Tor network. Usage is as follows:

    aorta [aorta parameters] [program] [program parameters]

possible (optional) aorta parameters are:

 -t   enable terminal output (for programs like wget, w3m etc.)
 -c   DO NOT CHECK if Tor handles all Internet traffic
 -a   DO NOT CHECK if the targeted program is already active

ONLY use a DO NOT CHECK option if you are *very sure* that the check is
indeed not needed.

examples:

    aorta firefox https://check.torproject.org
    aorta chromium expyuzz4wqqyqhjn.onion
    aorta -t w3m expyuzz4wqqyqhjn.onion
    aorta -t git clone http://dccbbv6cooddgcrq.onion/tor.git
    aorta bash


Requirements:
=============

Linux kernel >= 3.14          (check: uname -a)

iptables with cgroup support  (check: sudo iptables -m cgroup -h | grep cgroup)

local Tor configuration (/etc/tor/torrc) should have the following lines:

    VirtualAddrNetworkIPv4 10.192.0.0/10
    AutomapHostsOnResolve 1
    TransPort 9040
    DNSPort 9041

NOTE: if you change the Tor configuration the Tor daemon must be restarted.


Compilation:
============

gcc -Wall -o aorta aorta.c


Installation:
=============

execute the following commands as root:

    cp aorta /usr/local/bin/aorta
    chown root:root /usr/local/bin/aorta
    chmod u+s /usr/local/bin/aorta


Support:
========

https://hoevenstein.nl/aorta-a-transparent-tor-proxy-for-linux-programs


License:
========

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/utsname.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <termios.h>

#define TOR_TCP_PORT              "9040"
#define TOR_DNS_PORT              "9041"
#define TOR_ONION_NETWORK         "10.192.0.0/10"
// the .onion address below is for the torproject.org website
#define TOR_CONNECTION_TEST_HOST  "expyuzz4wqqyqhjn.onion"
// the .onion address below is for the facebook.com website
//#define TOR_CONNECTION_TEST_HOST  "facebookcorewwwi.onion"
#define AORTA_CGROUP_CLASSID      "0x19840001"

#define HAPPY(TEXT) "\x1b[32;1m" TEXT "\x1b[0m"
#define ANGRY(TEXT) "\x1b[31;1m" TEXT "\x1b[0m"
#define BOLD(TEXT)  "\x1b[37;1m" TEXT "\x1b[0m"

typedef int (*exec_function)(const char *path, char *const argv[]);

int check_tor_connection=1;
int check_if_program_is_active=1;
int enable_terminal_output=0;

const char iptables_path[]=
    "/sbin/iptables";

const char usage[]=
    "\n" BOLD("AORTA version 1.1")"\n\n"
    BOLD("usage   :")"  aorta [aorta parameters] [program] [program parameters]\n"
    BOLD("example :")"  aorta firefox https://check.torproject.org\n\n"
    "possible (optional) aorta parameters are:\n\n"
    " -t   " "enable terminal output (for programs like wget, w3m etc.)\n"
    " -c   " BOLD("DO NOT CHECK") " if Tor handles all Internet traffic\n"
    " -a   " BOLD("DO NOT CHECK") " if the targeted program is already active\n\n"
    BOLD("ONLY") " use a " BOLD("DO NOT CHECK") " option if you are " BOLD("*very sure*") " that the check is\n"
    "indeed not needed.\n\n";

const char program_is_active_warning[]=
    "\n" ANGRY("WARNING") "\n\n"
    "The program you want to start is already running. Some programs will clone\n"
    "a running program. If so, this cloned program will NOT USE THE Tor NETWORK.\n"
    "You can detect this behavior as follows:\n\n"
    " - AORTA exits after the program is started\n"
    " - The title bar of Firefox/Chrome does not show (on AORTA).\n"
    " - https://check.torproject.org reports: You are not using Tor.\n\n"
    "Do you want to continue (y/N)? ";

const char http_request[]=
    "HEAD / HTTP/1.1\r\n"
    "User-Agent: AORTA/1.1 (%.32s)\r\n"
    "Accept: */*\r\n"
    "Accept-Encoding: identity\r\n"
    "Host: " TOR_CONNECTION_TEST_HOST "\r\n"
    "Connection: Keep-Alive\r\n\r\n";

// iptables rules to forward traffic to the local Tor daemon
//
// NOTE: the iptables COMMAND should be the third parameter and must be 2 chars long

const char *aorta_rules[] =
{
    // create an aorta chain inside the nat table

    "-t nat -N aorta",

    // DNS queries for onion addresses are resolved to an address in the
    // TOR_ONION_NETWORK range. traffic in this network must always be
    // processed by the local Tor daemon

    "-t nat -A aorta -p tcp -m tcp -d " TOR_ONION_NETWORK " -j REDIRECT --to-ports " TOR_TCP_PORT,

    // do not touch non-routable addresses, except for DNS traffic

    "-t nat -A aorta -d 127.0.0.0/8    -p udp -m udp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 127.0.0.0/8    -p tcp -m tcp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 10.0.0.0/8     -p udp -m udp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 10.0.0.0/8     -p tcp -m tcp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 192.168.0.0/16 -p udp -m udp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 192.168.0.0/16 -p tcp -m tcp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 172.16.0.0/12  -p udp -m udp ! --dport 53 -j RETURN",
    "-t nat -A aorta -d 172.16.0.0/12  -p tcp -m tcp ! --dport 53 -j RETURN",

    // redirect to local Tor daemon

    "-t nat -A aorta -p tcp -m tcp -j REDIRECT --to-ports " TOR_TCP_PORT,
    "-t nat -A aorta -p udp -m udp --dport 53 -j REDIRECT --to-ports " TOR_DNS_PORT,

    // output traffic from processes inside our cgroup is processed by aorta chain

    "-t nat -A OUTPUT -m cgroup --cgroup " AORTA_CGROUP_CLASSID " -j aorta",
    0
};

char *argv_to_commandline(char *const argv[])
{
    static char commandline[1024];
    char **arg;
    int space;

    arg=(char**) argv;
    commandline[0]=0;
    space=sizeof(commandline)-3;

    while (*arg && ((space-=strlen(*arg)) > 0))
    {
        if (*commandline)
            strcat(commandline, " ");

        strcat(commandline, *arg++);
    }

    return commandline;
}

void execute(int enable_terminal_output, exec_function f, const char *path, char *const argv[])
{
    int fd_null, fd_stdout, fd_stderr, err;

    fd_stdout=dup(STDOUT_FILENO);
    fd_stderr=dup(STDERR_FILENO);
    fcntl(fd_stdout, F_SETFD, FD_CLOEXEC);
    fcntl(fd_stderr, F_SETFD, FD_CLOEXEC);

    // it would be silly to suppress the output of a shell....

    if (!enable_terminal_output && (strcmp(argv[0], "bash") != 0) && (strcmp(argv[0], "sh") != 0))
    {
        fd_null=open("/dev/null", O_APPEND);
        dup2(fd_null, STDOUT_FILENO);
        dup2(fd_null, STDERR_FILENO);
        close(fd_null);
    }

    // if something went wrong, restore output so the problem can be reported

    if (f(path, argv) == -1)
    {
        err=errno;
        dup2(fd_stdout, STDOUT_FILENO);
        dup2(fd_stderr, STDERR_FILENO);
        error(EXIT_FAILURE, err, ANGRY("FAILED") " to execute [%s]", argv_to_commandline(argv));
    }
}

int iptables_execute(const char *commandline)
{
    char *argv[50], *arg, buffer[256];
    int  argc, iptables_status;
    pid_t iptables_pid;
    char *check_command="-C";
    char *append_command="-A";

    memset(buffer, 0, sizeof(buffer));
    strncpy(buffer, commandline, sizeof(buffer)-1);

    argc=0;
    argv[argc++]= (char *) iptables_path;
    arg = strtok(buffer, " ");

    while (arg && argc < 49)
    {
        argv[argc++] = arg;
        arg = strtok(0, " ");
    }

    argv[argc] = 0;

    // before an append command, first check if the rule is already present

    if (strcmp(argv[3], append_command) == 0)
    {
        argv[3]=check_command;
        iptables_pid=fork();

        if (iptables_pid == -1)
            error(EXIT_FAILURE, errno, NULL);

        if (iptables_pid == 0)
            execute(0, execv, argv[0], argv);

        waitpid(iptables_pid, &iptables_status, 0);

        // exit code 0 means rule is already present.

        if (WEXITSTATUS(iptables_status) == 0)
            return 0;

        argv[3]=append_command;
    }

    // add rule

    iptables_pid=fork();

    if (iptables_pid == -1)
        error(EXIT_FAILURE, errno, NULL);

    if (iptables_pid == 0)
        execute(0, execv, argv[0], argv);

    waitpid(iptables_pid, &iptables_status,0);
    return WEXITSTATUS(iptables_status);
}

void iptables_add_rules(const char** rule)
{
    while (*rule)
        iptables_execute(*rule++);
}

void create_net_cls_cgroup(char *directory, char *classid)
{
    int fd;
    char path[256];

    sprintf(path, "/sys/fs/cgroup/net_cls/%s", directory);

    if ((mkdir(path, 0755) == -1) && (errno != EEXIST))
       error(EXIT_FAILURE, errno, ANGRY("FAILED") " to create cgroup [%s].", path);

    if ((fd=open(strcat(path, "/net_cls.classid"), O_WRONLY | O_APPEND | O_CLOEXEC)) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to open [%s].", path);

    if (write(fd, classid, strlen(classid)) == -1)
       error(EXIT_FAILURE, errno, ANGRY("FAILED") " to write classid [%s].", classid);

    close(fd);
}

void join_net_cls_cgroup(char *directory)
{
    int fd;
    char buffer[256];

    sprintf(buffer, "/sys/fs/cgroup/net_cls/%s/cgroup.procs", directory);

    if ((fd=open(buffer, O_WRONLY | O_APPEND | O_CLOEXEC)) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to open [%s].", buffer);

    if (write(fd, buffer, sprintf(buffer,"%d",getpid())) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to add PID to cgroup.procs.");

    close(fd);
}

void new_hostname(char *hostname)
{
    // A new hostname can be handy because:
    //
    // - When a shell is started by aorta, the hostname will be part of its prompt
    // - X11 programs like Firefox and Chromium show it on their title bars.
    //
    // So, a new hostname can give a visual indication that a program is using
    // the Tor network.
    //
    // BUT: new_hostname() makes the X-server think it is accessed from another
    // system. *Some* Linux distributions (ArchLinux) use a strict X-server access
    // configuration which prevent programs running on another system from
    // accessing the X11 screen. In this case programs will fail to run.
    //
    // There are 2 solutions for this problem:
    //
    // 1) Comment-out or remove the new_hostname function call
    // 2) Make the X-server configuration less restrictive by running the command:
    //
    //    xhost +local:
    //
    //    This command won't survive a restart. For this you have to add it
    //    to .xinitrc, just before the gui is started.

    if (unshare(CLONE_NEWUTS) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to unshare uts namespace.");

    if (sethostname(hostname, strlen(hostname)) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to change hostname to [%s].", hostname);
}

void test_tor_connection(char *program_name)
{
    struct addrinfo *address_info, hints;
    struct sockaddr_in *ip_address;
    char ip_address_str[INET_ADDRSTRLEN];
    char http_buffer[2048];
    int r,fd, count, pos, length;

    if (!check_tor_connection)
    {
        printf("\n" ANGRY("WARNING") " NOT testing if Tor handles all Internet traffic.\n");
        return;
    }

    printf("\n" HAPPY("TESTING") " if Tor handles all Internet traffic\n\n");
    printf("...Resolving        - " HAPPY("%s") "\n", TOR_CONNECTION_TEST_HOST);
    printf("...IP address       - ");

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_NUMERICSERV;

    if ((r=getaddrinfo(TOR_CONNECTION_TEST_HOST , "80", &hints, &address_info)))
    {
        printf(ANGRY("FAILED") " Tor connection test, result [%s]\n", gai_strerror(r));
        exit(EXIT_FAILURE);
    }

    if (address_info->ai_next)
    {
        printf(ANGRY("FAILED") " to resolve onion address error [%s]\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    ip_address = (struct sockaddr_in *) address_info->ai_addr;
    inet_ntop(AF_INET, &(ip_address->sin_addr), ip_address_str, INET_ADDRSTRLEN);

    // check if the address is non-routable and inside the TOR_ONION_NETWORK
    // range. Only check the first 3 positions.

    if (strncmp(ip_address_str, TOR_ONION_NETWORK, 3))
    {
        printf(ANGRY("FAILED") " address [%s] ouside expected range [%s]\n", ip_address_str, TOR_ONION_NETWORK);
        exit(EXIT_FAILURE);
    }

    printf(HAPPY("%s") "\n", ip_address_str);

    if ((fd = socket(address_info->ai_family, address_info->ai_socktype, address_info->ai_protocol)) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to get socket");

    printf("...Connecting       - ");
    fflush(stdout);

    if (connect(fd, address_info->ai_addr, address_info->ai_addrlen) == -1)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to connect");

    printf(HAPPY("Done!") "\n");
    printf("...Sending request  - ");
    fflush(stdout);

    // request a test page

    snprintf(http_buffer, sizeof(http_buffer)-1, http_request, program_name);
    http_buffer[sizeof(http_buffer)-1]=0;

    pos=0;
    length=strlen(http_buffer);

    while (pos < length)
    {
        if ((count=write(fd, http_buffer+pos, length - pos)) == -1)
        {
            if (errno == EINTR)
                continue;

            error(EXIT_FAILURE, errno, ANGRY("FAILED") " to send request");
        }

        pos+=count;
    }

    printf(HAPPY("Done!") "\n");
    printf("...Getting response - ");
    fflush(stdout);

    // read the response

    pos=0;
    do
    {
        if ((count=read(fd, http_buffer+pos, sizeof(http_buffer)-1-pos)) == -1)
        {
            if (errno == EINTR)
                continue;

            error(EXIT_FAILURE, errno, ANGRY("FAILED") " to read response");
        }

        pos+=count;
        http_buffer[pos]=0;

        // check for end of HTTP header

        if (strstr(http_buffer,"\r\n\r\n"))
        {
            printf(HAPPY("Done!") "\n\n");
            break;
        }
    }
    while (count);

    close(fd);
    printf(HAPPY("PASSED") " Tor connection test\n");
}

int test_if_program_is_active(char *program_name)
{
    DIR *directory;
    struct dirent* dir_info;
    char file_name[256], cmdline[256], *pos, *cmdline_name;
    int fd,count, active;

    if (!check_if_program_is_active)
    {
        printf("\n" ANGRY("WARNING") " NOT testing if [%s] is currently active.\n", program_name);
        return 0;
    }

    // a shell does not clone itself and need not be checked

    if ((strcmp(program_name, "bash") == 0) || (strcmp(program_name, "sh") == 0))
        return 0;

    active=0;

    if ((directory=opendir("/proc")) == 0)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to open /proc directory");

    errno=0;

    while ((dir_info = readdir(directory)))
    {
        // find numeric (PID) directories containing program info

        if (dir_info->d_type != DT_DIR)
            continue;

        for (pos=dir_info->d_name; *pos && *pos >='0' && *pos <='9'; pos++){};

        if (*pos)
            continue;

        // read and test program name, read /proc/<PID>/cmdline instead of
        // /proc/<PID>/comm because comm truncates long program names

        sprintf(file_name, "/proc/%s/cmdline", dir_info->d_name);

        if ((fd=open(file_name, O_RDONLY | O_CLOEXEC)) == -1)
            error(EXIT_FAILURE, errno, ANGRY("FAILED") " to open file [%s]", file_name);

        if ((count=read(fd, cmdline, sizeof(cmdline)-1)) == -1)
        {
            if (errno == EINTR)
            {
                errno=0;
                continue;
            }

            error(EXIT_FAILURE, errno, ANGRY("FAILED") " to read file [%s]", file_name);
        }

        close(fd);
        cmdline[count]=0;

        if ((pos=strchr(cmdline,' ')))
            *pos=0;

        if ((cmdline_name=strrchr(cmdline, '/')) == 0)
            cmdline_name=cmdline;
        else
            cmdline_name++;

        if (strstr(cmdline_name, program_name))
            active++;
    }

    if (errno)
        error(EXIT_FAILURE, errno, ANGRY("FAILED") " to read directory entry");

    closedir(directory);
    return active;
}

char read_char()
{
    int c;
    struct termios old_attributes, new_attributes;

    tcgetattr(STDIN_FILENO, &old_attributes);
    new_attributes = old_attributes;
    new_attributes.c_lflag &= ~ICANON;
    tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes);
    c=getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &old_attributes);
    return (char) c;
}

void run_child_program(int argc, char* argv[])
{
    char c;

    test_tor_connection(argv[0]);

    if (test_if_program_is_active(argv[0]))
    {
        printf(program_is_active_warning);
        c=read_char();
        printf("\n");

        if (c != 'Y' && c != 'y')
        {
            printf(ANGRY("NOT RUNNING") BOLD(" %s") "\n", argv_to_commandline(argv));
            exit(EXIT_FAILURE);
        }
    }

    printf( "\n" HAPPY("RUNNING") BOLD(" %s") "\n", argv_to_commandline(argv));
    execute(enable_terminal_output, execvp,argv[0], argv);
}

int main(int argc, char *argv[])
{
    int argc_aorta=1;
    char **arg=argv;
    pid_t child_pid;
    int   child_status;

    if (argc == 1)
    {
        printf(usage);
        return 0;
    }

    create_net_cls_cgroup("aorta", AORTA_CGROUP_CLASSID);
    join_net_cls_cgroup("aorta");
    iptables_add_rules(aorta_rules);

    child_pid=fork();

    if (child_pid == -1)
        error(EXIT_FAILURE, errno, NULL);

    if (child_pid == 0)
    {
        new_hostname("AORTA");

        if (setuid(getuid()) == -1)
            error(EXIT_FAILURE, errno, /*VERY*/ANGRY("FAILED") " to drop privileges");

        while (*++arg)
        {
            if (**arg != '-')
                break;

            argc_aorta++;

            if (strcmp(*arg, "-h") == 0 || strcmp(*arg, "--help") == 0)
            {
                printf(usage);
                return 0;
            }

            if (strcmp(*arg, "-c") == 0)
            {
                check_tor_connection=0;
                continue;
            }

            if (strcmp(*arg, "-a") == 0)
            {
                check_if_program_is_active=0;
                continue;
            }

            if (strcmp(*arg, "-t") == 0)
            {
                enable_terminal_output=1;
                continue;
            }

            error(EXIT_FAILURE, errno, ANGRY("ERROR") " unknown command line option [%s]", *arg);
        }

        run_child_program(argc-argc_aorta, argv+argc_aorta);
    }

    waitpid(child_pid, &child_status,0);
    printf("\n" HAPPY("AORTA CLOSED ...") "\n");
    return WEXITSTATUS(child_status);
}

Comments: 0

source code of avne.c

Rob van der Hoeven
Wed Oct 14 2015

The sourcecode below can be downloaded from the downloads page.

/*

Another Virtual Network Environment 

Version 0.5

Copyright (C) 2015 Rob van der Hoeven

This is an alpha release, intended for developers only.
-------------------------------------------------------

Support at   : https://hoevenstein.nl
Compilation  : gcc -Wall -o avne avne.c
Installation : chown root avne && chmod u+s avne 

License.
--------

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#define _GNU_SOURCE
#include <ctype.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <poll.h>
#include <netdb.h>
#include <resolv.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>

//
// NOTE: in Debian Wheezy the setns function does not have a prototype in <sched.h>
//       in Debian Jessie the function below is not needed and must be removed
//

static inline int setns(int fd, int nstype)
{
#ifdef __NR_setns
    return syscall(__NR_setns, fd, nstype);
#elif defined(__NR_set_ns)
    return syscall(__NR_set_ns, fd, nstype);
#else
    errno = ENOSYS;
    return -1;
#endif
}

//
// constants
//

#define MTU 1500
#define TCP_BUFFER_SIZE 4096
#define DNS_BUFFER_SIZE 1024

#define TCP_FIN 0x01
#define TCP_SYN 0x02
#define TCP_RST 0x04
#define TCP_PSH 0x08
#define TCP_ACK 0x10
#define TCP_URG 0x20

#define UPSTREAM_CLOSED          0
#define UPSTREAM_CONNECTING      1
#define UPSTREAM_CONNECTED       2

#define SOCKS5_SEND_METHODS      10
#define SOCKS5_READ_METHODS      11
#define SOCKS5_SEND_CONNECT      12
#define SOCKS5_READ_CONNECT      13

#define SOCKS5_TOR_SEND_RESOLVE  20
#define SOCKS5_TOR_READ_RESOLVE  21
#define SOCKS5_TOR_DONE_RESOLVE  22

// a TCP or DNS upstream connection uses one file descriptor. max number of 
// fd's is 1024 and also FD_SETSIZE is 1024. so *never* have the sum of
// TCP_CONNECTIONS_MAX and DNS_CONNECTIONS_MAX exceed 1000 

#define TCP_CONNECTIONS_MAX 500
#define DNS_CONNECTIONS_MAX 500

#define DNS_REQUEST_NORMAL 0
#define DNS_REQUEST_PROBE 1
#define DNS_REQUEST_LOCAL 2

#define LOG_INFO 0
#define LOG_ERROR 1

//
// prototypes
//

typedef struct tcp_connection_s      tcp_connection_t;
typedef struct dns_cache_entry_s     dns_cache_entry_t;
typedef struct dns_connection_s      dns_connection_t;
typedef struct upstream_connection_s upstream_connection_t;
typedef struct upstream_functions_s  upstream_functions_t;
typedef struct buffer_s              buffer_t;

u_int16_t tcp_connection_send_datagram(tcp_connection_t *tcp_connection, u_int8_t flags, u_int8_t *data, u_int32_t data_size);
void tcp_connection_write(tcp_connection_t *tcp_connection);
void tcp_connection_server_close(tcp_connection_t *tcp_connection);

void dns_connection_send_datagram(dns_connection_t *dns_connection);
void dns_connection_done(dns_connection_t *dns_connection);

char *dns_cache_entry_get_name(dns_cache_entry_t *dns_cache_entry);
char *dns_cache_get_name(u_int32_t address);

//
// types
//

struct buffer_s
{
    u_int16_t size;
    u_int16_t count;
    u_int8_t  *data;
};

struct upstream_functions_s
{
    int (* const connect)(upstream_connection_t *upstream_connection);
    int (* const disconnect)(upstream_connection_t *upstream_connection);
    void (* const read)(upstream_connection_t *upstream_connection);
    void (* const write)(upstream_connection_t *upstream_connection);
    void (* const prepare_events)(upstream_connection_t *upstream_connection);
    void (* const handle_events)(upstream_connection_t *upstream_connection);
};

struct upstream_connection_s
{
    int id;
    char *idstr;
    int fd;
    u_int16_t state;
    u_int16_t resultcode; 
    u_int32_t server_address;
    u_int16_t server_port;
    char*     server_name;    

    buffer_t *input;          // input buffer == output buffer of tcp/dns connection
    buffer_t *output;         // output buffer == input buffer of tcp/dns connection
    upstream_functions_t *functions;
};

struct tcp_connection_s 
{
    int id;
    char *idstr;
    tcp_connection_t *next;
    upstream_connection_t upstream;

    u_int32_t server_address; // stored in nbo
    u_int16_t server_port;    // stored in nbo
    u_int32_t client_address; // stored in nbo
    u_int16_t client_port;    // stored in nbo

    u_int32_t server_seq;     // current server sequence
    u_int32_t server_seq_ack; // last acked sequence from client
    u_int16_t server_window;
    u_int16_t server_mss;

    u_int32_t client_seq;     // next expected client sequence
    u_int32_t client_seq_ack; // last ack send to client
    u_int16_t client_window;
    u_int16_t client_mss;
    u_int8_t  client_wscale;  // not used

    u_int16_t tcp_state;

    buffer_t input;           // contains data from client
    buffer_t output;          // data that must be written to client
};

struct dns_cache_entry_s
{
    dns_cache_entry_t *next;

    time_t    expires;
    char      *name;
    u_int32_t address;

    u_int16_t request_size;
    u_int8_t  *request;
    u_int16_t response_size;
    u_int8_t  *response;

    u_int8_t  request_type;
    u_int16_t response_clients; // number of DNS connections waiting for the response
};

struct dns_connection_s
{
    int id;
    char *idstr;
    dns_connection_t *next;
    upstream_connection_t upstream;

    u_int32_t server_address; // stored in nbo
    u_int16_t server_port;    // stored in nbo
    u_int32_t client_address; // stored in nbo
    u_int16_t client_port;    // stored in nbo

    u_int16_t transaction_id; // stored in nbo

    buffer_t input;           // from client
    buffer_t output;          // to client

    // DNS requests and responses are cached for some time. for new connections
    // the cache is searched for an entry with the same request. if this entry
    // has a response then this response is send to the client immediately and
    // no upstream connection is needed

    dns_cache_entry_t *cache_entry;
};

//
// globals
//

int fd_tun=-1;
int fd_log=-1;
int fd_max=0;
int pid_child=0;
int child_running=1;

char log_filename[]="/var/log/avne/log.txt";
char http_filename[]="/var/log/avne/status.html";

char *socks_server_address=0;
u_int16_t socks_server_port=9050;

fd_set fdset_read;
fd_set fdset_write;

u_int8_t datagram[MTU];
struct iphdr *ip_header = (struct iphdr *) datagram;

upstream_functions_t *default_tcp_upstream_functions=0;
upstream_functions_t *default_dns_upstream_functions=0;

int tcp_connection_total_count=0;
int dns_connection_total_count=0;
//int dns_cache_total_count=0;

int dns_connection_fail_count=0;
int dns_connection_reject_count=0;
int dns_connection_cache_hit_count=0;
int dns_connection_use_tor_resolve=0;

int dns_cache_expired_count=0;

// linked lists of "active" items

int tcp_connection_count = 0;
tcp_connection_t *tcp_connection_root=0;

int dns_cache_count=0;
dns_cache_entry_t *dns_cache_entry_root=0;

int dns_connection_count=0;
dns_connection_t *dns_connection_root=0;

//
// Logging
//

char *get_log_time(void)
{
    static char log_time[128];
    struct timeval t;
    struct tm *tm;
    short  p;

    gettimeofday(&t, 0);
    tm = localtime(&t.tv_sec);

    if (tm)
    {
        if ((p=strftime(log_time, sizeof(log_time), "%F %T", tm)))
        {
            sprintf(log_time+p, ".%06ld",t.tv_usec);
            return log_time;
        }
    }

    return strcpy(log_time,"get_log_time failed");
}

void ts_log(int exitcode, int what, const char *where, const char *format, ...)
{
    char buffer[512];

    va_list arguments;

    if (fd_log != -1)
    {
        if (where)
            write(fd_log, buffer, sprintf(buffer,"[%s] %s [%s] ", get_log_time(), (what == LOG_ERROR) ? "Error" : "Info ", where));
        else
            write(fd_log, buffer, sprintf(buffer,"[%s] %s     ", get_log_time(), (what == LOG_ERROR) ? "Error" : "Info "));
    }

    va_start(arguments, format);
    vsnprintf(buffer, sizeof(buffer)-1, format, arguments);
    va_end(arguments);

    buffer[sizeof(buffer)-1]=0;

    if (fd_log != -1)
        dprintf(fd_log, buffer);

    // always report errors to stderr

    if (what == LOG_ERROR)
        dprintf(STDERR_FILENO, "avne: %s", buffer);

    if (exitcode)
        exit(exitcode);
}

void ts_log_hex(void* address, int count)
{
    char hex_buffer[16*3+1];
    char ascii_buffer[16+1];
    char *hex_pos=hex_buffer;
    char *hex_pos_max=hex_buffer+15*3;
    char *ascii_pos=ascii_buffer;
    char *c = (char *) address;

    while (count--)
    {
        sprintf(hex_pos,"%.2x ",(unsigned char) *c);

        if (isprint(*c))
            sprintf(ascii_pos,"%c",*c);
        else
            sprintf(ascii_pos,".");

        c++; // a very nice language....

        if (hex_pos < hex_pos_max)
        {
            hex_pos+=3;
            ascii_pos++;
            continue;
        }

        ts_log(0, LOG_INFO, 0, "%-50s%s\n", hex_buffer, ascii_buffer);
        hex_pos=hex_buffer;
        ascii_pos=ascii_buffer;
    }

    if (hex_pos != hex_buffer)
        ts_log(0, LOG_INFO, 0, "%-50s%s\n", hex_buffer, ascii_buffer);
}

void ts_log_open(const char *filename, int append)
{
    int flags=O_WRONLY | O_CREAT | O_CLOEXEC;

    umask(0);

    if (append)
        flags |= O_APPEND;
    else
        flags |= O_TRUNC;

    if ((fd_log=open(filename, flags, S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH)) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to open logfile [%s] - error [%s]\n", filename, strerror(errno));
}

void ip_string(u_int32_t address, char *buffer, int buffer_size)
{
    struct in_addr ip_address;

    ip_address.s_addr = address;
    inet_ntop(AF_INET,&ip_address, buffer, buffer_size);
}

//
// buffer
//

u_int8_t *buffer_init(buffer_t *buffer, u_int16_t size)
{
    buffer->size=size;
    buffer->count=0;
    return buffer->data=malloc(size);
}

void buffer_done(buffer_t *buffer)
{
    free(buffer->data);
    buffer->size=0;
    buffer->count=0;
}

u_int16_t buffer_space(buffer_t *buffer)
{
    return buffer->size-buffer->count;
}

u_int8_t *buffer_tail(buffer_t *buffer)
{
    return buffer->data+buffer->count;
}

int buffer_add(buffer_t *buffer, u_int8_t *data, u_int16_t data_size)
{
    if (data && data_size)
    {
        if (data_size > buffer_space(buffer))
        {
            ts_log(0, LOG_ERROR, __FUNCTION__, "data_size [%u] > buffer_free [%u]\n", data_size, buffer_space(buffer));
            return 0;
        }

        memcpy(buffer->data+buffer->count, data, data_size);
        buffer->count+=data_size;
    }

    return 1;
}

int buffer_remove(buffer_t *buffer, u_int16_t data_size)
{
    if (data_size)
    {
        if (data_size > buffer->count)
        {
            ts_log(0, LOG_ERROR, __FUNCTION__, "data_size [%u] > count [%u]\n", data_size, buffer->count);
            return 0;
        }

        memmove(buffer->data, buffer->data+data_size, buffer->count-data_size);
        buffer->count-=data_size;
    }

    return 1;
}

//
// checksums
//

u_int16_t ip_checksum(void *buffer, int count) // From RFC 1071
{
    u_int32_t sum = 0;
    u_int16_t *addr=(u_int16_t *) buffer;

    while (count > 1)
    {
        sum+=*addr++;
        count-=2;
    }

    if(count > 0)
        sum+=*(u_int8_t *) addr;

    while (sum >>  16)
        sum=(sum & 0xffff)+(sum >> 16);

    return (u_int16_t) ~sum;
}

u_int16_t udp_checksum(struct iphdr *ip_header, struct udphdr *udp_header)
{
    typedef struct
    {
        u_int32_t saddr;
        u_int32_t daddr;
        u_int8_t  reserved;
        u_int8_t  protocol;
        u_int16_t udp_length;
    } udp_pseudo_header_t;

    int count;
    u_int32_t sum;
    u_int16_t *addr, udp_header_check;
    udp_pseudo_header_t pseudo_header;

    pseudo_header.saddr=ip_header->saddr;
    pseudo_header.daddr=ip_header->daddr;
    pseudo_header.reserved=0;
    pseudo_header.protocol=ip_header->protocol;
    pseudo_header.udp_length=htons(ntohs(ip_header->tot_len)-(ip_header->ihl << 2));

    sum = 0;
    addr=(u_int16_t *) &pseudo_header;
    count = sizeof(pseudo_header);

    while (count > 1)
    {
        sum+=*addr++;
        count-=2;
    }

    addr=(u_int16_t *) udp_header;
    count=ntohs(ip_header->tot_len)-(ip_header->ihl << 2);
    udp_header_check=udp_header->check;
    udp_header->check=0;

    while (count > 1)
    {
        sum+=*addr++;
        count-=2;
    }

    if(count > 0)
        sum+=*(u_int8_t *) addr;

    while (sum >>  16)
        sum=(sum & 0xffff)+(sum >> 16);

    udp_header->check=udp_header_check;

    return (u_int16_t) ~sum;
}

u_int16_t tcp_checksum(struct iphdr *ip_header, struct tcphdr *tcp_header)
{
    typedef struct
    {
        u_int32_t saddr;
        u_int32_t daddr;
        u_int8_t  reserved;
        u_int8_t  protocol;
        u_int16_t segment_size;
    } tcp_pseudo_header_t;

    int count;
    u_int32_t sum;
    u_int16_t *addr, tcp_header_check;
    tcp_pseudo_header_t pseudo_header;

    pseudo_header.saddr=ip_header->saddr;
    pseudo_header.daddr=ip_header->daddr;
    pseudo_header.reserved=0;
    pseudo_header.protocol=ip_header->protocol;
    pseudo_header.segment_size=htons(ntohs(ip_header->tot_len)-(ip_header->ihl << 2));

    sum = 0;
    addr=(u_int16_t *) &pseudo_header;
    count = sizeof(pseudo_header);

    while (count > 1)
    {
        sum+=*addr++;
        count-=2;
    }

    addr=(u_int16_t *) tcp_header;
    count=ntohs(ip_header->tot_len)-(ip_header->ihl << 2);
    tcp_header_check=tcp_header->check;
    tcp_header->check=0;

    while (count > 1)
    {
        sum+=*addr++;
        count-=2;
    }

    if(count > 0)
        sum+=*(u_int8_t *) addr;

    while (sum >>  16)
        sum=(sum & 0xffff)+(sum >> 16);

    tcp_header->check=tcp_header_check;

    return (u_int16_t) ~sum;
}

//
// TCP upstream functions
//

int tcp_upstream_connect_start(upstream_connection_t *upstream_connection)
{
    struct sockaddr_in upstream_address;

    //ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", upstream_connection->idstr);

    if ((upstream_connection->fd=socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1)
    {
        ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to create socket - error [%s]\n", upstream_connection->idstr, strerror(errno));
        return 0;
    }

    upstream_address.sin_family=AF_INET;
    upstream_address.sin_addr.s_addr=upstream_connection->server_address;
    upstream_address.sin_port=upstream_connection->server_port;

    while (1)
    {
        if (connect(upstream_connection->fd, (struct sockaddr *) &upstream_address, sizeof(upstream_address)) != -1)
        {
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] fd [%d] upstream connected.\n", upstream_connection->idstr, upstream_connection->fd);
            upstream_connection->state=UPSTREAM_CONNECTED;
            return 1;
        }

        switch (errno)
        {
            case EINTR:
                continue;

            case EINPROGRESS:
                ts_log(0, LOG_INFO, __FUNCTION__, "[%s] fd [%d] upstream connecting\n", upstream_connection->idstr, upstream_connection->fd);
                upstream_connection->state=UPSTREAM_CONNECTING;
                return 1;

            default:
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] fd [%d] failed to start nonblocking connection\n", upstream_connection->idstr, upstream_connection->fd);
                close(upstream_connection->fd);
                upstream_connection->fd=-1;
                return 0;
        }
    }

    return 0;
}

int tcp_upstream_connect_end(upstream_connection_t *upstream_connection)
{
    int socket_error=0;
    socklen_t socket_error_size=sizeof(socket_error);

    //ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", upstream_connection->idstr);

    if (getsockopt(upstream_connection->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_size) == -1)
        ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to read socket_error - error [%s]\n", upstream_connection->idstr, strerror(errno));
    else
        if (socket_error == 0)
        {
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] upstream connected.\n", upstream_connection->idstr);
            upstream_connection->state=UPSTREAM_CONNECTED;
            return 1;
        }
        else
            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] upstream failed to connect - error [%s]\n", upstream_connection->idstr, strerror(socket_error));

    upstream_connection->state=UPSTREAM_CLOSED;
    close(upstream_connection->fd);
    upstream_connection->fd=-1;
    return 0;
}

int tcp_upstream_disconnect(upstream_connection_t *upstream_connection)
{
    ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", upstream_connection->idstr);

    upstream_connection->state=UPSTREAM_CLOSED;
    close(upstream_connection->fd);
    upstream_connection->fd=-1;
    return 1;
}

void tcp_upstream_read(upstream_connection_t *upstream_connection)
{
    int data_count;

    if (upstream_connection->state != UPSTREAM_CONNECTED)
        return;

    if (!buffer_space(upstream_connection->input))
        return;

    while (1)
    {
        data_count=read(upstream_connection->fd, buffer_tail(upstream_connection->input), buffer_space(upstream_connection->input));

        if (data_count == -1)
        {
            if (errno == EINTR)
                continue;

            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to read fd_upstream - error [%s]\n", upstream_connection->idstr, strerror(errno));
            return;
        }

        if (data_count == 0)
        {
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] upstream socket closed\n", upstream_connection->idstr);
            close(upstream_connection->fd);
            upstream_connection->fd=-1;
            upstream_connection->state=UPSTREAM_CLOSED;
        }

        break;
    }

    upstream_connection->input->count+=data_count;
}

void tcp_upstream_write(upstream_connection_t *upstream_connection)
{
    int data_count;

    if (upstream_connection->state != UPSTREAM_CONNECTED)
        return;

    while (upstream_connection->output->count)
    {
        data_count=write(upstream_connection->fd, upstream_connection->output->data, upstream_connection->output->count);

        if (data_count == -1)
        {
            if (errno == EINTR)
                continue;

            if (errno == EAGAIN || errno == EWOULDBLOCK)
            { 
                ts_log(0, LOG_ERROR, __FUNCTION__, "unexpected error [%s]\n", strerror(errno));
                return;
            }

            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to write to upstream socket - error [%s]\n", upstream_connection->idstr, strerror(errno));
            return;
        }

        if (data_count == 0)
        {
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] failed to write to upstream socket\n", upstream_connection->idstr);
            return;
        }

        buffer_remove(upstream_connection->output, data_count);
    }
}

void tcp_upstream_prepare_events(upstream_connection_t *upstream_connection)
{
    if (!upstream_connection || upstream_connection->fd == -1)
        return;

    switch (upstream_connection->state)
    {
        case UPSTREAM_CLOSED:
            return;

        case UPSTREAM_CONNECTING:
            FD_SET(upstream_connection->fd, &fdset_write);
            break;

        case UPSTREAM_CONNECTED:
            if (!buffer_space(upstream_connection->input) && !upstream_connection->output->count)
                return;

            if (buffer_space(upstream_connection->input))
                FD_SET(upstream_connection->fd, &fdset_read);
            if (upstream_connection->output->count)
                FD_SET(upstream_connection->fd, &fdset_write);

            break;
    }    

    // fd_upstream included in read or write set

    if (upstream_connection->fd > fd_max)
        fd_max=upstream_connection->fd;
}

void tcp_upstream_handle_events(upstream_connection_t *upstream_connection)
{
    if (!upstream_connection || upstream_connection->fd == -1)
        return;

    switch (upstream_connection->state)
    {
        case UPSTREAM_CLOSED:
            return;

        case UPSTREAM_CONNECTING:
            if (FD_ISSET(upstream_connection->fd, &fdset_write))
                tcp_upstream_connect_end(upstream_connection);

            break;

        case UPSTREAM_CONNECTED:
            if (FD_ISSET(upstream_connection->fd, &fdset_read))
                upstream_connection->functions->read(upstream_connection);

            // the read above may have closed the connection, so check if fd still ok

            if (upstream_connection->fd != -1 && FD_ISSET(upstream_connection->fd, &fdset_write))
                upstream_connection->functions->write(upstream_connection);

            break;
    }    
}

upstream_functions_t tcp_upstream_functions=
{
    .connect        = &tcp_upstream_connect_start,
    .disconnect     = &tcp_upstream_disconnect,
    .read           = &tcp_upstream_read,
    .write          = &tcp_upstream_write,
    .prepare_events = &tcp_upstream_prepare_events,
    .handle_events  = &tcp_upstream_handle_events
};

//
// SOCKS5 upstream functions
//

int socks5_upstream_connect_start(upstream_connection_t *upstream_connection)
{
    struct sockaddr_in upstream_address;
    struct in_addr socks5_address;

    ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", upstream_connection->idstr);

    if ((upstream_connection->fd=socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1)
    {
        ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to create socket - error [%s]\n", upstream_connection->idstr, strerror(errno));
        return 0;
    }

    inet_aton(socks_server_address, &socks5_address);
    upstream_address.sin_family=AF_INET;
    upstream_address.sin_addr.s_addr=socks5_address.s_addr;
    upstream_address.sin_port=htons(socks_server_port);

    while (1)
    {
        if (connect(upstream_connection->fd, (struct sockaddr *) &upstream_address, sizeof(upstream_address)) != -1)
        {
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] upstream connected.\n", upstream_connection->idstr);
            upstream_connection->state=SOCKS5_SEND_METHODS;
            return 1;
        }

        switch (errno)
        {
            case EINTR:
                continue;

            case EINPROGRESS:
                ts_log(0, LOG_INFO, __FUNCTION__, "[%s] upstream connecting\n", upstream_connection->idstr);
                upstream_connection->state=UPSTREAM_CONNECTING;
                return 1;

            default:
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to start nonblocking connection\n", upstream_connection->idstr);
                close(upstream_connection->fd);
                upstream_connection->fd=-1;
                return 0;
        }
    }

    return 0;
}

void socks5_upstream_prepare_events(upstream_connection_t *upstream_connection)
{
    if (!upstream_connection || upstream_connection->fd == -1)
        return;

    switch (upstream_connection->state)
    {
        case UPSTREAM_CLOSED:
        case SOCKS5_TOR_DONE_RESOLVE:
            return;

        case UPSTREAM_CONNECTING:
        case SOCKS5_SEND_METHODS:
        case SOCKS5_SEND_CONNECT:
        case SOCKS5_TOR_SEND_RESOLVE:
            FD_SET(upstream_connection->fd, &fdset_write);
            break;

        case SOCKS5_READ_METHODS:
        case SOCKS5_READ_CONNECT:
        case SOCKS5_TOR_READ_RESOLVE:
            FD_SET(upstream_connection->fd, &fdset_read);
            break;

        case UPSTREAM_CONNECTED:
            if (!buffer_space(upstream_connection->input) && !upstream_connection->output->count)
                return;

            if (buffer_space(upstream_connection->input))
                FD_SET(upstream_connection->fd, &fdset_read);
            if (upstream_connection->output->count)
                FD_SET(upstream_connection->fd, &fdset_write);

            break;
    }    

    // fd_upstream included in read or write set

    if (upstream_connection->fd > fd_max)
        fd_max=upstream_connection->fd;
}

void socks5_upstream_handle_events(upstream_connection_t *upstream_connection)
{
    static u_int8_t buffer[300];
    u_int8_t length;
    u_int8_t client_methods[]={0x05,0x01,0x00};
    int count;

    if (!upstream_connection || upstream_connection->fd == -1)
        return;

    switch (upstream_connection->state)
    {
        case UPSTREAM_CLOSED:
        case SOCKS5_TOR_DONE_RESOLVE:
            return;

        case UPSTREAM_CONNECTING:
            if (FD_ISSET(upstream_connection->fd, &fdset_write))
                tcp_upstream_connect_end(upstream_connection);

            if (upstream_connection->state == UPSTREAM_CONNECTED)
                upstream_connection->state=SOCKS5_SEND_METHODS;

            break;

        case SOCKS5_SEND_METHODS:
            if (!FD_ISSET(upstream_connection->fd, &fdset_write))
                break;

            if ((count=write(upstream_connection->fd, client_methods, sizeof(client_methods))) == -1)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to send client methods - error [%s]\n", upstream_connection->idstr, strerror(errno));
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            upstream_connection->state=SOCKS5_READ_METHODS;
            break;

        case SOCKS5_READ_METHODS:
            if (!FD_ISSET(upstream_connection->fd, &fdset_read))
                break;

            if ((count=read(upstream_connection->fd, buffer, sizeof(buffer))) == -1)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to read server method - error [%s]\n", upstream_connection->idstr, strerror(errno));
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            if (buffer[0] != 0x05 || buffer[1] != 0x00)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] unexpected socks reply for methods [%x][%x]\n", upstream_connection->idstr, buffer[0], buffer[1]);
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            if (!upstream_connection->server_name)
                upstream_connection->state=SOCKS5_SEND_CONNECT;
            else
                upstream_connection->state=SOCKS5_TOR_SEND_RESOLVE;
            break;

        case SOCKS5_TOR_SEND_RESOLVE:
            if (!FD_ISSET(upstream_connection->fd, &fdset_write))
                break;

            length=strlen(upstream_connection->server_name);

            buffer[0]=0x05;
            buffer[1]=0xf0;
            buffer[2]=0x00;
            buffer[3]=0x03;
            buffer[4]=length;
            memcpy(buffer+5, upstream_connection->server_name, length);
            buffer[6+length]=0x00;
            buffer[7+length]=0x50;

            if ((count=write(upstream_connection->fd, buffer, 7+length)) == -1)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to send resolve request - error [%s]\n", upstream_connection->idstr, strerror(errno));
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            upstream_connection->state=SOCKS5_TOR_READ_RESOLVE;
            break;

        case SOCKS5_TOR_READ_RESOLVE:
            if (!FD_ISSET(upstream_connection->fd, &fdset_read))
                break;

            if ((count=read(upstream_connection->fd, buffer, sizeof(buffer))) == -1)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to read resolve reply - error [%s]\n", upstream_connection->idstr, strerror(errno));
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            if (buffer[0] == 0x05 && buffer[1] == 0x00 && buffer[2] == 0x00 && buffer[3] == 0x01)
                memcpy(&upstream_connection->server_address, buffer+4, 4);
            else
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] unexpected resolve reply [%x] [%x]\n", upstream_connection->idstr, buffer[0], buffer[1]);
                ts_log_hex(buffer, count);
            }

            upstream_connection->resultcode=buffer[1];
            upstream_connection->state=SOCKS5_TOR_DONE_RESOLVE;
            break;

        case SOCKS5_SEND_CONNECT:
            if (!FD_ISSET(upstream_connection->fd, &fdset_write))
                break;

            buffer[0]=0x05;
            buffer[1]=0x01;
            buffer[2]=0x00;
            buffer[3]=0x01;
            memcpy(buffer+4, &upstream_connection->server_address,4);
            memcpy(buffer+8, &upstream_connection->server_port,2);

            if ((count=write(upstream_connection->fd, buffer, 10)) == -1)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to send connect request - error [%s]\n", upstream_connection->idstr, strerror(errno));
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            upstream_connection->state=SOCKS5_READ_CONNECT;
            break;

        case SOCKS5_READ_CONNECT:
            if (!FD_ISSET(upstream_connection->fd, &fdset_read))
                break;

            if ((count=read(upstream_connection->fd, buffer, sizeof(buffer))) == -1)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to read connect reply - error [%s]\n", upstream_connection->idstr, strerror(errno));
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            if (buffer[0] != 0x05 || buffer[1] != 0x00)
            {
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] unexpected server connect reply [%x][%x]\n", upstream_connection->idstr, buffer[0], buffer[1]);
                upstream_connection->state=UPSTREAM_CLOSED;
                break;
            }

            upstream_connection->state=UPSTREAM_CONNECTED;
            break;

        case UPSTREAM_CONNECTED:
            if (FD_ISSET(upstream_connection->fd, &fdset_read))
                upstream_connection->functions->read(upstream_connection);

            if (upstream_connection->fd != -1 && FD_ISSET(upstream_connection->fd, &fdset_write))
                upstream_connection->functions->write(upstream_connection);

            break;
    }    
}

upstream_functions_t socks5_upstream_functions=
{
    .connect        = &socks5_upstream_connect_start,
    .prepare_events = &socks5_upstream_prepare_events,
    .handle_events  = &socks5_upstream_handle_events,
    .disconnect     = &tcp_upstream_disconnect,
    .read           = &tcp_upstream_read,
    .write          = &tcp_upstream_write
};

//
// HTTP upstream functions
//

u_int8_t http_200_response_header[]=
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Server: AVNE Status 0.5\r\n"
"\r\n";

u_int8_t http_404_html_response[]=
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Server: AVNE Status 0.5\r\n"
"\r\n"
"<!DOCTYPE html>\r\n"
"<html lang=\"en\">\r\n"
"    <head>\r\n"
"        <title>AVNE status</title>\r\n"
"        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n"
"        <meta name=\"description\" content=\"404 Not Found\">\r\n"
"    </head>\r\n"
"    <body>\r\n"
"       <h1>404 Not Found</h1>\r\n"
"    </body>\r\n"
"</html>\r\n";

char html_response_start[]=
"<!DOCTYPE html>\r\n"
"<html lang=\"en\">\r\n"
"    <head>\r\n"
"        <title>AVNE status</title>\r\n"
"        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n"
"        <meta name=\"description\" content=\"current status\">\r\n"
"        <style>\r\n"
"           body {font-family: Arial, Helvetica, sans-serif;}\r\n"
"           td, a, b {padding : 0.25em 1em;}\r\n"
"        </style>\r\n"
"    </head>\r\n"
"    <body>\r\n"
"       <h1>Another Virtual Network Environment....</h1>\r\n"
"       <h3>%s<a href=""http://10.10.10.10"">refresh</a></h3>\r\n" 
"       <table>\r\n"
"            <tr><td>TCP Connections</td><td>%d</td><td>active</td><td>%d</td></tr>\r\n"
"            <tr><td>DNS Connections</td><td>%d</td><td>active</td><td>%d</td></tr>\r\n"
"            <tr><td>DNS failes</td><td>%d</td><td>rejects</td><td>%d</td></tr>\r\n"
"            <tr><td>DNS cache entries</td><td>%d</td><td>cache hits</td><td>%d</td></tr>\r\n"
"       </table>\r\n"  
"";

char html_response_end[]=
"    </body>\r\n"
"</html>";

int http_upstream_connect(upstream_connection_t *upstream_connection)
{
    // write a status html page

    if ((upstream_connection->fd=open(http_filename, O_RDWR | O_CREAT | O_TRUNC , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) == -1)
    {
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to open http_file [%s] - error [%s]\n", http_filename, strerror(errno));
        return 0;
    }

    dprintf(upstream_connection->fd, html_response_start,
        get_log_time(),
        tcp_connection_total_count, tcp_connection_count, 
        dns_connection_total_count, dns_connection_count,
        dns_connection_fail_count, dns_connection_reject_count,
        dns_cache_count, dns_connection_cache_hit_count,
        dns_connection_reject_count);

    // write list of open tcp connections

    char client_ip_string[20],server_ip_string[20];

    tcp_connection_t *tcp_connection=tcp_connection_root;

    dprintf(upstream_connection->fd, "<h3>List of open TCP Connections</h3>\r\n");

    while (tcp_connection)
    {
        ip_string(tcp_connection->client_address, client_ip_string, sizeof(client_ip_string));
        ip_string(tcp_connection->server_address, server_ip_string, sizeof(server_ip_string));

        dprintf(upstream_connection->fd,"<p>[%s] [%s:%u] -> [%s:%u] <b>(%s)</b></p>\r\n", 
            tcp_connection->idstr, 
            client_ip_string, ntohs(tcp_connection->client_port),
            server_ip_string, ntohs(tcp_connection->server_port),
            dns_cache_get_name(tcp_connection->server_address));

        tcp_connection=tcp_connection->next;
    }

    // write list of open dns connections

    dns_connection_t *dns_connection=dns_connection_root;

    dprintf(upstream_connection->fd, "<h3>List of open DNS Connections</h3>\r\n");

    while (dns_connection)
    {
        dprintf(upstream_connection->fd,"<p>[%s] [%s]</p>\r\n", dns_connection->idstr, 
            dns_cache_entry_get_name(dns_connection->cache_entry));
        dns_connection=dns_connection->next;
    }

    dprintf(upstream_connection->fd, html_response_end);
    lseek(upstream_connection->fd, 0, SEEK_SET);

    upstream_connection->state=UPSTREAM_CONNECTING;
    return 1;
}

void http_upstream_prepare_events(upstream_connection_t *upstream_connection)
{
    if (!upstream_connection || upstream_connection->fd == -1)
        return;

    switch (upstream_connection->state)
    {
        case UPSTREAM_CONNECTING:
            if (upstream_connection->output->count)
            {
                if (strcasestr((char*) upstream_connection->output->data, "GET / "))
                {
                    buffer_add(upstream_connection->input, http_200_response_header, sizeof(http_200_response_header)-1);
                    lseek(upstream_connection->fd, 0, SEEK_SET);
                }
                else
                    buffer_add(upstream_connection->input, http_404_html_response, sizeof(http_404_html_response)-1);

                upstream_connection->output->count=0;
                upstream_connection->state=UPSTREAM_CONNECTED;
            }
            break;

        case UPSTREAM_CONNECTED:
            if (!buffer_space(upstream_connection->input))
                return;

            FD_SET(upstream_connection->fd, &fdset_read);

            if (upstream_connection->fd > fd_max)
                fd_max=upstream_connection->fd;

            break;
    }
}

upstream_functions_t http_upstream_functions=
{
    .connect        = &http_upstream_connect,
    .prepare_events = &http_upstream_prepare_events,
    .disconnect     = &tcp_upstream_disconnect,
    .read           = &tcp_upstream_read,
    .write          = &tcp_upstream_write,
    .handle_events  = &tcp_upstream_handle_events
};

//
// The next code does absolutely nothing...
//

int nop_upstream_int(upstream_connection_t *upstream_connection)
{
    return 1;
}

void nop_upstream_void(upstream_connection_t *upstream_connection)
{
}

upstream_functions_t nop_upstream_functions=
{
    .connect        = &nop_upstream_int,
    .disconnect     = &nop_upstream_int,
    .read           = &nop_upstream_void,
    .write          = &nop_upstream_void,
    .prepare_events = &nop_upstream_void,
    .handle_events  = &nop_upstream_void
};

//
// TCP
//

void log_tcp_datagram(struct iphdr *ip_header, struct tcphdr *tcp_header, int what, const char *where, const char *message)
{
    char client_ip_string[20],server_ip_string[20];

    ip_string(ip_header->saddr, client_ip_string, sizeof(client_ip_string));
    ip_string(ip_header->daddr, server_ip_string, sizeof(server_ip_string));

    ts_log(0, what, where, "[%s:%u] -> [%s:%u] %s\n", 
       client_ip_string, ntohs(tcp_header->source), 
       server_ip_string, ntohs(tcp_header->dest), 
       message);
}

void log_tcp_connection(tcp_connection_t *tcp_connection, int what, const char *where, const char *message)
{
    char client_ip_string[20],server_ip_string[20];

    ip_string(tcp_connection->client_address, client_ip_string, sizeof(client_ip_string));
    ip_string(tcp_connection->server_address, server_ip_string, sizeof(server_ip_string));

    ts_log(0, what, where, "[%s] [%s:%u] -> [%s:%u] %s\n", tcp_connection->idstr, 
       client_ip_string, ntohs(tcp_connection->client_port), 
       server_ip_string, ntohs(tcp_connection->server_port), 
       message);
}

int seq_between(u_int32_t seq, u_int32_t seq_start, u_int32_t seq_end)
{
    if (seq_end >= seq_start)
        return (seq >= seq_start && seq <= seq_end) ? 1 : 0;

    // seq_end has wrapped, this means that seq *might* have wrapped also...

    return (seq_between(seq, seq_start, 0xffffffff) || seq_between(seq, 0, seq_end)) ? 1 : 0;
}

void tcp_connection_upstream_init(tcp_connection_t *tcp_connection, upstream_functions_t *functions)
{
    tcp_connection->upstream.fd=-1;
    tcp_connection->upstream.state=UPSTREAM_CLOSED;
    tcp_connection->upstream.id=tcp_connection->id;
    tcp_connection->upstream.idstr=tcp_connection->idstr;
    tcp_connection->upstream.server_address=tcp_connection->server_address;
    tcp_connection->upstream.server_port=tcp_connection->server_port;
    tcp_connection->upstream.input=&tcp_connection->output;
    tcp_connection->upstream.output=&tcp_connection->input;
    tcp_connection->upstream.functions=functions;
}

void tcp_connection_init(tcp_connection_t *tcp_connection, struct iphdr *ip_header, struct tcphdr * tcp_header)
{
    char idstr[16];

    if (!(tcp_connection && ip_header && tcp_header))
        return;

    memset(tcp_connection, 0, sizeof(tcp_connection_t));

    tcp_connection->id=tcp_connection_total_count++;
    sprintf(idstr,"tcp:%d",tcp_connection->id);
    tcp_connection->idstr=strdup(idstr);

    tcp_connection->server_address=ip_header->daddr;
    tcp_connection->server_port=tcp_header->dest;
    tcp_connection->client_address=ip_header->saddr;
    tcp_connection->client_port=tcp_header->source;

    tcp_connection->server_seq=1;
    tcp_connection->server_mss=MTU-sizeof(struct iphdr)-sizeof(struct tcphdr);
    tcp_connection->server_window=TCP_BUFFER_SIZE;
    tcp_connection->tcp_state=TCP_LISTEN;

    tcp_connection->client_seq=ntohl(tcp_header->seq);
    tcp_connection->client_window=ntohs(tcp_header->window);

    buffer_init(&tcp_connection->input, TCP_BUFFER_SIZE);
    buffer_init(&tcp_connection->output, TCP_BUFFER_SIZE);

    tcp_connection->upstream.functions=&nop_upstream_functions;

    // insert into linked list

    tcp_connection->next=tcp_connection_root;
    tcp_connection_root=tcp_connection;
    tcp_connection_count++;

    char client_ip_string[20],server_ip_string[20];

    ip_string(tcp_connection->client_address, client_ip_string, sizeof(client_ip_string));
    ip_string(tcp_connection->server_address, server_ip_string, sizeof(server_ip_string));

    ts_log(0, LOG_INFO, __FUNCTION__, "[%s] [%s:%u] -> [%s:%u] (%s) - active [%d]\n", 
        tcp_connection->idstr, 
        client_ip_string, ntohs(tcp_connection->client_port),
        server_ip_string, ntohs(tcp_connection->server_port),
        dns_cache_get_name(tcp_connection->server_address),
        tcp_connection_count);
}

void tcp_connection_done(tcp_connection_t *tcp_connection)
{
    tcp_connection_t *tcp_connection_previous;

    tcp_connection->upstream.functions->disconnect(&tcp_connection->upstream);

    // remove from linked list

    tcp_connection_count--;

    if (tcp_connection_root == tcp_connection)
        tcp_connection_root=tcp_connection->next;
    else
    {
        tcp_connection_previous=tcp_connection_root;

        while (tcp_connection_previous && tcp_connection_previous->next != tcp_connection)
            tcp_connection_previous=tcp_connection_previous->next;

        if (tcp_connection_previous)
            tcp_connection_previous->next=tcp_connection->next;
        else
            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to find previous linked-list entry\n", tcp_connection->idstr);
    }

    ts_log(0, LOG_INFO, __FUNCTION__, "[%s] active [%d]\n", tcp_connection->idstr, tcp_connection_count);

    buffer_done(&tcp_connection->input);
    buffer_done(&tcp_connection->output);
    free(tcp_connection->idstr);
    free(tcp_connection);
}

void tcp_connection_prepare_events(tcp_connection_t *tcp_connection)
{
    if (!tcp_connection->upstream.functions->prepare_events)
        return;

    tcp_connection->upstream.functions->prepare_events(&tcp_connection->upstream);

    if (tcp_connection->output.count)
        tcp_connection_write(tcp_connection);
}

void tcp_connection_handle_events(tcp_connection_t *tcp_connection)
{
    u_int16_t previous_input_count, previous_upstream_state, bytes_to_ack;

    if (!tcp_connection->upstream.functions->handle_events)
        return;

    previous_input_count=tcp_connection->input.count;
    previous_upstream_state=tcp_connection->upstream.state;
    tcp_connection->upstream.functions->handle_events(&tcp_connection->upstream);

    if ((bytes_to_ack = previous_input_count - tcp_connection->input.count))
    {
        tcp_connection->client_seq_ack+=bytes_to_ack;
        tcp_connection_send_datagram(tcp_connection, TCP_PSH+TCP_ACK, 0, 0);
    }

    if (tcp_connection->output.count)
        tcp_connection_write(tcp_connection);

    if (tcp_connection->upstream.state == UPSTREAM_CLOSED &&
        previous_upstream_state != UPSTREAM_CLOSED)
        tcp_connection_server_close(tcp_connection);
}

u_int16_t tcp_connection_send_datagram(tcp_connection_t *tcp_connection, u_int8_t flags, u_int8_t *data, u_int32_t data_size)
{
    // send a server->client datagram
    //
    // NOTES:
    // 
    // 1) uses the same buffer as the client->server datagram
    // 2) never sends a datagram larger than MTU
    // 3) never sends more data than MIN(client_mss , server_mss, client_window_left)
    // 4) returns the number of data bytes send

    typedef struct
    {
        u_int8_t   kind;
        u_int8_t   length;
        u_int16_t  mss;
    } tcp_options_t;

    struct iphdr *ip_header;
    struct tcphdr *tcp_header;
    tcp_options_t *tcp_options;
    u_int8_t  *tcp_data;
    u_int16_t data_count=0;
    u_int16_t datagram_size;
    int count;

    //ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", tcp_connection->idstr);

    ip_header=(struct iphdr *) datagram;
    ip_header->ihl=5;
    ip_header->version=4;
    ip_header->id=htons((u_int16_t) tcp_connection->server_seq);
    ip_header->frag_off=0x40; // do not fragment flag
    ip_header->ttl=10;
    ip_header->protocol=IPPROTO_TCP;
    ip_header->check=0;
    ip_header->saddr=tcp_connection->server_address;
    ip_header->daddr=tcp_connection->client_address;

    tcp_header=(struct tcphdr *) (datagram+sizeof(struct iphdr));
    tcp_header->source=tcp_connection->server_port;
    tcp_header->dest=tcp_connection->client_port;
    tcp_header->seq=htonl(tcp_connection->server_seq);
    tcp_header->ack_seq=htonl(tcp_connection->client_seq_ack);
    tcp_header->fin=(flags & TCP_FIN) ? 1 : 0;
    tcp_header->syn=(flags & TCP_SYN) ? 1 : 0;
    tcp_header->rst=(flags & TCP_RST) ? 1 : 0;
    tcp_header->psh=(flags & TCP_PSH) ? 1 : 0;
    tcp_header->ack=(flags & TCP_ACK) ? 1 : 0;
    tcp_header->urg=(flags & TCP_URG) ? 1 : 0;
    tcp_header->res1=0;
    tcp_header->res2=0;
    tcp_header->window=htons(TCP_BUFFER_SIZE);

    if (tcp_header->syn) // add mss option, no data
    {
        tcp_options = (tcp_options_t *) (datagram+sizeof(struct iphdr)+sizeof(struct tcphdr));
        tcp_options->kind=TCPOPT_MAXSEG;
        tcp_options->length=TCPOLEN_MAXSEG;
        tcp_options->mss=htons(MTU-sizeof(struct iphdr)-sizeof(struct tcphdr));

        tcp_header->doff=6; 
        datagram_size=sizeof(struct iphdr)+sizeof(struct tcphdr)+sizeof(tcp_options_t);
        ip_header->tot_len=htons(datagram_size);
    }
    else // add data, no options
    {
        u_int32_t client_window_left;

        if (tcp_connection->server_seq >= tcp_connection->server_seq_ack)
            client_window_left=tcp_connection->client_window-(tcp_connection->server_seq-tcp_connection->server_seq_ack);
        else
        {
            if (tcp_connection->server_seq_ack-tcp_connection->server_seq > 0x8fffffff)
                client_window_left=tcp_connection->client_window-(0xffffffff-tcp_connection->server_seq_ack+tcp_connection->server_seq);
            else
            {
                client_window_left=0;
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] unable to determine client_window_left - server_seq [%u] server_seq_ack [%u]\n", 
                    tcp_connection->idstr, tcp_connection->server_seq, tcp_connection->server_seq_ack);
            }
        } 

        if (!client_window_left)
        {
            //ts_log(0, LOG_INFO, __FUNCTION__, "[%s] no client window left\n", tcp_connection->idstr);
        }  

        data_count=data_size;

        if (data_count > tcp_connection->client_mss)
            data_count=tcp_connection->client_mss;

        if (data_count > tcp_connection->server_mss)
            data_count=tcp_connection->server_mss;

        if (data_count > client_window_left)
            data_count=client_window_left;

        if ((data_count == 0) && data_size)
            return 0;

        tcp_data = datagram+sizeof(struct iphdr)+sizeof(struct tcphdr);

        if (data && data_count)
            memcpy(tcp_data, data, data_count);

        tcp_header->doff=5;
        datagram_size=sizeof(struct iphdr)+sizeof(struct tcphdr)+data_count;  
        ip_header->tot_len=htons(datagram_size);
    }

    ip_header->check=ip_checksum(ip_header, sizeof(struct iphdr));
    tcp_header->check=tcp_checksum(ip_header, tcp_header);

    while (1)
    {
        count=write(fd_tun, datagram, datagram_size);

        if (count == -1)
        {
            if (errno == EINTR)
                continue;

            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to write [%u] bytes to fd_tun - error [%s]\n", tcp_connection->idstr, datagram_size, strerror(errno)); 
        }
        else
            if (count != datagram_size)
                ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] datagram size [%u] != written size [%d]\n", tcp_connection->idstr, datagram_size, count); 

        break;
    } 

    return data_count;
}

tcp_connection_t* get_tcp_connection(struct iphdr *ip_header, struct tcphdr *tcp_header)
{
    tcp_connection_t *tcp_connection;

    // new connection ?

    if (tcp_header->syn)
    {
        tcp_connection_t *tcp_connection=malloc(sizeof(tcp_connection_t));
        tcp_connection_init(tcp_connection, ip_header, tcp_header);

        // address 10.10.10.10:80 is for the internal status server...

        if (tcp_connection->server_address == 0x0a0a0a0a && tcp_connection->server_port == htons(80))
            tcp_connection_upstream_init(tcp_connection, &http_upstream_functions);
        else
            tcp_connection_upstream_init(tcp_connection, default_tcp_upstream_functions);

        return tcp_connection;
    }

    // lookup existing connection

    tcp_connection=tcp_connection_root;

    while (tcp_connection)
    {
        if (tcp_connection->server_address == ip_header->daddr &&
            tcp_connection->server_port == tcp_header->dest &&
            tcp_connection->client_address == ip_header->saddr &&
            tcp_connection->client_port == tcp_header->source)
            return tcp_connection;

        tcp_connection=tcp_connection->next;
    }

    log_tcp_datagram(ip_header, tcp_header, LOG_ERROR, __FUNCTION__,"failed to find tcp connection");
    return 0;
}

void tcp_connection_read(tcp_connection_t *tcp_connection, u_int8_t *data, u_int16_t data_size)
{
    if (!data_size)
        return;

    buffer_add(&tcp_connection->input, data, data_size);
    tcp_connection->client_seq+=data_size;
}

void tcp_connection_write(tcp_connection_t *tcp_connection)
{
    //ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", tcp_connection->idstr);

    u_int16_t tcp_data_size;

    while (tcp_connection->output.count)
    {
        tcp_data_size=tcp_connection_send_datagram(tcp_connection, TCP_ACK, tcp_connection->output.data, tcp_connection->output.count);

        if (!tcp_data_size)
        {
            //ts_log(0, LOG_INFO, __FUNCTION__, "[%s] failed to write to client\n", tcp_connection->idstr);
            break;
        }  

        buffer_remove(&tcp_connection->output, tcp_data_size);
        tcp_connection->server_seq+=tcp_data_size;
    }
}

void tcp_connection_server_close(tcp_connection_t *tcp_connection)
{
    ts_log(0,LOG_INFO, __FUNCTION__,"[%s]\n", tcp_connection->idstr);

    if (tcp_connection->tcp_state != TCP_ESTABLISHED)
    {
        ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] wrong tcp_state [%u]\n", tcp_connection->idstr, tcp_connection->tcp_state);
        return;
    }

    tcp_connection_send_datagram(tcp_connection, TCP_FIN+TCP_ACK, 0,0);
    tcp_connection->server_seq++;
    tcp_connection->tcp_state=TCP_FIN_WAIT1;
}

void tcp_connection_process_header_options(tcp_connection_t *tcp_connection, struct tcphdr *tcp_header)
{
    u_int8_t *tcp_option, *tcp_data, tcp_option_length;

    if (tcp_header->doff == 5)
        return;

    tcp_option=(u_int8_t *) tcp_header + sizeof(struct tcphdr);
    tcp_data=(u_int8_t*) tcp_header + (tcp_header->doff << 2);

    while (tcp_option < tcp_data)
    {
        tcp_option_length=*(tcp_option+1);

        switch (*tcp_option)
        {
            case TCPOPT_EOL:
            case TCPOPT_NOP:
                tcp_option_length=1;
                break;

            case TCPOPT_MAXSEG:
                tcp_connection->client_mss=ntohs(*((u_int16_t *) (tcp_option+2)));
                break;

            case TCPOPT_WINDOW:
                tcp_connection->client_wscale=*(tcp_option+2);
                break;

            default:
                break;
        }

        tcp_option+=tcp_option_length;
    }
}

void handle_tcp(u_int16_t datagram_size)
{
    struct tcphdr *tcp_header;
    tcp_connection_t *tcp_connection;
    u_int8_t  *tcp_data;
    u_int32_t ip_header_size;
    u_int32_t tcp_header_size, tcp_header_seq, tcp_header_seq_ack, tcp_data_size;
    u_int16_t tcp_check;

    ip_header_size=ip_header->ihl << 2;
    tcp_header=(struct tcphdr*) (datagram+ip_header_size);

    if ((tcp_check=tcp_checksum(ip_header, tcp_header)) != tcp_header->check)
    {
        ts_log(0, LOG_ERROR, __FUNCTION__, "checksum error - header [%x], calculated [%x]\n", ntohs(tcp_header->check), ntohs(tcp_check));
        return;
    }

    if (!(tcp_connection=get_tcp_connection(ip_header, tcp_header)))
    {
        tcp_connection_t *tcp_connection_reply=malloc(sizeof(tcp_connection_t));

        tcp_connection_init(tcp_connection_reply, ip_header, tcp_header);
        tcp_connection_send_datagram(tcp_connection_reply, TCP_RST+TCP_ACK, 0, 0);
        tcp_connection_done(tcp_connection_reply);
        return;
    }

    if (tcp_header->rst)
    {
        log_tcp_connection(tcp_connection, LOG_INFO, __FUNCTION__, "got RST");
        // TODO - response datagram?
        // NO, but... sometimes (very seldom) I see packets from client after RST...
        tcp_connection_done(tcp_connection);
        return;
    }

    tcp_header_size=tcp_header->doff << 2;
    tcp_header_seq = ntohl(tcp_header->seq);
    tcp_header_seq_ack = ntohl(tcp_header->ack_seq);
    tcp_data=(u_int8_t *) tcp_header+tcp_header_size;
    tcp_data_size=datagram_size-ip_header_size-tcp_header_size;

    tcp_connection->client_window=ntohs(tcp_header->window);
    tcp_connection_process_header_options(tcp_connection, tcp_header);

    switch (tcp_connection->tcp_state)
    {
        case TCP_LISTEN:
            tcp_connection->client_seq++;
            tcp_connection->client_seq_ack=tcp_connection->client_seq;
            tcp_connection_send_datagram(tcp_connection, TCP_SYN+TCP_ACK, 0, 0);
            tcp_connection->server_seq++;
            tcp_connection->tcp_state=TCP_SYN_RECV;
            break;

        case TCP_SYN_RECV: 
            if (tcp_header->ack == 1 && tcp_header_seq == tcp_connection->client_seq)
            {
                tcp_connection->tcp_state=TCP_ESTABLISHED;
                tcp_connection->upstream.functions->connect(&tcp_connection->upstream);
                break;
            }

            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] TCP_SYN_RECV client did not correctly finisch TCP handshake\n", tcp_connection->idstr);
            tcp_connection_send_datagram(tcp_connection, TCP_RST, 0, 0);
            tcp_connection_done(tcp_connection);
            tcp_connection=0;
            break;

        case TCP_ESTABLISHED:
            // check if the segment has the expected seq nr

            if (tcp_header_seq == tcp_connection->client_seq)
            {
                if (tcp_header->fin)
                {
                    ts_log(0, LOG_INFO, __FUNCTION__, "[%s] TCP_ESTABLISHED got FIN\n", tcp_connection->idstr);
                    tcp_connection->client_seq++;
                    tcp_connection->client_seq_ack=tcp_connection->client_seq;
                    tcp_connection_send_datagram(tcp_connection, TCP_ACK+TCP_FIN, 0, 0);
                    tcp_connection->server_seq++;
                    tcp_connection->tcp_state=TCP_LAST_ACK;
                    break;
                }

                if (seq_between(tcp_header_seq_ack, tcp_connection->server_seq_ack, tcp_connection->server_seq))
                    tcp_connection->server_seq_ack=tcp_header_seq_ack;
                else 
                    ts_log(0,LOG_ERROR, __FUNCTION__,"[%s] TCP_ESTABLISHED unexpected ACK value\n", tcp_connection->idstr);
            }
            else 
            {
                // keepalive packet?
                if ((tcp_header_seq == tcp_connection->client_seq-1) 
                    && seq_between(tcp_header_seq_ack, tcp_connection->server_seq_ack, tcp_connection->server_seq))
                {
                    tcp_connection_send_datagram(tcp_connection, TCP_ACK, 0, 0);
                    break;
                }
                else  
                {
                    // probably retransmission, ignore the segment
                    break;
                }
            }

            tcp_connection_read(tcp_connection, tcp_data, tcp_data_size);

            // an ACK or window update from the client may have opened the 
            // send-window to the client so always try to write after a read...

            tcp_connection_write(tcp_connection);
            break;

        case TCP_CLOSE_WAIT:
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] TCP_CLOSE_WAIT\n", tcp_connection->idstr);

            tcp_connection->client_seq++;
            tcp_connection->client_seq_ack=tcp_connection->client_seq;
            tcp_connection_send_datagram(tcp_connection, TCP_ACK+TCP_FIN, 0, 0);
            tcp_connection->server_seq++;
            tcp_connection->tcp_state=TCP_LAST_ACK;
            break;

        case TCP_LAST_ACK:
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] TCP_LAST_ACK\n", tcp_connection->idstr);

            if (tcp_header->ack && tcp_header_seq_ack == tcp_connection->server_seq)
            {
                tcp_connection_done(tcp_connection);
                tcp_connection=0;
            }        
            break;

        case TCP_FIN_WAIT1:
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] TCP_FIN_WAIT1\n", tcp_connection->idstr);

            // only ACK on server FIN ?

            if (tcp_header->ack && !tcp_header->fin && tcp_header_seq_ack == tcp_connection->server_seq)
            {
                tcp_connection->tcp_state = TCP_FIN_WAIT2;
                break;
            }

            // only FIN from client ?  

            if (tcp_header->fin && !tcp_header->ack)
            {
                tcp_connection->client_seq=tcp_header_seq+1;
                tcp_connection->client_seq_ack=tcp_header_seq+1;
                tcp_connection_send_datagram(tcp_connection, TCP_ACK, 0, 0);
                tcp_connection->tcp_state = TCP_CLOSING;
                break;
            }

            // received FIN+ACK ?

            if (tcp_header->fin && tcp_header->ack && tcp_header_seq_ack == tcp_connection->server_seq)
            {
                tcp_connection->client_seq=tcp_header_seq+1;
                tcp_connection->client_seq_ack=tcp_header_seq+1;
                tcp_connection_send_datagram(tcp_connection, TCP_ACK, 0, 0);
                tcp_connection->tcp_state = TCP_CLOSE;
                tcp_connection_done(tcp_connection);
                break;
            }

            //ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] unexpected TCP_FIN_WAIT1 response", tcp_connection->idstr); 
            break;

        case TCP_FIN_WAIT2:
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] TCP_FIN_WAIT2\n", tcp_connection->idstr);

            // cient FIN ?

            if (tcp_header->fin)
            {
                tcp_connection->client_seq=tcp_header_seq+1;
                tcp_connection->client_seq_ack=tcp_header_seq+1;
                tcp_connection_send_datagram(tcp_connection, TCP_ACK, 0, 0);
                tcp_connection_done(tcp_connection);
                break;
            }

            //ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] unexpected TCP_FIN_WAIT2 response", tcp_connection->idstr); 
            break;

        case TCP_CLOSING:
            ts_log(0, LOG_INFO, __FUNCTION__, "[%s] TCP_CLOSING\n", tcp_connection->idstr);

            if (tcp_header->ack && tcp_header_seq_ack == tcp_connection->server_seq)
                tcp_connection_done(tcp_connection);

            break;

        // TCP_TIME_WAIT is only needed for connections that can loose
        // the server ACK on the last FIN, this can not happen in our case so
        // we go to the closed state immediately

        case TCP_TIME_WAIT:
        case TCP_CLOSE:
            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] client sended on a CLOSED connection\n", tcp_connection->idstr);
            break;
    }
}

//
// DNS cache
//

typedef struct dns_header_s dns_header_t;
typedef struct dns_answer_s dns_answer_t;

struct dns_header_s
{
    u_int16_t transaction_id;
#if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t rd:1;
    u_int16_t tc:1;
    u_int16_t aa:1;
    u_int16_t opcode:4;
    u_int16_t qr:1;

    u_int16_t rcode:4;
    u_int16_t cd:1;
    u_int16_t ad:1;
    u_int16_t z:1;
    u_int16_t ra:1;
#elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t qr:1;
    u_int16_t opcode:4;
    u_int16_t aa:1;
    u_int16_t tc:1;
    u_int16_t rd:1;

    u_int16_t ra:1;
    u_int16_t z:1;
    u_int16_t ad:1;
    u_int16_t cd:1;
    u_int16_t rcode:4;
#else
#   error "Adjust your <bits/endian.h> defines"
#endif
    union
    {
        u_int16_t qdcount;
        u_int16_t zocount;
    };
    union
    {
        u_int16_t ancount;
        u_int16_t prcount;
    };
    union
    {
        u_int16_t nscount;
        u_int16_t upcount;
    };
    union
    {
        u_int16_t adcount;
        u_int16_t arcount;    
    };
} __attribute__((packed));

struct dns_answer_s
{
    u_int8_t name_compression;
    u_int8_t name_offset;
    u_int16_t type;
    u_int16_t class;
    u_int32_t ttl;
    u_int16_t rdl;
    u_int32_t address;
} __attribute__((packed));

void log_dns_message(dns_header_t *dns_header, u_int16_t dns_header_size)
{
    ts_log(0, LOG_INFO, __FUNCTION__, "\n");
    ts_log(0, LOG_INFO, 0, "transaction_id [%x]\n", ntohs(dns_header->transaction_id));
    ts_log(0, LOG_INFO, 0, "qr [%u] opcode [%u] aa [%u] tc [%u] rd [%u]\n",
        dns_header->qr, dns_header->opcode, dns_header->aa, dns_header->tc, dns_header->rd);
    ts_log(0, LOG_INFO, 0, "ra [%u] z [%u] ad [%u] cd [%u] rcode [%u]\n",
        dns_header->ra, dns_header->z, dns_header->ad, dns_header->cd, dns_header->rcode);
    ts_log(0, LOG_INFO, 0, "qd/zo count [%u]\n" , ntohs(dns_header->qdcount));
    ts_log(0, LOG_INFO, 0, "an/pr count [%u]\n" , ntohs(dns_header->ancount));
    ts_log(0, LOG_INFO, 0, "ns/up count [%u]\n" , ntohs(dns_header->nscount));
    ts_log(0, LOG_INFO, 0, "ad/ar count [%u]\n" , ntohs(dns_header->adcount));
    ts_log_hex(((u_int8_t *) dns_header) + sizeof(dns_header_t), dns_header_size-sizeof(dns_header_t)); 
}

void dns_cache_entry_init(dns_cache_entry_t *dns_cache_entry)
{
    memset(dns_cache_entry, 0, sizeof(dns_cache_entry_t));

    dns_cache_entry->expires=time(0)+3;

    // insert into linked list

    dns_cache_count++;
    dns_cache_entry->next=dns_cache_entry_root;
    dns_cache_entry_root=dns_cache_entry;
}

void dns_cache_entry_done(dns_cache_entry_t *dns_cache_entry)
{
    dns_cache_entry_t *dns_cache_entry_previous;

    dns_cache_count--;

    if (dns_cache_entry_root == dns_cache_entry)
        dns_cache_entry_root=dns_cache_entry->next;
    else
    {
        dns_cache_entry_previous=dns_cache_entry_root;

        while (dns_cache_entry_previous && dns_cache_entry_previous->next != dns_cache_entry)
            dns_cache_entry_previous=dns_cache_entry_previous->next;

        if (dns_cache_entry_previous)
            dns_cache_entry_previous->next=dns_cache_entry->next;
        else
            ts_log(0, LOG_ERROR, __FUNCTION__, "failed to find previous linked-list entry\n");
    }

    free(dns_cache_entry->name);
    free(dns_cache_entry->request);
    free(dns_cache_entry->response);
    free(dns_cache_entry);
}

char *dns_cache_entry_get_name(dns_cache_entry_t *dns_cache_entry)
{
    static char question_name[256];
    u_int8_t length, *length_pos, *question;

    if (dns_cache_entry->name)
        return dns_cache_entry->name;

    if (dns_cache_entry && dns_cache_entry->request)
    {
        question=dns_cache_entry->request+sizeof(dns_header_t);
        strncpy(question_name, (char *) question, sizeof(question_name)); 
        length_pos= (u_int8_t*) question_name;

        while ((length=*length_pos))
        {
            *length_pos='.';
            length_pos+=length+1;
        }

        dns_cache_entry->name=strdup(question_name+1);
    }
    else
        strcpy(question_name, "?unknown question name");

    return question_name+1;
}

// nbo

#define DNS_TYPE_A 0x0100
#define DNS_CLASS_IN 0x0100

u_int32_t dns_cache_entry_get_address(dns_cache_entry_t *dns_cache_entry)
{
    char *query_question;
    dns_header_t *header;
    dns_answer_t *answer;
    u_int16_t *query_type, *query_class, i;

    if (!dns_cache_entry->response)
        return 0;

    if (dns_cache_entry->address)
        return dns_cache_entry->address;

    header=(dns_header_t *) dns_cache_entry->response;

    if (ntohs(header->qdcount) != 1 || ntohs(header->ancount) == 0 || header->rcode != 0)
    {
//        ts_log(0, LOG_INFO, __FUNCTION__, "no address - rcode [%u] query count [%u] answer count [%u]\n", 
//            header->rcode, ntohs(header->qdcount), ntohs(header->ancount));
        return 0;
    }    

    query_question=(char *) header + sizeof(dns_header_t);
    query_type= (u_int16_t *) (query_question+strlen(query_question)+1); // only safe if 1 query
    query_class=query_type+1;

    if (*query_type != DNS_TYPE_A || *query_class != DNS_CLASS_IN)
    {
        ts_log(0, LOG_INFO, __FUNCTION__, "no address for query of type [%x] class [%x]\n", ntohs(*query_type), ntohs(*query_class));
        return 0;
    }

    answer=(dns_answer_t *)(query_class+1);    

    for (i=0; i < header->ancount; i++)
    {
        if (answer->name_compression != 0xc0)
        {
            ts_log(0, LOG_ERROR, __FUNCTION__, "expected name compression\n");
            return 0;
        }

        if (answer->type == DNS_TYPE_A && answer->class == DNS_CLASS_IN)
        {
            dns_cache_entry->address=answer->address;
            return (answer->address);
        }

        answer = (dns_answer_t*) ((u_int8_t *) answer + sizeof(dns_answer_t) - 4 + ntohs(answer->rdl));
    } 

    return 0; 
}

void dns_cache_entry_set_request(dns_cache_entry_t *dns_cache_entry, struct udphdr *udp_header)
{
    u_int8_t *udp_data=(u_int8_t *) udp_header+sizeof(struct udphdr);
    u_int16_t udp_data_size=ntohs(udp_header->len)-sizeof(struct udphdr);

    dns_cache_entry->request_size=udp_data_size;
    dns_cache_entry->request=malloc(udp_data_size);
    memcpy(dns_cache_entry->request, udp_data, udp_data_size);

    // find out if its a local or fake request

    char* name=dns_cache_entry_get_name(dns_cache_entry);
    int local_name_length=strlen(_res.defdname);

    if (strchr(name, '.') == NULL)
    {
        dns_cache_entry->request_type=DNS_REQUEST_PROBE;
        return;
    }

    dns_cache_entry->request_type=DNS_REQUEST_NORMAL;

    if (local_name_length)
    {
        char* local_name_pos=strcasestr(name, _res.defdname);

        if (local_name_pos && *(local_name_pos+local_name_length) == 0)
            dns_cache_entry->request_type=DNS_REQUEST_LOCAL;
    }
}

void dns_cache_entry_set_response(dns_cache_entry_t *dns_cache_entry, u_int8_t *udp_data, u_int16_t udp_data_size)
{
    char buffer[32];

    dns_cache_entry->expires=time(0)+3600;

    dns_cache_entry->response_size=udp_data_size;
    dns_cache_entry->response=malloc(udp_data_size);
    memcpy(dns_cache_entry->response, udp_data, udp_data_size);

    ip_string(dns_cache_entry_get_address(dns_cache_entry), buffer, sizeof(buffer)); 

    ts_log(0, LOG_INFO, __FUNCTION__, "response address [%s]\n", buffer); 
}

void dns_cache_entry_build_response(dns_cache_entry_t *dns_cache_entry, u_int8_t rcode, u_int32_t server_address)
{
    dns_header_t *dns_header;
    dns_answer_t answer;

    switch (rcode)
    {
        case 3: // Name Error
        case 5: // Refused
            dns_cache_entry_set_response(dns_cache_entry, dns_cache_entry->request, dns_cache_entry->request_size);

            dns_header= (dns_header_t *) dns_cache_entry->response;
            dns_header->qr=1;
            dns_header->rcode=rcode;
            break;

        case 0: // Success!
            answer.name_compression=0xc0;
            answer.name_offset=sizeof(dns_header_t);
            answer.type=DNS_TYPE_A;
            answer.class=DNS_CLASS_IN;
            answer.ttl=htonl(3600);
            answer.rdl=htons(4);
            answer.address=server_address;

            dns_cache_entry->expires=time(0)+3600;

            dns_cache_entry->response_size=dns_cache_entry->request_size+sizeof(answer);
            dns_cache_entry->response=malloc(dns_cache_entry->response_size);
            memcpy(dns_cache_entry->response, dns_cache_entry->request, dns_cache_entry->request_size);
            memcpy(dns_cache_entry->response+dns_cache_entry->request_size, &answer, sizeof(answer));

            dns_header=(dns_header_t *) dns_cache_entry->response;
            dns_header->qr=1;
            dns_header->rcode=rcode;
            dns_header->ancount=htons(1);

            break;
        default:
            ts_log(0, LOG_ERROR, __FUNCTION__, "rcode [%u] is not supported\n", rcode);
            break;
    }
}

int dns_cache_entry_request_equal(dns_cache_entry_t *dns_cache_entry, struct udphdr *udp_header)
{
    u_int8_t *udp_data=(u_int8_t *) udp_header+sizeof(struct udphdr);
    u_int16_t udp_data_size=ntohs(udp_header->len)-sizeof(struct udphdr);

    // compare udp_data with request except for the first two bytes which contain the transaction_id

    if (dns_cache_entry->request_size != udp_data_size)
        return 0;

    return (memcmp(dns_cache_entry->request+2, udp_data+2, udp_data_size-2) == 0) ? 1 : 0;
}

int dns_cache_entry_handle_expired(dns_cache_entry_t *dns_cache_entry)
{
    dns_connection_t *dns_connection, *dns_connection_next;

    if (time(0) < dns_cache_entry->expires)
        return 0;

    // remove DNS connections that have this cache_entry

    if (dns_cache_entry->response == 0)
    {
        dns_connection=dns_connection_root;

        while (dns_connection)
        {
            dns_connection_next=dns_connection->next;

            if (dns_connection->cache_entry == dns_cache_entry)
                dns_connection_done(dns_connection);

            dns_connection=dns_connection_next;
        }
    }

    dns_cache_entry_done(dns_cache_entry);
    return 1;
}

dns_cache_entry_t *dns_cache_get_entry(struct udphdr *udp_header)
{
    ts_log(0, LOG_INFO, __FUNCTION__, "start\n");

    dns_cache_entry_t *dns_cache_entry, *dns_cache_entry_next;

    dns_cache_entry=dns_cache_entry_root;

    while (dns_cache_entry)
    {
        dns_cache_entry_next=dns_cache_entry->next;

        if (dns_cache_entry_request_equal(dns_cache_entry, udp_header))
        {
            if (dns_cache_entry_handle_expired(dns_cache_entry))
                break;
            else
                return dns_cache_entry;
        }

        dns_cache_entry=dns_cache_entry_next;
    }

    // insert a new entry

    ts_log(0, LOG_INFO, __FUNCTION__, "insert new entry start\n");

    dns_cache_entry=malloc(sizeof(dns_cache_entry_t));
    dns_cache_entry_init(dns_cache_entry);
    dns_cache_entry_set_request(dns_cache_entry, udp_header);
    return dns_cache_entry;
}

char *dns_cache_get_name(u_int32_t address)
{
    static char unknown[]="unknown";

    dns_cache_entry_t *dns_cache_entry=dns_cache_entry_root;

    while (dns_cache_entry)
    {
        if (address == dns_cache_entry_get_address(dns_cache_entry))
            return dns_cache_entry_get_name(dns_cache_entry);

        dns_cache_entry=dns_cache_entry->next;
    }

    return unknown;
}

//
// DNS connection
//

void dns_connection_upstream_init(dns_connection_t *dns_connection, upstream_functions_t *functions)
{
    //ts_log(0, LOG_INFO, __FUNCTION__, "start\n");

    dns_connection->upstream.fd=-1;
    dns_connection->upstream.state=UPSTREAM_CLOSED;
    dns_connection->upstream.id=dns_connection->id;
    dns_connection->upstream.idstr=dns_connection->idstr;

    if (functions == &socks5_upstream_functions && dns_connection_use_tor_resolve)
    {
        dns_connection->upstream.server_address=0;
        dns_connection->upstream.server_port=0;
        dns_connection->upstream.server_name=strdup(dns_cache_entry_get_name(dns_connection->cache_entry));
    }
    else
    {
        dns_connection->upstream.server_address=0x08080808; // Google DNS snooper
        dns_connection->upstream.server_port=dns_connection->server_port;
        dns_connection->upstream.server_name=0;
    }

    dns_connection->upstream.input=&dns_connection->output;
    dns_connection->upstream.output=&dns_connection->input;
    dns_connection->upstream.functions=functions;
}

void dns_connection_init(dns_connection_t *dns_connection, struct iphdr *ip_header, struct udphdr *udp_header)
{
    char idstr[16];
    u_int8_t *udp_data;
    u_int16_t udp_data_size, udp_data_size_nbo;
    dns_cache_entry_t *cache_entry;

    udp_data=(u_int8_t *) udp_header+sizeof(struct udphdr);
    udp_data_size=ntohs(udp_header->len)-sizeof(struct udphdr);
    log_dns_message((dns_header_t *) udp_data, udp_data_size);

    memset(dns_connection, 0, sizeof(dns_connection_t));

    dns_connection->id=dns_connection_total_count++;
    sprintf(idstr,"dns:%d",dns_connection->id);
    dns_connection->idstr=strdup(idstr);

    dns_connection->transaction_id=*((u_int16_t *) udp_data);
    dns_connection->server_address=ip_header->daddr;
    dns_connection->server_port=udp_header->dest;
    dns_connection->client_address=ip_header->saddr;
    dns_connection->client_port=udp_header->source;

    // insert connection into the linked list

    dns_connection->next=dns_connection_root;
    dns_connection_root=dns_connection;
    dns_connection_count++;

    ts_log(0, LOG_INFO, __FUNCTION__, "[%s] active [%d]\n",dns_connection->idstr, dns_connection_count);

    cache_entry=dns_cache_get_entry(udp_header);
    dns_connection->cache_entry=cache_entry;
    dns_connection_upstream_init(dns_connection, default_dns_upstream_functions);

    if (cache_entry->response)
    {
        dns_connection_cache_hit_count++;
        dns_connection_send_datagram(dns_connection);
        return;
    }

    // not resolved yet, test for unwanted requests

    if (cache_entry->request_type == DNS_REQUEST_LOCAL)
    {
        ts_log(0, LOG_INFO, __FUNCTION__, "[%s] dropping local DNS request [%s]\n", dns_connection->idstr, dns_cache_entry_get_name(cache_entry));

        dns_connection_reject_count++;
        dns_cache_entry_build_response(cache_entry, 3, 0);
        dns_connection_send_datagram(dns_connection);
        dns_cache_entry_done(cache_entry);
        return;            
    }

    if (cache_entry->request_type == DNS_REQUEST_PROBE)
    {
        ts_log(0, LOG_INFO, __FUNCTION__, "[%s] dropping probe DNS request [%s]\n", dns_connection->idstr, dns_cache_entry_get_name(cache_entry));

        dns_connection_reject_count++;
        dns_cache_entry_build_response(cache_entry, 5, 0);
        dns_connection_send_datagram(dns_connection);
        dns_cache_entry_done(cache_entry);
        return;            
    }

    // are we the first to see this request?    

    if (++cache_entry->response_clients == 1)
    {
        // then we need an upstream connection....

        buffer_init(&dns_connection->input, DNS_BUFFER_SIZE);
        buffer_init(&dns_connection->output, DNS_BUFFER_SIZE);

        ts_log(0, LOG_INFO, __FUNCTION__, "[%s] name [%s]\n",dns_connection->idstr, dns_cache_entry_get_name(dns_connection->cache_entry));

        // convert the UDP request into a TCP request and put it in the input buffer

        udp_data_size_nbo=htons(udp_data_size);
        buffer_add(&dns_connection->input, (u_int8_t *) &udp_data_size_nbo, sizeof(udp_data_size_nbo));
        buffer_add(&dns_connection->input, udp_data, udp_data_size);

        dns_connection->upstream.functions->connect(&dns_connection->upstream);
    }
}

void dns_connection_done(dns_connection_t *dns_connection)
{
    dns_connection_t *dns_connection_previous;

    dns_connection->upstream.functions->disconnect(&dns_connection->upstream);

    // remove connection from linked list

    dns_connection_count--;
    dns_connection->cache_entry->response_clients--;

    if (dns_connection_root == dns_connection)
        dns_connection_root=dns_connection->next;
    else
    {
        dns_connection_previous=dns_connection_root;

        while (dns_connection_previous && dns_connection_previous->next != dns_connection)
            dns_connection_previous=dns_connection_previous->next;

        if (dns_connection_previous)
            dns_connection_previous->next=dns_connection->next;
        else
            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to find previous linked-list entry\n", dns_connection->idstr);
    }

    ts_log(0, LOG_INFO, __FUNCTION__, "[%s] active [%d] total/cache_hits/failed/rejected [%d/%d/%d/%d] \n",dns_connection->idstr, 
        dns_connection_count, dns_connection_total_count, dns_connection_cache_hit_count, 
        dns_connection_fail_count, dns_connection_reject_count);

    free(dns_connection->upstream.server_name);
    buffer_done(&dns_connection->input);
    buffer_done(&dns_connection->output);
    free(dns_connection->idstr);
    free(dns_connection);
}

void dns_connection_send_datagram(dns_connection_t *dns_connection)
{
    struct iphdr *ip_header;
    struct udphdr *udp_header;
    u_int8_t   *udp_data;
    u_int16_t datagram_size;
    int data_count;

    ts_log(0, LOG_INFO, __FUNCTION__, "start\n");

    ip_header=(struct iphdr *) datagram;
    ip_header->ihl=5;
    ip_header->version=4;
    ip_header->id=htons((u_int16_t) dns_connection_count);
    ip_header->frag_off=0x40; // do not fragment flag
    ip_header->ttl=10;
    ip_header->protocol=IPPROTO_UDP;
    ip_header->check=0;
    ip_header->saddr=dns_connection->server_address;
    ip_header->daddr=dns_connection->client_address;

    udp_header=(struct udphdr *)(datagram +sizeof(struct iphdr));
    udp_header->source=dns_connection->server_port;
    udp_header->dest=dns_connection->client_port;
    udp_header->len=htons(sizeof(struct udphdr) + dns_connection->cache_entry->response_size);
    udp_header->check=0;

    udp_data=(u_int8_t *) udp_header+sizeof(struct udphdr);
    *((u_int16_t *) udp_data)=dns_connection->transaction_id;
    memcpy(udp_data+2, dns_connection->cache_entry->response+2, dns_connection->cache_entry->response_size-2);

    log_dns_message((dns_header_t *) udp_data, dns_connection->cache_entry->response_size);

    datagram_size=sizeof(struct iphdr)+sizeof(struct udphdr)+dns_connection->cache_entry->response_size;  
    ip_header->tot_len=htons(datagram_size);
    ip_header->check=ip_checksum(ip_header, sizeof(struct iphdr));
    udp_header->check=udp_checksum(ip_header, udp_header);

    while (1)
    {
        data_count=write(fd_tun, datagram, datagram_size);

        if (data_count == -1)
        {
            if (errno == EINTR)
                continue;

            ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to write datagram - error [%s]\n", dns_connection->idstr, strerror(errno));
        }

        break;
    }

    if (data_count != datagram_size)
        ts_log(0, LOG_ERROR, __FUNCTION__, "[%s] failed to write datagram - size [%u] written [%d]\n", dns_connection->idstr, datagram_size, data_count);

    dns_connection_done(dns_connection);
}

void dns_connection_prepare_events(dns_connection_t *dns_connection)
{
    dns_connection->upstream.functions->prepare_events(&dns_connection->upstream);
}

void dns_connection_handle_events(dns_connection_t *dns_connection)
{
    dns_connection_t  *dns_connection_next;
    dns_cache_entry_t *dns_cache_entry;
    u_int16_t response_size;

    dns_connection->upstream.functions->handle_events(&dns_connection->upstream);

    if (dns_connection->upstream.functions == &socks5_upstream_functions && dns_connection_use_tor_resolve)
    {
        // Tor - DNS response expected...

        if (dns_connection->upstream.state != SOCKS5_TOR_DONE_RESOLVE)
            return;

        if (dns_connection->upstream.resultcode == 0x04) //  SOCKS5_HOST_UNREACHABLE 
            dns_cache_entry_build_response(dns_connection->cache_entry, 3, dns_connection->upstream.server_address); // report name error
        else
            dns_cache_entry_build_response(dns_connection->cache_entry, 0, dns_connection->upstream.server_address);
    }
    else 
    {
        // TCP-response, is it complete?

        if (dns_connection->output.count <= 2)
            return;

        response_size=ntohs(*((u_int16_t *) dns_connection->output.data));

        if ((response_size+2) != dns_connection->output.count)
            return;

        dns_cache_entry_set_response(dns_connection->cache_entry, dns_connection->output.data+2, response_size);
    }

    // send datagram to all connections with our cache_entry

    dns_cache_entry=dns_connection->cache_entry;
    dns_connection=dns_connection_root;

    while (dns_cache_entry->response_clients && dns_connection)
    {
        dns_connection_next=dns_connection->next;

        if (dns_connection->cache_entry == dns_cache_entry)
            dns_connection_send_datagram(dns_connection);

        dns_connection=dns_connection_next;
    }
}

//
// UDP
//

void handle_udp(u_int16_t datagram_size)
{
    struct udphdr *udp_header;
    u_int16_t ip_header_size, udp_check;
    dns_connection_t *dns_connection;

    ip_header_size=ip_header->ihl << 2;
    udp_header=(struct udphdr*) (datagram+ip_header_size);

    //ts_log(0,LOG_INFO, __FUNCTION__,"start\n");

    if ((udp_check=udp_checksum(ip_header, udp_header)) != udp_header->check)
    {
        ts_log(0, LOG_ERROR, __FUNCTION__, "wrong udp checksum got [0x%x] calculated [0x%x]\n", ntohs(udp_header->check), ntohs(udp_check));
        return;
    }

    switch (ntohs(udp_header->dest))
    {
        case 53: 
            dns_connection=malloc(sizeof(dns_connection_t));
            dns_connection_init(dns_connection, ip_header, udp_header);
            break;

        default:
            break;
    }
}

//
// ICMP
//

void handle_icmp(u_int16_t datagram_size)
{
    struct icmphdr *icmp_header;
    u_int32_t source_address;
    u_int16_t ip_header_size, icmp_message_size;

    ip_header_size=ip_header->ihl << 2;
    icmp_header=(struct icmphdr*) (datagram+ip_header_size);
    icmp_message_size=datagram_size-ip_header_size;

    if (ip_checksum(icmp_header, icmp_message_size))
    {
        ts_log(0, LOG_INFO, __FUNCTION__, "wrong checksum\n");
        return;
    }

    ts_log(0, LOG_INFO, __FUNCTION__, "got ICMP message of type [%u] code [%u] size [%d]\n",icmp_header->type, icmp_header->code, icmp_message_size);

    switch (icmp_header->type)
    {
        case ICMP_ECHO:
            // just a simple ping responder for testing....

            icmp_header->type=ICMP_ECHOREPLY;
            icmp_header->checksum=0;
            icmp_header->checksum=ip_checksum(icmp_header, icmp_message_size);

            source_address=ip_header->saddr;
            ip_header->saddr=ip_header->daddr;
            ip_header->daddr=source_address;

            ip_header->check=0;
            ip_header->check=ip_checksum(ip_header, ip_header_size);

            if (write(fd_tun, datagram, datagram_size) == -1)
                ts_log(0, LOG_INFO, __FUNCTION__, "failed to write ICMP_ECHO reply - error [%s]\n", strerror(errno));
            break;

        default:
            break;
    }
}

//
// IP
//

void handle_datagram(u_int16_t datagram_size)
{
    if (ip_checksum(ip_header, ip_header->ihl << 2))
    {
        ts_log(0, LOG_ERROR, __FUNCTION__, "wrong ip_checksum\n");
        return;
    }

    if (ntohs(ip_header->tot_len) != datagram_size)
        ts_log(0, LOG_INFO, __FUNCTION__, "packet size mismatch: got [%u] - tot_len [%u]\n", datagram_size, ntohs(ip_header->tot_len));

    switch (ip_header->protocol)
    {
        case IPPROTO_ICMP:
            handle_icmp(datagram_size);
            break;

        case IPPROTO_UDP:
            handle_udp(datagram_size);
            break;

        case IPPROTO_TCP:
            handle_tcp(datagram_size);
            break;
    }
}

void tun_handle_events()
{
    int datagram_size;

    if (!FD_ISSET(fd_tun, &fdset_read))
        return;

    while (1)
    {
        datagram_size=read(fd_tun, datagram, sizeof(datagram));

        if (datagram_size == -1)
        {
            if (errno == EWOULDBLOCK)
                break;

            if (errno == EINTR)
                continue;

            ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to read datagram - error [%s]\n", strerror(errno));
        }

        handle_datagram(datagram_size);
        break;
    }
}

void io_loop()
{
    dns_connection_t *dns_connection, *dns_connection_next;
    tcp_connection_t *tcp_connection, *tcp_connection_next;

    while (child_running)
    {
        FD_ZERO(&fdset_read);
        FD_ZERO(&fdset_write);
        FD_SET(fd_tun, &fdset_read);
        fd_max=fd_tun;

        // prepare TCP events

        tcp_connection=tcp_connection_root;

        while (tcp_connection)
        {
            tcp_connection_prepare_events(tcp_connection);
            tcp_connection=tcp_connection->next;
        }

        // prepare DNS events

        dns_connection=dns_connection_root;

        while (dns_connection)
        {
            dns_connection_prepare_events(dns_connection);
            dns_connection=dns_connection->next;
        }

        // gather events

        if (select(1+fd_max, &fdset_read, &fdset_write, 0, 0) == -1)
        {
            if (errno == EINTR)
                continue;

            ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "select failed - error [%s]\n", strerror(errno));
        }

        // handle TCP events

        tcp_connection=tcp_connection_root;

        while (tcp_connection)
        {
            tcp_connection_next=tcp_connection->next;
            tcp_connection_handle_events(tcp_connection);
            tcp_connection=tcp_connection_next;
        }

        // handle DNS events

        dns_connection=dns_connection_root;

        while (dns_connection)
        {
            dns_connection_next=dns_connection->next;
            dns_connection_handle_events(dns_connection);
            dns_connection=dns_connection_next;
        }

        // handle client events 

        tun_handle_events();
    }
}

void configure_network(char *tun_interface_name)
{
    int fd;
    struct ifreq interface_request;
    struct rtentry route_entry;
    struct sockaddr_in *socket_address, *gateway_address, *destination_address, *genmask;

    // open tun interface

    if ((fd_tun=open("/dev/net/tun", O_RDWR)) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to open tun device with name [%s] - error [%s]\n", tun_interface_name, strerror(errno));

    memset(&interface_request, 0, sizeof(interface_request));
    strcpy(interface_request.ifr_name, tun_interface_name);
    interface_request.ifr_flags=IFF_TUN | IFF_NO_PI;

    if(ioctl(fd_tun, TUNSETIFF, (void *) &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set tun device interface - error [%s]\n", strerror(errno));

    if (fcntl(fd_tun, F_SETFL, O_NONBLOCK) == -1) 
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to make tun device nonblocking - error [%s]\n", strerror(errno));

    // configure tun interface. 

    memset(&interface_request, 0, sizeof(interface_request));
    strcpy(interface_request.ifr_name, tun_interface_name);
    fd=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);

    // set address

    socket_address= (struct sockaddr_in *) &interface_request.ifr_addr;
    socket_address->sin_family=AF_INET;
    inet_aton("10.0.0.1", &socket_address->sin_addr);

    if (ioctl(fd, SIOCSIFADDR, (void *) &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set tun interface address - error [%s]\n", strerror(errno));

    // set netmask

    socket_address->sin_addr.s_addr=htonl(0xffffff00);

    if (ioctl(fd, SIOCSIFNETMASK, (void *) &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set tun interface netmask - error [%s]\n", strerror(errno));

    // set state

    interface_request.ifr_flags=IFF_POINTOPOINT | IFF_RUNNING | IFF_NOARP | IFF_UP;

    if (ioctl(fd, SIOCSIFFLAGS, &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set tun interface UP flags - error [%s]\n", strerror(errno));

    // set gateway

    memset(&route_entry, 0, sizeof(route_entry));

    route_entry.rt_flags = RTF_UP | RTF_GATEWAY;
    route_entry.rt_metric = 0;
    route_entry.rt_dev=tun_interface_name;

    gateway_address= (struct sockaddr_in *) &route_entry.rt_gateway;
    gateway_address->sin_family = AF_INET;
    inet_aton("10.0.0.1", &gateway_address->sin_addr);

    destination_address= (struct sockaddr_in *) &route_entry.rt_dst;
    destination_address->sin_family = AF_INET;
    destination_address->sin_addr.s_addr = INADDR_ANY;

    genmask= (struct sockaddr_in *) &route_entry.rt_genmask;
    genmask->sin_family = AF_INET;
    genmask->sin_addr.s_addr = INADDR_ANY;

    if (ioctl(fd, SIOCADDRT, &route_entry) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set tun interface default route - error [%s]\n", strerror(errno));

    // MTU?

    // configure localhost

    memset(&interface_request, 0, sizeof(interface_request));
    strcpy(interface_request.ifr_name, "lo");
    socket_address->sin_family=AF_INET;
    inet_aton("127.0.0.1", &socket_address->sin_addr);

    if (ioctl(fd, SIOCSIFADDR, (void *) &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set localhost address - error [%s]\n", strerror(errno));

    socket_address->sin_addr.s_addr=htonl(0xff000000);

    if (ioctl(fd, SIOCSIFNETMASK, (void *) &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set localhost netmask - error [%s]\n", strerror(errno));

    interface_request.ifr_flags=IFF_RUNNING | IFF_UP;

    if (ioctl(fd, SIOCSIFFLAGS, &interface_request) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to change localhost state to UP - error [%s]\n", strerror(errno));

    close(fd);
}

static void sigchld_handler(int sig)
{
    int errno_process=errno;

    if (waitpid(pid_child, NULL, WNOHANG) == pid_child)
        child_running=0;

    errno=errno_process;
}

void inject_program(int argc, char *argv[])
{
    char filename[PATH_MAX];
    char *hostname="AVNE";
    int fd_netns_attach;

    // run a program inside an existing network namespace

    if (atoi(argv[1]) == 0)
    {
        dprintf(STDERR_FILENO, "avne: invalid pid [%s] - error [%s]\n", argv[1], strerror(errno));
        exit(EXIT_FAILURE);
    }

    sprintf(filename,"/proc/%s/ns/net", argv[1]);

    if ((fd_netns_attach = open(filename, O_RDONLY)) == -1)
    {
        dprintf(STDERR_FILENO, "avne: failed to open netns of [%s] - error [%s]\n", filename, strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (setns(fd_netns_attach, CLONE_NEWNET) == -1)
    {
        dprintf(STDERR_FILENO, "avne: failed to switch to netns of [%s] - error [%s]\n", filename, strerror(errno));
        exit(EXIT_FAILURE);
    }

    close(fd_netns_attach);

    // unshare the uts namespace so we can change the host name

    if (unshare(CLONE_NEWUTS) == -1)
    {
        dprintf(STDERR_FILENO, "avne: failed to unshare uts namespace - error [%s]\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (sethostname(hostname, strlen(hostname)) == -1)
    {
        dprintf(STDERR_FILENO, "avne: failed to change hostname to [%s] - error [%s]\n", hostname, strerror(errno));
        exit(EXIT_FAILURE);
    }

    argv+=2;

    if (setuid(getuid()) == -1)
    {
        dprintf(STDERR_FILENO, "avne: failed drop privileges - error [%s]\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (execvp(argv[0], argv) == -1)
    {
        dprintf(STDERR_FILENO, "avne: failed to exec program [%s] - error [%s]\n", argv[0], strerror(errno));
        exit(EXIT_FAILURE);
    }
}

void start_program(int argc, char *argv[])
{
    char buffer[256];
    char *filename=buffer;
    char *hostname="AVNE";
    int  fd_netns_old;
    struct sigaction sa_sigchld;

    sprintf(filename,"/proc/%u/ns/net", getpid());

    if ((fd_netns_old = open(filename, O_RDONLY)) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to open netns_old - error [%s]\n", strerror(errno));

    // unshare the uts namespace so we can change the host name

    if (unshare(CLONE_NEWUTS) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to unshare uts namespace - error [%s]\n", strerror(errno));

    ts_log(0, LOG_INFO, __FUNCTION__, "setting hostname to [%s]\n", hostname);

    if (sethostname(hostname, strlen(hostname)) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to change hostname to [%s] - error [%s]\n", hostname, strerror(errno));

    // create a new network namespace

    if (unshare(CLONE_NEWNET) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to unshare network namespace - error [%s]\n", strerror(errno));

    configure_network("avni0");

    // fork a child that execs the client program

    sigemptyset(&sa_sigchld.sa_mask);
    sa_sigchld.sa_handler = &sigchld_handler;
    sa_sigchld.sa_flags = SA_RESTART | SA_NOCLDSTOP;

    if (sigaction(SIGCHLD, &sa_sigchld, 0) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to set SIGCHLD handler - error [%s]\n", strerror(errno));

    pid_child=fork();

    if (pid_child == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to fork - error [%s]\n", strerror(errno));

    if (pid_child == 0)
    {
        close(fd_netns_old);
        close(fd_tun);

        if (setuid(getuid()) == -1)
            ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed drop child privileges - error [%s]\n", argv[0], strerror(errno));

        if (execvp(argv[0], argv) == -1)
            ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to exec program [%s] - error [%s]\n", argv[0], strerror(errno));
    }

    printf("avne: child pid is [%d]\n",pid_child);

    // change network namespace back to original namespace so we can make upstream connections 

    if (setns(fd_netns_old, CLONE_NEWNET) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to switch back to netns_old - error [%s]\n", strerror(errno));

    if (setuid(getuid()) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed drop privileges - error [%s]\n", argv[0], strerror(errno));
}

void read_settings()
{
    char conf_filename[PATH_MAX];
    char *buffer=conf_filename;
    char *buffer_pos, *line_pos, *value_pos;
    FILE *stream;
    char *line = 0;
    size_t line_buffer_size = 0;
    ssize_t line_length;
    long port;
    u_int32_t address;

    // default settings

    socks_server_address="127.0.0.1";
    socks_server_port=9050;
    dns_connection_use_tor_resolve=1;
    default_tcp_upstream_functions=&socks5_upstream_functions;
    default_dns_upstream_functions=&socks5_upstream_functions;

    if (readlink( "/proc/self/exe", conf_filename, PATH_MAX ) == -1)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "failed to get program path - error [%s]\n", strerror(errno));

    if (strlen(conf_filename)+6 > PATH_MAX)
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "conf filename would be too long\n");

    strcat(conf_filename, ".conf");

    if ((stream=fopen(conf_filename, "r")) == 0)
    {
        ts_log(0, LOG_INFO, __FUNCTION__, "failed to read configuration file [%s] : using defaults\n", conf_filename);
        return;
    }

    while ((line_length=getline(&line, &line_buffer_size, stream)) != -1) 
    {
        buffer_pos=buffer;
        line_pos=line;

        while (line_pos <= line+line_length)
        {
            if (!isspace(*line_pos))
                *buffer_pos++=*line_pos;

            line_pos++;
        }

        if ((buffer[0] == '#') || !(value_pos=strchr(buffer,'=')))
            continue;

        *value_pos++=0;

        if (strcasecmp(buffer, "socks_address") == 0)
        {
            if (inet_pton(AF_INET, value_pos, &address) == 0)
                ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "illegal value for socks_address [%s]\n", value_pos);

            socks_server_address=strdup(value_pos);
            continue;
        }

        if (strcasecmp(buffer, "socks_port") == 0) 
        {
            port=strtol(value_pos,0, 10);

            if (port > 1024 && port < 0xffff)
                socks_server_port=port;
            else
                ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "illegal value for socks_port [%s]\n", value_pos);

            continue;
        }

        if (strcasecmp(buffer, "socks_tcp_connection") == 0)
        {
            if (strcmp(value_pos,"0") == 0)
                default_tcp_upstream_functions=&tcp_upstream_functions;
            {
                if (strcmp(value_pos,"1") == 0)
                    default_tcp_upstream_functions=&socks5_upstream_functions;
                else
                    ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "illegal value for socks_tcp_connection [%s]\n", value_pos);
            }

            continue;
        }

        if (strcasecmp(buffer, "socks_dns_connection") == 0)
        {
            if (strcmp(value_pos,"0") == 0)
                default_dns_upstream_functions=&tcp_upstream_functions;
            else
            {
                if (strcmp(value_pos,"1") == 0)
                    default_dns_upstream_functions=&socks5_upstream_functions;
                else
                    ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "illegal value for socks_dns_connection [%s]\n", value_pos);
            }

            continue;
        }

        if (strcasecmp(buffer, "socks_dns_use_tor_resolve") == 0)
        {
            if (strcmp(value_pos,"0") == 0)
                dns_connection_use_tor_resolve=0;
            else
            {
                if (strcmp(value_pos,"1") == 0)
                    dns_connection_use_tor_resolve=1;
                else
                    ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "illegal value for socks_dns_use_tor_resolve [%s]\n", value_pos);
            }

            continue;
        }

        // unknown configuration option

        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "unknown configuration option: name [%s] value [%s]\n", buffer, value_pos);
    }

    // sanity check

    if (dns_connection_use_tor_resolve && (default_dns_upstream_functions != &socks5_upstream_functions))
        ts_log(EXIT_FAILURE, LOG_ERROR, __FUNCTION__, "you specified socks_dns_use_tor_resolve=1 on a non-socks connection\n");

    ts_log(0, LOG_INFO, __FUNCTION__, "\n");
    ts_log(0, LOG_INFO, 0, "socks_address             [%s]\n", socks_server_address);
    ts_log(0, LOG_INFO, 0, "socks_port                [%u]\n", socks_server_port);
    ts_log(0, LOG_INFO, 0, "socks_tcp_connection      [%d]\n", (default_tcp_upstream_functions == &socks5_upstream_functions) ? 1 : 0);
    ts_log(0, LOG_INFO, 0, "socks_dns_connection      [%d]\n", (default_dns_upstream_functions == &socks5_upstream_functions) ? 1 : 0);
    ts_log(0, LOG_INFO, 0, "socks_dns_use_tor_resolve [%d]\n", dns_connection_use_tor_resolve);

    free(line);
    fclose(stream);
}


const char *program_version="0.5";

const char *program_options=""
"\n"
"Another Virtual Network Environment - version %s\n"
"\n"
"Usage 1:\n"
"\n"
"    avne [program path] [program options]\n"
"\n"
"Examples:\n"
"\n"
"    avne chromium --disable-cache\n"
"    avne wget https://blog.torproject.org/\n"
"\n"
"Usage 2:\n"
"\n"
"    avne --use-namespace [pid] [program path] [program options]\n"
"\n"
"Example:\n"
"\n" 
"    avne --use-namespace 1234 wireshark\n"
"\n"
"The --use-namespace option does not configure a new network environment, but\n"
"allows you to use an existing network environment. Specify a pid of a running\n"
"avne CHILD program (e.g. a firefox instance that was previously started by\n"
"avne)\n"
"\n";

int main(int argc, char *argv[])
{
    res_init(); 

    if (argc < 2 )
    {
        printf(program_options, program_version);
        return EXIT_SUCCESS;
    }

    if (*argv[1] == '-')
    {
        if (strcasecmp(argv[1],"-h") == 0 || strcasecmp(argv[1],"--help") == 0)
        {
            printf(program_options, program_version);
            return EXIT_SUCCESS;
        }

        if (strcasecmp(argv[1],"--use-namespace") != 0 || argc < 4)
        {
            printf("\n\nERROR: INVALID OPTION OR TOO FEW PARAMETERS\n\n");
            printf(program_options, program_version);
            return EXIT_FAILURE;
        }
    }

    if (strcasecmp(argv[1],"--use-namespace") == 0)
    {
        inject_program(argc-1, argv+1);
        return 0;
    }

    ts_log_open(log_filename, 0);

    read_settings();

    printf("AVNE started at %s\n", get_log_time());
    start_program(argc-1, argv+1);
    io_loop();
    printf("AVNE closed\n");

    close(fd_tun);
    return EXIT_SUCCESS;
}

Comments: 0