source code of avne.c
Rob van der HoevenWed 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; }