#include "util.h" #include "rlocs.h" // We use a TUN device right now so we don't have to care about layer 2 headers // or complicated, hard scaling stuff. This isn't likely to scale very well. #include #include #include #include #include #include // We use writev() to send the packet, so we don't have to copy the // unencrypted part. #include typedef struct wrapper { struct rlocs *rlocs; int listen_if; int output_if; int same_if; } wrapper; struct recv_pkt { union { #ifdef __USE_BSD struct ip ip; #else struct iphdr ip; #endif struct ip6_hdr ip6; } hdr; char payload[IP_MAXPACKET]; /* payload is this - header size, but OK */ }; // It's all our code that uses this. 12 is more than we probably need to // construct a wrapped packet - just being careful. // // initial usage: // 0 - wrapping ip header, including enc_size // 1 - encrypted portion of payload, in scratch. // 2 - unencrypted portion of payload, in recv_pkt #define MAX_IOVS 12 struct rsp_data { int count; struct iovec iovs[MAX_IOVS]; unsigned char scratch[IP_MAXPACKET]; // somewhere easy to put results }; int wrap_ipv4_packet(struct rlocs* reg, struct recv_pkt* pkt, struct rsp_data* out) { out->count = 3; assert( out->count < MAX_IOVS ); unsigned char *scratch = &out->scratch[0]; // iovec 0: wrapping header struct iphdr* wrap_hdr = (struct iphdr*) scratch; unsigned int wrap_hdr_size = sizeof( struct iphdr ); scratch += wrap_hdr_size; memset( wrap_hdr, 0, wrap_hdr_size ); wrap_hdr->version = 0x04; wrap_hdr->ihl = wrap_hdr_size / 4; wrap_hdr->ttl = IPDEFTTL; wrap_hdr->protocol = IPPROTO_HIDE_EID; out->iovs[0].iov_base = wrap_hdr; out->iovs[0].iov_len = wrap_hdr_size; // TODO: id, still needs filling now. // We need to know source and destination rlocs to construct the packet struct rloc* s_rloc; struct rloc* d_rloc; struct in_addr tmp; // TODO: check endianness of saddr/daddr tmp.s_addr = pkt->hdr.ip.saddr; if ( ( s_rloc = rloc_find_for_ipv4( reg, &tmp ) ) == NULL ) { warn( "Couldn't find source rloc, dropping packet" ); // TODO: fallback behaviour here? return 0; } tmp.s_addr = pkt->hdr.ip.daddr; if ( ( d_rloc = rloc_find_for_ipv4( reg, &tmp ) ) == NULL ) { warn( "Couldn't find destination rloc, dropping packet" ); // TODO: fallback behaviour here? return 0; } wrap_hdr->saddr = s_rloc->addr.ip4.s_addr; wrap_hdr->daddr = d_rloc->addr.ip4.s_addr; // iovec 1: encrypted part. // FIXME: Need to inspect the protocol field and gobble up the TCP/UDP/etc // header as well, for decent anonymity. TCP/UDP ports are an obvious way // to perform a correlation attack. // RSA pubkey encryption with 4096-bit keys gobbles up at least 512 bytes // of space, so we make sure to use it. ssize_t enc_size; size_t orig_data_size = ntohs( pkt->hdr.ip.tot_len ); size_t bytes_to_encrypt; if ( orig_data_size > 512 ) { bytes_to_encrypt = pkt->hdr.ip.ihl * 4; } else { bytes_to_encrypt = orig_data_size; } off_t enc_max_len = IP_MAXPACKET - wrap_hdr_size - orig_data_size - bytes_to_encrypt; // We use two bytes to store the size of the encrypted blob unsigned short *pkt_enc_size = (unsigned short *) scratch; scratch += 2; enc_size = rloc_encrypt( d_rloc, (unsigned char *)&pkt->hdr, bytes_to_encrypt, scratch, enc_max_len - 2 ); if ( enc_size < 0 ) { warn( "failed to encrypt, dropping packet" ); return 0; } *pkt_enc_size = htons( enc_size ); enc_size += 2; scratch = (unsigned char*) pkt_enc_size; warn( "Encrypted size: 2 + %zu", enc_size - 2); out->iovs[1].iov_base = scratch; out->iovs[1].iov_len = enc_size; scratch += enc_size; // iovec 2: unencrypted remains if ( bytes_to_encrypt == orig_data_size ) { out->count = 2; out->iovs[2].iov_base = NULL; out->iovs[2].iov_len = 0; } else { out->iovs[2].iov_base = (char *) pkt + bytes_to_encrypt; out->iovs[2].iov_len = ntohs( pkt->hdr.ip.tot_len ) - bytes_to_encrypt; } wrap_hdr->tot_len = htons( wrap_hdr_size + enc_size + out->iovs[2].iov_len ); compute_ip_checksum( wrap_hdr ); info( "Finished building return packet" ); return 1; } int wrap_ipv6_packet(struct rlocs *reg, struct recv_pkt* pkt, struct rsp_data* out) { struct ip6_hdr wrap_hdr; memset( &wrap_hdr, 0, sizeof( struct iphdr ) ); return -1; } /* * Entry point. Expects an invocation like: * wrapper */ int main(int argc, char** argv) { wrapper wrap; if ( argc < 4 ) { warn( "Usage: %s ", argv[0] ); return 1; } memset( &wrap, 0, sizeof( wrapper ) ); rlocs_init(); wrap.rlocs = rlocs_new( argv[1] ); if ( wrap.rlocs == NULL ) { warn( "Failed to get config from %s", argv[1] ); return 1; } rlocs_debug_output( wrap.rlocs ); // TODO: We can scale the tun architecture by using multiqueue and having // a bunch of workers, rather than this noddy scheme. If we don't jump // directly to something saner, anyway... wrap.listen_if = create_tun( argv[2] ); if ( wrap.listen_if == -1 ) { warn( "Error opening %s for listening", argv[2] ); rlocs_free( wrap.rlocs ); return 1; } link_set_up( argv[3], 1 ); if ( strcmp( argv[2], argv[3] ) == 0 ) { wrap.same_if = 1; wrap.output_if = wrap.listen_if; } else { wrap.same_if = 0; wrap.output_if = create_tun( argv[3] ); if ( wrap.output_if == -1 ) { warn( "Error opening %s for outputting", argv[3] ); rlocs_free( wrap.rlocs ); close( wrap.listen_if ); return 1; } link_set_up( argv[3], 1 ); } warn( "TODO: Write BGP interventions to file" ); info( "Processing packets" ); struct recv_pkt recv_pkt; struct rsp_data to_send; ssize_t count; int result; memset( &recv_pkt, 0, sizeof( struct recv_pkt ) ); memset( &to_send, 0, sizeof( struct rsp_data ) ); while(1) { // TODO: this isn't zero-copy. Not even close if ( ( count = read( wrap.listen_if, &recv_pkt, sizeof( struct recv_pkt ) ) ) < 0 ) { warn( "Failed to get a packet (%s)", strerror( errno ) ); break; } info( "Got a packet \\o/. %zu bytes", count ); switch( recv_pkt.hdr.ip.version ) { case 0x04 : result = wrap_ipv4_packet( wrap.rlocs, &recv_pkt, &to_send ); break; case 0x06 : result = wrap_ipv6_packet( wrap.rlocs, &recv_pkt, &to_send ); break; default: warn( "Unknown IP version: %i", recv_pkt.hdr.ip.version ); } // We can't send the unwrapped one - it'll just be returned to us // forever, given our bgp interventions, unless the router is clever. // TODO: make fallback-to-unwrapped a configurable option? if ( !result ) { warn( "Failed to construct a wrapped version of received packet, dropping." ); continue; } // no failure, but nothing to forward. if ( to_send.count == 0 ) { continue; } // TODO: Drop the packet if we would fragment. A real implementation // will need to fragment or inform the source, of course. // docs say this should never block and should always write everything - // trust that for now. if ( ( count = writev( wrap.output_if, to_send.iovs, to_send.count ) ) < 0 ) { warn( "Error writing wrapped packet to output: %s", strerror(errno) ); } } info( "Finished, cleaning up" ); rlocs_free( wrap.rlocs ); close( wrap.listen_if ); if ( !wrap.same_if ) { close( wrap.output_if ); } return 0; }