HOEVENSTEIN

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