/* * ptunnel.c * ptunnel is licensed under the BSD license: * * Copyright (c) 2004-2011, Daniel Stoedle , * Yellow Lemon Software. All rights reserved. * * Copyright (c) 2017 Toni Uhlig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of the Yellow Lemon Software nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Contacting the author: * You can get in touch with me, Daniel Stødle (that's the Norwegian letter oe, * in case your text editor didn't realize), here: * * The official ptunnel website is here: * * * Note that the source code is best viewed with tabs set to 4 spaces. */ #include "ptunnel.h" #include "options.h" #include "md5.h" #ifdef HAVE_SELINUX #include #endif #ifdef WIN32 /* pthread porting to windows */ typedef CRITICAL_SECTION pthread_mutex_t; typedef unsigned long pthread_t; #define pthread_mutex_init InitializeCriticalSectionAndSpinCount #define pthread_mutex_lock EnterCriticalSection #define pthread_mutex_unlock LeaveCriticalSection #include /* Map errno (which Winsock doesn't use) to GetLastError; include the code in the strerror */ #ifdef errno #undef errno #endif /* errno */ #define errno GetLastError() /* Local error string storage */ static char errorstr[255]; static char * print_last_windows_error() { DWORD last_error = GetLastError(); memset(errorstr, 0, sizeof(errorstr)); FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, 0, errorstr, sizeof(errorstr), NULL); snprintf(errorstr, sizeof(errorstr), "%s (%d)", errorstr, last_error); return errorstr; } #define strerror(x) print_last_windows_error() #else #endif /* WIN32 */ // Lots of globals pthread_mutex_t chain_lock, // Lock protecting the chain of connections num_threads_lock; // Lock protecting the num_threads variable int num_threads = 0; // Current thread count uint32_t num_tunnels = 0; // Current tunnel count /** Table indicating when a connection ID is allowable (used by proxy) */ uint32_t *seq_expiry_tbl = NULL; // Some buffer constants const int tcp_receive_buf_len = kDefault_buf_size, icmp_receive_buf_len = kDefault_buf_size + kIP_header_size + kICMP_header_size + sizeof(ping_tunnel_pkt_t), pcap_buf_size = (kDefault_buf_size + kIP_header_size + kICMP_header_size + sizeof(ping_tunnel_pkt_t)+64)*64; char pcap_filter_program[] = "icmp"; // && (icmp[icmptype] = icmp-echo || icmp[icmptype] = icmp-echoreply)"; // The chain of client/proxy connections proxy_desc_t *chain = 0; const char *state_name[kNum_proto_types] = { "start", "ack", "data", "close", "authenticate" }; // Let the fun begin! int main(int argc, char *argv[]) { #ifndef WIN32 pid_t pid; #endif #ifdef WIN32 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return -1; } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup(); return -1; } #endif /* WIN32 */ // Seed random generator; it'll be used in combination with a timestamp // when generating authentication challenges. srand(time(0)); memset(opts.password_digest, 0, kMD5_digest_size); /* The seq_expiry_tbl is used to prevent the remote ends from prematurely re-using a sequence number. */ seq_expiry_tbl = calloc(65536, sizeof(uint32_t)); // Parse options if (parse_options(argc, argv)) return -1; if (opts.pcap && opts.udp) { pt_log(kLog_error, "Packet capture is not supported (or needed) when using UDP for transport.\n"); opts.pcap = 0; } pt_log(kLog_info, "Starting ptunnel v %d.%.2d.\n", kMajor_version, kMinor_version); pt_log(kLog_info, "(c) 2004-2011 Daniel Stoedle, \n"); #ifdef WIN32 pt_log(kLog_info, "Windows version by Mike Miller, \n"); #else pt_log(kLog_info, "Security features by Sebastien Raveau, \n"); #endif pt_log(kLog_info, "%s.\n", (opts.mode == kMode_forward ? "Relaying packets from incoming TCP streams" : "Forwarding incoming ping packets over TCP")); if (opts.udp) pt_log(kLog_info, "UDP transport enabled.\n"); pt_log(kLog_debug, "Destination at %s:%u\n", opts.given_dst_hostname, opts.given_dst_port); if (opts.mode == kMode_forward) pt_log(kLog_debug, "Listen for incoming connections at 0.0.0.0:%u\n", opts.tcp_listen_port); #ifndef WIN32 signal(SIGPIPE, SIG_IGN); if (opts.use_syslog) { if (opts.log_file != stdout) { pt_log(kLog_error, "Logging using syslog overrides the use of a specified logfile (using -f).\n"); fclose(opts.log_file); opts.log_file = stdout; } openlog("ptunnel", LOG_PID, LOG_USER); } if (opts.chroot) { pt_log(kLog_info, "Restricting file access to %s\n", opts.root_dir); if (-1 == chdir(opts.root_dir) || -1 == chroot(opts.root_dir)) { pt_log(kLog_error, "%s: %s\n", opts.root_dir, strerror(errno)); exit(1); } } if (opts.daemonize) { pt_log(kLog_info, "Going to the background.\n"); if (0 < (pid = fork())) exit(0); if (0 > pid) pt_log(kLog_error, "fork: %s\n", strerror(errno)); else if (-1 == setsid()) pt_log(kLog_error, "setsid: %s\n", strerror(errno)); else { if (0 < (pid = fork())) exit(0); if (0 > pid) pt_log(kLog_error, "fork: %s\n", strerror(errno)); else { if (NULL != opts.pid_file) { fprintf(opts.pid_file, "%d\n", getpid()); fclose(opts.pid_file); } if (! freopen("/dev/null", "r", stdin) || ! freopen("/dev/null", "w", stdout) || ! freopen("/dev/null", "w", stderr)) pt_log(kLog_error, "freopen: %s\n", strerror(errno)); } } } #endif /* !WIN32 */ #ifdef WIN32 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return -1; } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup(); return -1; } #endif /* WIN32 */ pthread_mutex_init(&chain_lock, 0); pthread_mutex_init(&num_threads_lock, 0); // Check mode, validate arguments and start either client or proxy. if (opts.mode == kMode_forward) { if (!opts.given_proxy_ip || !opts.given_dst_ip || !opts.given_dst_port || !opts.tcp_listen_port) { printf("One of the options are missing or invalid.\n"); print_usage(argv[0]); return -1; } pt_forwarder(); } else pt_proxy(0); #ifdef WIN32 WSACleanup(); #else if (opts.root_dir) free(opts.root_dir); #ifdef HAVE_SELINUX if (NULL != opts.selinux_context) free(opts.selinux_context); #endif #endif /* WIN32 */ pt_log(kLog_info, "ptunnel is exiting.\n"); return 0; } /* pt_forwarder: Sets up a listening TCP socket, and forwards incoming connections over ping packets. */ void pt_forwarder(void) { int server_sock, new_sock, sock, yes = 1; fd_set set; struct timeval time; struct sockaddr_in addr, dest_addr; socklen_t addr_len; pthread_t pid; uint16_t rand_id; pt_log(kLog_debug, "Starting forwarder..\n"); // Open our listening socket sock = socket(AF_INET, SOCK_STREAM, 0); if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &yes, sizeof(int)) == -1) { pt_log(kLog_error, "Failed to set SO_REUSEADDR option on listening socket: %s\n", strerror(errno)); close(sock); return; } addr.sin_family = AF_INET; addr.sin_port = htons(opts.tcp_listen_port); addr.sin_addr.s_addr = INADDR_ANY; memset(&(addr.sin_zero), 0, 8); if (bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) == -1) { pt_log(kLog_error, "Failed to bind listening socket to port %u: %s\n", opts.tcp_listen_port, strerror(errno)); close(sock); return; } server_sock = sock; // Fill out address structure memset(&dest_addr, 0, sizeof(struct sockaddr_in)); dest_addr.sin_family = AF_INET; if (opts.udp) dest_addr.sin_port = htons(kDNS_port /* dns port.. */); else dest_addr.sin_port = 0; dest_addr.sin_addr.s_addr = opts.given_proxy_ip; pt_log(kLog_verbose, "Proxy IP address: %s\n", inet_ntoa(*((struct in_addr*)&opts.given_proxy_ip))); listen(server_sock, 10); while (1) { FD_ZERO(&set); FD_SET(server_sock, &set); time.tv_sec = 1; time.tv_usec = 0; if (select(server_sock+1, &set, 0, 0, &time) > 0) { pt_log(kLog_info, "Incoming connection.\n"); addr_len = sizeof(struct sockaddr_in); new_sock = accept(server_sock, (struct sockaddr*)&addr, &addr_len); if (new_sock < 0) { pt_log(kLog_error, "Accepting incoming connection failed.\n"); continue; } pthread_mutex_lock(&num_threads_lock); if (num_threads <= 0) { pt_log(kLog_event, "No running proxy thread - starting it.\n"); #ifndef WIN32 if (pthread_create(&pid, 0, pt_proxy, 0) != 0) #else if (0 == (pid = _beginthreadex(0, 0, (unsigned int (__stdcall *)(void *))pt_proxy, 0, 0, 0))) #endif { pt_log(kLog_error, "Couldn't create thread! Dropping incoming connection.\n"); close(new_sock); pthread_mutex_unlock(&num_threads_lock); continue; } } addr = dest_addr; rand_id = (uint16_t)rand(); create_and_insert_proxy_desc(rand_id, rand_id, new_sock, &addr, opts.given_dst_ip, opts.given_dst_port, kProxy_start, kUser_flag); pthread_mutex_unlock(&num_threads_lock); } } } int pt_create_udp_socket(int port) { struct sockaddr_in addr; int sock, yes = 1; sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { pt_log(kLog_error, "Failed to set create UDP socket..\n"); return 0; } if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void*)&yes, sizeof(int)) < 0) { pt_log(kLog_error, "Failed to set UDP REUSEADDR socket option. (Not fatal, hopefully.)\n"); close(sock); return 0; } #ifdef SO_REUSEPORT yes = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const void*)&yes, sizeof(int)) < 0) pt_log(kLog_error, "Failed to set UDP REUSEPORT socket option. (Not fatal, hopefully.)\n"); #endif //SO_REUSEPORT memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if (bind(sock, (struct sockaddr*) &addr, sizeof(struct sockaddr_in)) < 0) { pt_log(kLog_error, "Failed to bind UDP socket to port %d (try running as root).\n", port); close(sock); return 0; } return sock; } #define kPT_add_iphdr 0 /* pt_proxy: This function does all the client and proxy stuff. */ void* pt_proxy(void *args) { fd_set set; struct timeval timeout; int bytes; struct sockaddr_in addr; socklen_t addr_len; int fwd_sock = 0, max_sock = 0, idx; char *buf; double now, last_status_update = 0.0; proxy_desc_t *cur, *prev, *tmp; pcap_info_t pc; xfer_stats_t xfer; ip_packet_t *pkt; uint32_t ip; in_addr_t *adr; // Start the thread, initialize protocol and ring states. pt_log(kLog_debug, "Starting ping proxy..\n"); if (opts.udp) { pt_log(kLog_debug, "Creating UDP socket..\n"); if (opts.mode == kMode_proxy) fwd_sock = pt_create_udp_socket(kDNS_port); else fwd_sock = pt_create_udp_socket(0); if (!fwd_sock) { pt_log(kLog_error, "Failed to create UDP socket.\n"); return 0; } } else { if (opts.unprivileged) { pt_log(kLog_debug, "Attempting to create unprivileged ICMP datagram socket..\n"); fwd_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); } else { #if kPT_add_iphdr int opt = 1; #endif pt_log(kLog_debug, "Attempting to create privileged ICMP raw socket..\n"); #if kPT_add_iphdr // experimental fwd_sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP); printf("Set ip-hdr-inc; result = %d\n", setsockopt(fwd_sock, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt))); #else fwd_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); #endif } if (fwd_sock < 0) { pt_log(kLog_error, "Couldn't create %s socket: %s\n", (opts.unprivileged ? "unprivileged datagram" : "privileged raw"), strerror(errno)); return 0; } } max_sock = fwd_sock+1; if (opts.pcap) { if (opts.udp) { pt_log(kLog_error, "Packet capture is not useful with UDP [should not get here!]!\n"); close(fwd_sock); return 0; } if (!opts.unprivileged) { memset(&pc, 0, sizeof(pc)); pt_log(kLog_info, "Initializing pcap.\n"); pc.pcap_err_buf = malloc(PCAP_ERRBUF_SIZE); pc.pcap_data_buf = malloc(pcap_buf_size); pc.pcap_desc = pcap_open_live(opts.pcap_device, pcap_buf_size, 0 /* promiscous */, 50 /* ms */, pc.pcap_err_buf); if (pc.pcap_desc) { if (pcap_lookupnet(opts.pcap_device, &pc.netp, &pc.netmask, pc.pcap_err_buf) == -1) { pt_log(kLog_error, "pcap error: %s\n", pc.pcap_err_buf); opts.pcap = 0; } pt_log(kLog_verbose, "Network: %s\n", inet_ntoa(*(struct in_addr*)&pc.netp)); pt_log(kLog_verbose, "Netmask: %s\n", inet_ntoa(*(struct in_addr*)&pc.netmask)); if (pcap_compile(pc.pcap_desc, &pc.fp, pcap_filter_program, 0, pc.netp) == -1) { pt_log(kLog_error, "Failed to compile pcap filter program.\n"); pcap_close(pc.pcap_desc); opts.pcap = 0; } else if (pcap_setfilter(pc.pcap_desc, &pc.fp) == -1) { pt_log(kLog_error, "Failed to set pcap filter program.\n"); pcap_close(pc.pcap_desc); opts.pcap = 0; } } else { pt_log(kLog_error, "pcap error: %s\n", pc.pcap_err_buf); opts.pcap = 0; } pc.pkt_q.head = 0; pc.pkt_q.tail = 0; pc.pkt_q.elems = 0; // Check if we have succeeded, and free stuff if not if (!opts.pcap) { pt_log(kLog_error, "There were errors enabling pcap - pcap has been disabled.\n"); free(pc.pcap_err_buf); free(pc.pcap_data_buf); return 0; } } else pt_log(kLog_info, "pcap disabled since we're running in unprivileged mode.\n"); } pthread_mutex_lock(&num_threads_lock); num_threads++; pthread_mutex_unlock(&num_threads_lock); // Allocate icmp receive buffer buf = malloc(icmp_receive_buf_len); // Start forwarding :) pt_log(kLog_info, "Ping proxy is listening in %s mode.\n", (opts.unprivileged ? "unprivileged" : "privileged")); #ifndef WIN32 #ifdef HAVE_SELINUX if (opts.uid || opts.gid || opts.selinux_context) #else if (opts.uid || opts.gid) #endif pt_log(kLog_info, "Dropping privileges now.\n"); if (opts.gid && -1 == setgid(opts.gid)) pt_log(kLog_error, "setgid(%d): %s\n", opts.gid, strerror(errno)); if (opts.uid && -1 == setuid(opts.uid)) pt_log(kLog_error, "setuid(%d): %s\n", opts.uid, strerror(errno)); #ifdef HAVE_SELINUX if (NULL != opts.selinux_context && -1 == setcon(opts.selinux_context)) pt_log(kLog_error, "setcon(%s) failed: %s\n", opts.selinux_context, strerror(errno)); #endif #endif while (1) { FD_ZERO(&set); FD_SET(fwd_sock, &set); max_sock = fwd_sock+1; pthread_mutex_lock(&chain_lock); for (cur=chain;cur;cur=cur->next) { if (cur->sock) { FD_SET(cur->sock, &set); if (cur->sock >= max_sock) max_sock = cur->sock+1; } } pthread_mutex_unlock(&chain_lock); timeout.tv_sec = 0; timeout.tv_usec = 10000; select(max_sock, &set, 0, 0, &timeout); // Don't care about return val, since we need to check for new states anyway.. pthread_mutex_lock(&chain_lock); for (prev=0,cur=chain;cur && cur->sock;cur=tmp) { // Client: If we're starting up, send a message to the remote end saying so, // causing him to connect to our desired endpoint. if (cur->state == kProxy_start) { pt_log(kLog_verbose, "Sending proxy request.\n"); cur->last_ack = time_as_double(); queue_packet(fwd_sock, cur->pkt_type, 0, 0, cur->id_no, cur->id_no, &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, cur->dst_ip, cur->dst_port, cur->state | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); cur->xfer.icmp_out++; cur->state = kProto_data; } if (cur->should_remove) { pt_log(kLog_info, "\nSession statistics:\n"); print_statistics(&cur->xfer, 0); pt_log(kLog_info, "\n"); tmp = cur->next; remove_proxy_desc(cur, prev); continue; } // Only handle traffic if there is traffic on the socket, we have // room in our send window AND we either don't use a password, or // have been authenticated. if (FD_ISSET(cur->sock, &set) && cur->send_wait_ack < kPing_window_size && (!opts.password || cur->authenticated)) { bytes = recv(cur->sock, cur->buf, tcp_receive_buf_len, 0); if (bytes <= 0) { pt_log(kLog_info, "Connection closed or lost.\n"); tmp = cur->next; send_termination_msg(cur, fwd_sock); pt_log(kLog_info, "Session statistics:\n"); print_statistics(&cur->xfer, 0); remove_proxy_desc(cur, prev); // No need to update prev continue; } cur->xfer.bytes_out += bytes; cur->xfer.icmp_out++; queue_packet(fwd_sock, cur->pkt_type, cur->buf, bytes, cur->id_no, cur->icmp_id, &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, 0, 0, cur->state | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); } prev = cur; tmp = cur->next; } pthread_mutex_unlock(&chain_lock); if (FD_ISSET(fwd_sock, &set)) { // Handle ping traffic addr_len = sizeof(struct sockaddr); bytes = recvfrom(fwd_sock, buf, icmp_receive_buf_len, 0, (struct sockaddr*)&addr, &addr_len); if (bytes < 0) { pt_log(kLog_error, "Error receiving packet on ICMP socket: %s\n", strerror(errno)); break; } handle_packet(buf, bytes, 0, &addr, fwd_sock); } // Check for packets needing resend, and figure out if any connections // should be closed down due to inactivity. pthread_mutex_lock(&chain_lock); now = time_as_double(); for (cur=chain;cur;cur=cur->next) { if (cur->last_activity + kAutomatic_close_timeout < now) { pt_log(kLog_info, "Dropping tunnel to %s:%d due to inactivity.\n", inet_ntoa(*(struct in_addr*)&cur->dst_ip), cur->dst_port, cur->id_no); cur->should_remove = 1; continue; } if (cur->recv_wait_send && cur->sock) cur->xfer.bytes_in += send_packets(cur->recv_ring, &cur->recv_xfer_idx, &cur->recv_wait_send, &cur->sock); // Check for any icmp packets requiring resend, and resend _only_ the first packet. idx = cur->send_first_ack; if (cur->send_ring[idx].pkt && cur->send_ring[idx].last_resend+kResend_interval < now) { pt_log(kLog_debug, "Resending packet with seq-no %d.\n", cur->send_ring[idx].seq_no); cur->send_ring[idx].last_resend = now; cur->send_ring[idx].pkt->seq = htons(cur->ping_seq); cur->ping_seq++; cur->send_ring[idx].pkt->checksum = 0; cur->send_ring[idx].pkt->checksum = htons(calc_icmp_checksum((uint16_t*)cur->send_ring[idx].pkt, cur->send_ring[idx].pkt_len)); //printf("ID: %d\n", htons(cur->send_ring[idx].pkt->identifier)); sendto(fwd_sock, (const void*)cur->send_ring[idx].pkt, cur->send_ring[idx].pkt_len, 0, (struct sockaddr*)&cur->dest_addr, sizeof(struct sockaddr)); cur->xfer.icmp_resent++; } // Figure out if it's time to send an explicit acknowledgement if (cur->last_ack+1.0 < now && cur->send_wait_ack < kPing_window_size && cur->remote_ack_val+1 != cur->next_remote_seq) { cur->last_ack = now; queue_packet(fwd_sock, cur->pkt_type, 0, 0, cur->id_no, cur->icmp_id, &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, cur->dst_ip, cur->dst_port, kProto_ack | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); cur->xfer.icmp_ack_out++; } } pthread_mutex_unlock(&chain_lock); if (opts.pcap) { if (pcap_dispatch(pc.pcap_desc, 32, pcap_packet_handler, (u_char*)&pc.pkt_q) > 0) { pqueue_elem_t *cur; //pt_log(kLog_verbose, "pcap captured %d packets - handling them..\n", pc.pkt_q.elems); while (pc.pkt_q.head) { cur = pc.pkt_q.head; memset(&addr, 0, sizeof(struct sockaddr)); addr.sin_family = AF_INET; pkt = (ip_packet_t*)&cur->data[0]; ip = pkt->src_ip; adr = (in_addr_t*)&ip; addr.sin_addr.s_addr = *adr; handle_packet(cur->data, cur->bytes, 1, &addr, fwd_sock); pc.pkt_q.head = cur->next; free(cur); pc.pkt_q.elems--; } pc.pkt_q.tail = 0; pc.pkt_q.head = 0; } } // Update running statistics, if requested (only once every second) if (opts.print_stats && opts.mode == kMode_forward && now > last_status_update+1) { pthread_mutex_lock(&chain_lock); memset(&xfer, 0, sizeof(xfer_stats_t)); for (cur=chain;cur;cur=cur->next) { xfer.bytes_in += cur->xfer.bytes_in; xfer.bytes_out += cur->xfer.bytes_out; xfer.icmp_in += cur->xfer.icmp_in; xfer.icmp_out += cur->xfer.icmp_out; xfer.icmp_resent += cur->xfer.icmp_resent; } pthread_mutex_unlock(&chain_lock); print_statistics(&xfer, (opts.log_level >= kLog_verbose ? 0 : 1)); last_status_update = now; } } pt_log(kLog_debug, "Proxy exiting..\n"); if (fwd_sock) close(fwd_sock); // TODO: Clean up the other descs. Not really a priority since there's no // real way to quit ptunnel in the first place.. free(buf); pt_log(kLog_debug, "Ping proxy done\n"); return 0; } /* print_statistics: Prints transfer statistics for the given xfer block. The is_continuous variable controls the output mode, either printing a new line or overwriting the old line. */ void print_statistics(xfer_stats_t *xfer, int is_continuous) { const double mb = 1024.0*1024.0; double loss = 0.0; if (xfer->icmp_out > 0) loss = (double)xfer->icmp_resent/(double)xfer->icmp_out; if (is_continuous) printf("\r"); printf("[inf]: I/O: %6.2f/%6.2f mb ICMP I/O/R: %8d/%8d/%8d Loss: %4.1f%%", xfer->bytes_in/mb, xfer->bytes_out/mb, xfer->icmp_in, xfer->icmp_out, xfer->icmp_resent, loss); if (!is_continuous) printf("\n"); else fflush(stdout); } /* pcap_packet_handler: This is our callback function handling captured packets. We already know that the packets are ICMP echo or echo-reply messages, so all we need to do is strip off the ethernet header and append it to the queue descriptor (the refcon argument). Ok, the above isn't entirely correct (we can get other ICMP types as well). This function also has problems when it captures packets on the loopback interface. The moral of the story: Don't do ping forwarding over the loopback interface. Also, we currently don't support anything else than ethernet when in pcap mode. The reason is that I haven't read up on yet on how to remove the frame header from the packet.. */ void pcap_packet_handler(u_char *refcon, const struct pcap_pkthdr *hdr, const u_char* pkt) { pqueue_t *q; pqueue_elem_t *elem; ip_packet_t *ip; //pt_log(kLog_verbose, "Packet handler: %d =? %d\n", hdr->caplen, hdr->len); q = (pqueue_t*)refcon; elem = malloc(sizeof(pqueue_elem_t)+hdr->caplen-sizeof(struct ether_header)); memcpy(elem->data, pkt+sizeof(struct ether_header), hdr->caplen-sizeof(struct ether_header)); ip = (ip_packet_t*)elem->data; // TODO: Add fragment support elem->bytes = ntohs(ip->pkt_len); if (elem->bytes > hdr->caplen-sizeof(struct ether_header)) { pt_log(kLog_error, "Received fragmented packet - unable to reconstruct!\n"); pt_log(kLog_error, "This error usually occurs because pcap is used on devices that are not wlan or ethernet.\n"); free(elem); return; } //elem->bytes = hdr->caplen-sizeof(struct ether_header); elem->next = 0; if (q->tail) { q->tail->next = elem; q->tail = elem; } else { q->head = elem; q->tail = elem; } q->elems++; } #if kPT_add_iphdr static int ip_id_counter = 1; #endif /* queue_packet: Creates an ICMP packet descriptor, and sends it. The packet descriptor is added to the given send ring, for potential resends later on. */ int queue_packet(int icmp_sock, uint8_t type, char *buf, int num_bytes, uint16_t id_no, uint16_t icmp_id, uint16_t *seq, icmp_desc_t ring[], int *insert_idx, int *await_send, uint32_t ip, uint32_t port, uint32_t state, struct sockaddr_in *dest_addr, uint16_t next_expected_seq, int *first_ack, uint16_t *ping_seq) { #if kPT_add_iphdr ip_packet_t *ip_pkt = 0; int pkt_len = sizeof(ip_packet_t)+sizeof(icmp_echo_packet_t)+sizeof(ping_tunnel_pkt_t)+num_bytes, #else int pkt_len = sizeof(icmp_echo_packet_t)+sizeof(ping_tunnel_pkt_t)+num_bytes, #endif err = 0; icmp_echo_packet_t *pkt = 0; ping_tunnel_pkt_t *pt_pkt = 0; uint16_t ack_val = next_expected_seq-1; if (pkt_len % 2) pkt_len++; #if kPT_add_iphdr printf("add header\n"); ip_pkt = malloc(pkt_len); pkt = (icmp_echo_packet_t*)ip_pkt->data; memset(ip_pkt, 0, sizeof(ip_packet_t)); ip_pkt->vers_ihl = 0x45;//|(pkt_len>>2);//5;//(IPVERSION << 4) | (sizeof(ip_packet_t) >> 2); ip_pkt->tos = IPTOS_LOWDELAY; ip_pkt->pkt_len = pkt_len; ip_pkt->id = 0; //kernel sets proper value htons(ip_id_counter); ip_pkt->flags_frag_offset = 0; ip_pkt->ttl = IPDEFTTL; // default time to live (64) ip_pkt->proto = 1; // ICMP ip_pkt->checksum = 0; // maybe the kernel helps us out..? ip_pkt->src_ip = htonl(0x0); // insert source IP address here ip_pkt->dst_ip = dest_addr->sin_addr.s_addr;//htonl(0x7f000001); // localhost.. #else pkt = calloc(1, pkt_len); #endif pkt->type = type; // ICMP Echo request or reply pkt->code = 0; // Must be zero (non-zero requires root) pkt->identifier = htons(icmp_id); pkt->seq = htons(*ping_seq); pkt->checksum = 0; (*ping_seq)++; // Add our information pt_pkt = (ping_tunnel_pkt_t*)pkt->data; pt_pkt->magic = htonl(opts.magic); pt_pkt->dst_ip = ip; pt_pkt->dst_port = htonl(port); pt_pkt->ack = htonl(ack_val); pt_pkt->data_len = htonl(num_bytes); pt_pkt->state = htonl(state); pt_pkt->seq_no = htons(*seq); pt_pkt->id_no = htons(id_no); // Copy user data if (buf && num_bytes > 0) memcpy(pt_pkt->data, buf, num_bytes); #if kPT_add_iphdr pkt->checksum = htons(calc_icmp_checksum((uint16_t*)pkt, pkt_len-sizeof(ip_packet_t))); ip_pkt->checksum = htons(calc_icmp_checksum((uint16_t*)ip_pkt, sizeof(ip_packet_t))); #else pkt->checksum = htons(calc_icmp_checksum((uint16_t*)pkt, pkt_len)); #endif // Send it! pt_log(kLog_sendrecv, "Send: %d [%d] bytes [seq = %d] [type = %s] [ack = %d] [icmp = %d] [user = %s]\n", pkt_len, num_bytes, *seq, state_name[state & (~kFlag_mask)], ack_val, type, ((state & kUser_flag) == kUser_flag ? "yes" : "no")); #if kPT_add_iphdr err = sendto(icmp_sock, (const void*)ip_pkt, pkt_len, 0, (struct sockaddr*)dest_addr, sizeof(struct sockaddr)); #else err = sendto(icmp_sock, (const void*)pkt, pkt_len, 0, (struct sockaddr*)dest_addr, sizeof(struct sockaddr)); #endif if (err < 0) { pt_log(kLog_error, "Failed to send ICMP packet: %s\n", strerror(errno)); return -1; } else if (err != pkt_len) pt_log(kLog_error, "WARNING WARNING, didn't send entire packet\n"); // Update sequence no's and so on #if kPT_add_iphdr // NOTE: Retry mechanism needs update for PT_add_ip_hdr ring[*insert_idx].pkt = ip_pkt; #else ring[*insert_idx].pkt = pkt; #endif ring[*insert_idx].pkt_len = pkt_len; ring[*insert_idx].last_resend = time_as_double(); ring[*insert_idx].seq_no = *seq; ring[*insert_idx].icmp_id = icmp_id; (*seq)++; if (!ring[*first_ack].pkt) *first_ack = *insert_idx; (*await_send)++; (*insert_idx)++; if (*insert_idx >= kPing_window_size) *insert_idx = 0; return 0; } /* send_packets: Examines the passed-in ring, and forwards data in it over TCP. */ uint32_t send_packets(forward_desc_t *ring[], int *xfer_idx, int *await_send, int *sock) { forward_desc_t *fwd_desc; int bytes, total = 0; while (*await_send > 0) { fwd_desc = ring[*xfer_idx]; if (!fwd_desc) // We haven't got this packet yet.. break; if (fwd_desc->length > 0) { bytes = send(*sock, &fwd_desc->data[fwd_desc->length - fwd_desc->remaining], fwd_desc->remaining, 0); if (bytes < 0) { printf("Weirdness.\n"); // TODO: send close stuff close(*sock); *sock = 0; break; } fwd_desc->remaining -= bytes; total += bytes; } if (!fwd_desc->remaining) { ring[*xfer_idx] = 0; free(fwd_desc); (*xfer_idx)++; (*await_send)--; if (*xfer_idx >= kPing_window_size) *xfer_idx = 0; } else break; } return total; } /* handle_data: Utility function for handling kProto_data packets, and place the data it contains onto the passed-in receive ring. */ void handle_data(icmp_echo_packet_t *pkt, int total_len, forward_desc_t *ring[], int *await_send, int *insert_idx, uint16_t *next_expected_seq) { ping_tunnel_pkt_t *pt_pkt = (ping_tunnel_pkt_t*)pkt->data; int expected_len = sizeof(ip_packet_t) + sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t); // 20+8+28 /* Place packet in the receive ring, in its proper place. This works as follows: -1. Packet == ack packet? Perform ack, and continue. 0. seq_no < next_remote_seq, and absolute difference is bigger than w size => discard 1. If seq_no == next_remote_seq, we have no problems; just put it in the ring. 2. If seq_no > next_remote_seq + remaining window size, discard packet. Send resend request for missing packets. 3. Else, put packet in the proper place in the ring (don't overwrite if one is already there), but don't increment next_remote_seq_no 4. If packed was not discarded, process ack info in packet. */ expected_len += pt_pkt->data_len; expected_len += expected_len % 2; if (opts.udp) expected_len -= sizeof(ip_packet_t); if (total_len < expected_len) { pt_log(kLog_error, "Packet not completely received: %d Should be: %d. For some reason, this error is fatal.\n", total_len, expected_len); pt_log(kLog_debug, "Data length: %d Total length: %d\n", pt_pkt->data_len, total_len); // TODO: This error isn't fatal, so it should definitely be handled in some way. We could simply discard it. exit(0); } if (pt_pkt->seq_no == *next_expected_seq) { // hmm, what happens if this test is true? if (!ring[*insert_idx]) { // && pt_pkt->state == kProto_data // pt_log(kLog_debug, "Queing data packet: %d\n", pt_pkt->seq_no); ring[*insert_idx] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data); (*await_send)++; (*insert_idx)++; } else if (ring[*insert_idx]) pt_log(kLog_debug, "Dup packet?\n"); (*next_expected_seq)++; if (*insert_idx >= kPing_window_size) *insert_idx = 0; // Check if we have already received some of the next packets while (ring[*insert_idx]) { if (ring[*insert_idx]->seq_no == *next_expected_seq) { (*next_expected_seq)++; (*insert_idx)++; if (*insert_idx >= kPing_window_size) *insert_idx = 0; } else break; } } else { int r, s, d, pos; pos = -1; // If pos ends up staying -1, packet is discarded. r = *next_expected_seq; s = pt_pkt->seq_no; d = s - r; if (d < 0) { // This packet _may_ be old, or seq_no may have wrapped around d = (s+0xFFFF) - r; if (d < kPing_window_size) { // Counter has wrapped, so we should add this packet to the recv ring pos = ((*insert_idx)+d) % kPing_window_size; } } else if (d < kPing_window_size) pos = ((*insert_idx)+d) % kPing_window_size; if (pos != -1) { if (!ring[pos]) { pt_log(kLog_verbose, "Out of order. Expected: %d Got: %d Inserted: %d (cur = %d)\n", *next_expected_seq, pt_pkt->seq_no, pos, (*insert_idx)); ring[pos] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data); (*await_send)++; } } //else // pt_log(kLog_debug, "Packet discarded - outside receive window.\n"); } } void handle_ack(uint16_t seq_no, icmp_desc_t ring[], int *packets_awaiting_ack, int one_ack_only, int insert_idx, int *first_ack, uint16_t *remote_ack, int is_pcap) { int i, j, k; ping_tunnel_pkt_t *pt_pkt; if (*packets_awaiting_ack > 0) { if (one_ack_only) { for (i=0;idata; *remote_ack = (uint16_t)ntohl(pt_pkt->ack); // WARNING: We make the dangerous assumption here that packets arrive in order! free(ring[i].pkt); ring[i].pkt = 0; (*packets_awaiting_ack)--; if (i == *first_ack) { for (j=1;j> 16); sum = htons(0xFFFF - sum); return sum; } /* send_termination_msg: Sends two packets to the remote end, informing it that the tunnel is being closed down. */ void send_termination_msg(proxy_desc_t *cur, int icmp_sock) { // Send packet twice, hoping at least one of them makes it through.. queue_packet(icmp_sock, cur->pkt_type, 0, 0, cur->id_no, cur->icmp_id, &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, 0, 0, kProto_close | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); queue_packet(icmp_sock, cur->pkt_type, 0, 0, cur->id_no, cur->icmp_id, &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, 0, 0, kProto_close | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); cur->xfer.icmp_out += 2; }