/* Name: slitheen-proxy.c * * Slitheen - a decoy routing system for censorship resistance * Copyright (C) 2017 Cecylia Bocovich (cbocovic@uwaterloo.ca) * * 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, version 3. * * 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 . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with the OpenSSL library (or a modified version of that library), * containing parts covered by the terms of the OpenSSL Licence and the * SSLeay license, the licensors of this Program grant you additional * permission to convey the resulting work. Corresponding Source for a * non-source form of such a combination shall include the source code * for the parts of the OpenSSL library used as well as that of the covered * work. */ #include #include #include #include #include #include #include #include #include #include "util.h" #include "flow.h" #include "slitheen.h" #include "relay.h" #include "crypto.h" #include "cryptothread.h" void save_packet(flow *f, struct packet_info *info); void update_window_expiration(flow *f, struct packet_info *info); void retransmit(flow *f, struct packet_info *info, uint32_t data_to_fill); void usage(void){ printf("Usage: slitheen-proxy [internal network interface] [NAT interface]\n"); } int main(int argc, char *argv[]){ pthread_t t1, t2; char *dev1 = NULL; /* Device that leads to the internal network */ char *dev2 = NULL; /* Device that leads out to the world */ struct sniff_args outbound; struct sniff_args inbound; if (argc != 3) { usage(); return(2); } dev1 = argv[1]; dev2 = argv[2]; if(init_tables()){ exit(1); } if(init_session_cache()){ exit(1); } init_crypto_locks(); /* Create threads */ outbound.readdev = dev1; outbound.writedev = dev2; inbound.readdev = dev2; inbound.writedev = dev1; pthread_create(&t1, NULL, sniff_packets, (void *) &outbound); pthread_create(&t2, NULL, sniff_packets, (void *) &inbound); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_exit(NULL); crypto_locks_cleanup(); return(0); } void *sniff_packets(void *args){ pcap_t *rd_handle; pcap_t *wr_handle; char rd_errbuf[BUFSIZ]; char wr_errbuf[BUFSIZ]; uint8_t MAC[ETHER_ADDR_LEN]; bpf_u_int32 mask; bpf_u_int32 net; char *readdev, *writedev; struct sniff_args *arg_st = (struct sniff_args *) args; readdev = arg_st->readdev; writedev = arg_st->writedev; //Find MAC address of each interface struct ifreq ifr; int s = socket(AF_INET, SOCK_DGRAM, 0); strcpy(ifr.ifr_name, writedev); ioctl(s, SIOCGIFHWADDR, &ifr); memcpy(MAC, ifr.ifr_hwaddr.sa_data, ETHER_ADDR_LEN); close(s); if (pcap_lookupnet(readdev, &net, &mask, rd_errbuf) == -1){ fprintf(stderr, "Can't get netmask for device %s\n", readdev); exit(2); } rd_handle = pcap_open_live(readdev, BUFSIZ, 0, 0, rd_errbuf); if (rd_handle == NULL){ fprintf(stderr, "Couldn't open device %s: %s\n", readdev, rd_errbuf); } if(pcap_datalink(rd_handle) != DLT_EN10MB) { fprintf(stderr, "Device %s does not provide Ethernet headers - not supported\n", readdev); exit(2); } if(pcap_setdirection(rd_handle, PCAP_D_IN)){ fprintf(stderr, "Platform does not support write direction. Update filters with MAC address\n"); exit(2); } wr_handle = pcap_open_live(writedev, BUFSIZ, 0, 0, wr_errbuf); if (wr_handle == NULL){ fprintf(stderr, "Couldn't open device %s: %s\n", writedev, wr_errbuf); } struct inject_args iargs; iargs.mac_addr = MAC; iargs.write_dev = wr_handle; /*callback function*/ pcap_loop(rd_handle, -1, got_packet, (unsigned char *) &iargs); /*Sniff a packet*/ pcap_close(rd_handle); return NULL; } /* * Injects a packet back out the opposite interface */ void inject_packet(struct inject_args *iargs, const struct pcap_pkthdr *header, uint8_t *packet){ pcap_t *handle = iargs->write_dev; //write back out to the MAC ADDR it came in on //memmove(packet, packet+ETHER_ADDR_LEN, ETHER_ADDR_LEN); //memcpy(packet+ETHER_ADDR_LEN, iargs->mac_addr, ETHER_ADDR_LEN); if((pcap_inject(handle, packet, header->len)) < 0 ){ fprintf(stderr, "Error: %s\n", pcap_geterr(handle)); printf("Length: %d\n", header->len); } #ifdef DEBUG fprintf(stderr, "injected the following packet:\n"); for(int i=0; i< header->len; i++){ fprintf(stderr, "%02x ", packet[i]); } fprintf(stderr, "\n"); #endif free(packet); } /** * Runs when pcap_loop receives a packet from the specified interface * If the received packet is a tcp packet, processes it and then writes it back out * to the interface * */ void got_packet(uint8_t *args, const struct pcap_pkthdr *header, const uint8_t *packet){ struct inject_args *iargs = (struct inject_args *) args; uint8_t *tmp_packet = emalloc(header->len); memcpy(tmp_packet, packet, header->len); process_packet(iargs, header, tmp_packet); } /* This function receives a full ip packet and then: * 1) identifies the flow * 2) adds the packet to the flow's data chain * 3) updates the flow's state */ void process_packet(struct inject_args *iargs, const struct pcap_pkthdr *header, uint8_t *packet){ struct packet_info *info = emalloc(sizeof(struct packet_info)); extract_packet_headers(packet, info); //Ignore non-TCP packets (shouldn't actually get any) if((info->ip_hdr == NULL) || (info->tcp_hdr == NULL)){ //free(info); //free(packet); //return; goto err; } /* Checks to see if this is a possibly tagged hello msg */ if ((info->record_hdr != NULL) && (info->record_hdr->type == HS)){ /* This is a TLS handshake */ check_handshake(info); } /* Now if flow is in table, update state */ flow *observed; if((observed = check_flow(info)) != NULL){ #ifdef DEBUG /*Check sequence number and replay application data if necessary*/ fprintf(stdout,"Flow: %x:%d > %x:%d (%s)\n", info->ip_hdr->src.s_addr, ntohs(info->tcp_hdr->src_port), info->ip_hdr->dst.s_addr, ntohs(info->tcp_hdr->dst_port), (info->ip_hdr->src.s_addr != observed->src_ip.s_addr)? "incoming":"outgoing"); fprintf(stdout,"ID number: %u\n", htonl(info->ip_hdr->id)); fprintf(stdout,"Sequence number: %u\n", htonl(info->tcp_hdr->sequence_num)); fprintf(stdout,"Acknowledgement number: %u\n", htonl(info->tcp_hdr->ack_num)); #endif uint8_t incoming = (info->ip_hdr->src.s_addr != observed->src_ip.s_addr)? 1 : 0; uint32_t seq_num = htonl(info->tcp_hdr->sequence_num); uint32_t expected_seq = (incoming)? observed->downstream_seq_num : observed->upstream_seq_num; #ifdef DEBUG fprintf(stdout,"Expected sequence number: %u\n", expected_seq); #endif /* Remove acknowledged data from queue after TCP window is exceeded */ update_window_expiration(observed, info); /* fill with retransmit data, process new data */ uint32_t data_to_fill; uint32_t data_to_process; if(seq_num > expected_seq){ data_to_process = info->app_data_len; data_to_fill = 0; } else if (seq_num + info->app_data_len > expected_seq){ data_to_fill = expected_seq - seq_num; data_to_process = seq_num + info->app_data_len - expected_seq; } else { data_to_fill = info->app_data_len; data_to_process = 0; } uint8_t *p = info->app_data; if(data_to_fill){ //retransmit printf("Retransmiting data (%u:%u)\n", seq_num, seq_num + info->app_data_len); retransmit(observed, info, data_to_fill); } p += data_to_fill; if(data_to_process){ if(p != info->app_data){ printf("UH OH something weird might happen\n"); } if(observed->application){ if(seq_num > expected_seq){ //For now, enters into FORFEIT state //TODO: change upstream behaviour to try to mask slitheen hdr //printf("ERROR: future packet in app data, forfeiting flow\n"); remove_flow(observed); goto err; } replace_packet(observed, info); } else { //We're still in the TLS handshake; hold packets misordered packets if(seq_num > expected_seq){ //Delay and process later frame *new_frame = ecalloc(1, sizeof(frame)); new_frame->iargs = iargs; new_frame->packet = packet; new_frame->header = header; new_frame->seq_num = seq_num; new_frame->next = NULL; frame_queue *queue = (incoming) ? observed->ds_frame_queue : observed->us_frame_queue; printf("Delay processing of frame (seq = %u )\n", seq_num); //add to end of list if(queue->first_frame == NULL){ queue->first_frame = new_frame; } else { frame *last = queue->first_frame; while(last->next != NULL){ last = last->next; } last->next = new_frame; } free(info); observed->ref_ctr--; printf("Misordered packet. %p ref_ctr %d\n", observed, observed->ref_ctr); return; //TODO: fix terrible spaghetti returns } /* Pass data to packet chain */ if(observed->stall){ } if(add_packet(observed, info)){//removed_flow goto err; } } /* Update TCP state */ if(info->tcp_hdr->flags & (FIN | RST) ){ /* Remove flow from table, connection ended */ remove_flow(observed); goto err; } /* add packet to application data queue */ save_packet(observed, info); } /*process and release held frames with current sequence numbers*/ frame_queue *queue = (incoming) ? observed->ds_frame_queue : observed->us_frame_queue; frame *first = queue->first_frame; frame *prev = queue->first_frame; expected_seq = (incoming)? observed->downstream_seq_num : observed->upstream_seq_num; while (first != NULL){ if(first->seq_num <= expected_seq){ //remove from queue and process if(first == queue->first_frame) { queue->first_frame = first->next; } else { prev->next = first->next; } printf("Now processing frame (seq = %u )\n", first->seq_num); process_packet(iargs, first->header, first->packet); free(first); first = queue->first_frame; prev = queue->first_frame; } else { prev = first; first = first->next; } } observed->ref_ctr--; printf("Finished processing packet. %p ref_ctr %d\n", observed, observed->ref_ctr); } err: free(info);//Note: don't free this while a thread is using it inject_packet(iargs, header, packet); return; } //TODO: rewrite this function to remove bloat void save_packet(flow *f, struct packet_info *info){ uint8_t incoming = (info->ip_hdr->src.s_addr != f->src_ip.s_addr)? 1 : 0; uint32_t seq_num = htonl(info->tcp_hdr->sequence_num); //add new app block packet *new_block = ecalloc(1, sizeof(packet)); new_block->seq_num = htonl(info->tcp_hdr->sequence_num); new_block->data = ecalloc(1, info->app_data_len); memcpy(new_block->data, info->app_data, info->app_data_len); new_block->len = info->app_data_len; new_block->next = NULL; new_block->expiration = 0; packet *saved_data = (incoming)? f->downstream_app_data->first_packet : f->upstream_app_data->first_packet; //put app data block in queue if(saved_data == NULL){ if(incoming){ f->downstream_app_data->first_packet = new_block; if(new_block->seq_num == f->downstream_seq_num){ f->downstream_seq_num += new_block->len; #ifdef DEBUG printf("Updated downstream expected seqnum to %u\n", f->downstream_seq_num ); #endif } } else { f->upstream_app_data->first_packet = new_block; if(new_block->seq_num == f->upstream_seq_num){ f->upstream_seq_num += new_block->len; #ifdef DEBUG printf("Updated upstream expected seqnum to %u\n", f->upstream_seq_num ); #endif } } } else { uint8_t saved = 0; while(saved_data->next != NULL){ if(!saved && (saved_data->next->seq_num > seq_num)){ new_block->next = saved_data->next; saved_data->next = new_block; saved = 1; } //update expected sequence number if(incoming){ if(saved_data->next->seq_num == f->downstream_seq_num){ f->downstream_seq_num += saved_data->next->len; #ifdef DEBUG printf("Updated downstream expected seqnum to %u\n", f->downstream_seq_num ); #endif } } else {//outgoing if(saved_data->next->seq_num == f->upstream_seq_num){ f->upstream_seq_num += saved_data->next->len; #ifdef DEBUG printf("Updated upstream expected seqnum to %u\n", f->upstream_seq_num ); #endif } } saved_data = saved_data->next; } if(!saved){ saved_data->next = new_block; //update expected sequence number if(incoming){ if(saved_data->next->seq_num == f->downstream_seq_num){ f->downstream_seq_num += saved_data->next->len; #ifdef DEBUG printf("Updated downstream expected seqnum to %u\n", f->downstream_seq_num ); #endif } } else {//outgoing if(saved_data->next->seq_num == f->upstream_seq_num){ f->upstream_seq_num += saved_data->next->len; #ifdef DEBUG printf("Updated upstream expected seqnum to %u\n", f->upstream_seq_num ); #endif } } } } } /** * This function cleans up data that has been acked, after the TCP window of the recipient has been * exceeded. This ensures that a retransmisson of the data will no longer occur. * * Sets the expiration for recent data base on the TCP window */ void update_window_expiration(flow *f, struct packet_info *info){ uint8_t incoming = (info->ip_hdr->src.s_addr != f->src_ip.s_addr)? 1 : 0; uint32_t ack_num = htonl(info->tcp_hdr->ack_num); uint32_t end_seq = htonl(info->tcp_hdr->sequence_num) + info->app_data_len - 1; uint32_t window = ack_num + htons(info->tcp_hdr->win_size); #ifdef DEBUG printf("Received sequence number %u\n", htonl(info->tcp_hdr->sequence_num)); printf("Acknowledged up to %u with window expiring at %u\n", ack_num, window); printf("Removing all packets up to %u\n", end_seq); #endif packet *saved_data = (incoming)? f->downstream_app_data->first_packet : f->upstream_app_data->first_packet; while((saved_data != NULL) && (saved_data->expiration != 0) && (end_seq > saved_data->expiration)){ //remove entire block if(incoming){ f->downstream_app_data->first_packet = saved_data->next; } else { f->upstream_app_data->first_packet = saved_data->next; } free(saved_data->data); free(saved_data); saved_data = (incoming)? f->downstream_app_data->first_packet : f->upstream_app_data->first_packet; #ifdef DEBUG if(saved_data != NULL){ printf("Currently saved seq_num is now %u\n", saved_data->seq_num); } else { printf("Acked all data, queue is empty\n"); } #endif } /* Update expiration for packets based on TCP window size */ saved_data = (incoming)? f->upstream_app_data->first_packet : f->downstream_app_data->first_packet; while((saved_data != NULL) && (ack_num > saved_data->seq_num)){ //update window if(ack_num >= saved_data->seq_num + saved_data->len){ //remove entire block saved_data->expiration = window; } saved_data = saved_data->next; } } /** * This function retransmits previously sent (and possibly modified) data * */ void retransmit(flow *f, struct packet_info *info, uint32_t data_to_fill){ uint8_t *p = info->app_data; uint32_t seq_num = htonl(info->tcp_hdr->sequence_num); uint8_t incoming = (info->ip_hdr->src.s_addr != f->src_ip.s_addr)? 1 : 0; packet *saved_data = (incoming)? f->downstream_app_data->first_packet : f->upstream_app_data->first_packet; while(data_to_fill > 0){ if(saved_data == NULL){ //have already acked all data p += data_to_fill; seq_num += data_to_fill; data_to_fill -= data_to_fill; continue; } if(seq_num < saved_data->seq_num){ //we are missing a block. Use what was given if(saved_data->seq_num - seq_num > data_to_fill){ //skip the rest p += data_to_fill; seq_num += data_to_fill; data_to_fill -= data_to_fill; } else { p += saved_data->seq_num - seq_num; data_to_fill -= saved_data->seq_num - seq_num; seq_num += saved_data->seq_num - seq_num; } } else if ( seq_num == saved_data->seq_num) { if(data_to_fill >= saved_data->len){ //exhaust this block and move onto next one memcpy(p, saved_data->data, saved_data->len); p += saved_data->len; seq_num += saved_data->len; data_to_fill -= saved_data->len; saved_data = saved_data->next; } else { //fill with partial block memcpy(p, saved_data->data, data_to_fill); p += data_to_fill; seq_num += data_to_fill; data_to_fill -= data_to_fill; } } else { //seq_num > saved_data->seq_num uint32_t offset = seq_num - saved_data->seq_num; if(offset > saved_data->len){ saved_data = saved_data->next; offset -= saved_data->len; } else { if(data_to_fill > saved_data->len - offset){ memcpy(p, saved_data->data + offset, saved_data->len - offset); p += saved_data->len - offset; seq_num += saved_data->len - offset; data_to_fill -= saved_data->len - offset; saved_data = saved_data->next; } else { memcpy(p, saved_data->data + offset, data_to_fill); p += data_to_fill; seq_num += data_to_fill; data_to_fill -= data_to_fill; } } } } tcp_checksum(info);//update checksum } /** This function extracts the ip, tcp, and tls record headers * from a received packet (if they exist), and put them in * a packet_info struct * */ void extract_packet_headers(uint8_t *packet, struct packet_info *info){ /* First fill in IP header */ uint8_t *p = packet; p += ETHER_HEADER_LEN; //skip ethernet header info->ip_hdr = (struct ip_header*) p; info->size_ip_hdr = IP_HEADER_LEN(info->ip_hdr); /* Verify this is an IP packet */ if( (info->ip_hdr->versionihl >>4) != 4){ info->ip_hdr = NULL; info->size_ip_hdr = 0; info->tcp_hdr = NULL; info->size_tcp_hdr = 0; info->record_hdr = NULL; return; } /* If this is a TCP segment, fill in TCP header */ if (info->ip_hdr->proto == IPPROTO_TCP){ p += info->size_ip_hdr; //skip IP header info->tcp_hdr = (struct tcp_header*) p; info->size_tcp_hdr = TCP_HEADER_LEN(info->tcp_hdr); p += info->size_tcp_hdr; } else { info->tcp_hdr = NULL; info->size_tcp_hdr = 0; info->record_hdr = NULL; return; } /* If the application data contains a TLS record, fill in hdr */ info->app_data_len = htons(info->ip_hdr->len) - (info->size_ip_hdr + info->size_tcp_hdr); if(info->app_data_len > 0){ info->app_data = p; info->record_hdr = (struct tls_header*) p; //check to see if this is a valid record if((info->record_hdr->type < 0x14) || (info->record_hdr->type > 0x18)){ info->record_hdr = NULL; } } else { info->record_hdr = NULL; info->app_data = NULL; } return; } /** Copies a packet_info structure and returns a pointer to the duplicate. */ struct packet_info *copy_packet_info(struct packet_info *src_info){ struct packet_info *dst_info = emalloc(sizeof(struct packet_info)); dst_info->ip_hdr = src_info->ip_hdr; dst_info->tcp_hdr = src_info->tcp_hdr; dst_info->size_tcp_hdr = src_info->size_tcp_hdr; dst_info->size_ip_hdr = src_info->size_ip_hdr; dst_info->app_data = src_info->app_data; dst_info->app_data_len = src_info->app_data_len; return dst_info; }