|
@@ -0,0 +1,286 @@
|
|
|
+using Invercargill;
|
|
|
+
|
|
|
+namespace Astrogate {
|
|
|
+
|
|
|
+ public errordomain TunnelError {
|
|
|
+ IFACE_NAME_TOO_LONG,
|
|
|
+ POSIX_ERROR,
|
|
|
+ ADDRESS_SPACE_EXHAUSTED
|
|
|
+ }
|
|
|
+
|
|
|
+ public class Tunnel {
|
|
|
+
|
|
|
+ public InetAddress proxy_internal_ip {get; private set;}
|
|
|
+ public InetAddress internal_network_ip {get; private set;}
|
|
|
+ public InetAddress internal_network_mask {get; private set;}
|
|
|
+ public InetSocketAddress proxy_socket_address {get; private set;}
|
|
|
+ public int mtu { get; private set; }
|
|
|
+ public string iface { get; private set; }
|
|
|
+
|
|
|
+ private int address_allocations = 0;
|
|
|
+ private Socket socket;
|
|
|
+ private Vector<TunnelAddressMapping> address_mapping = new Vector<TunnelAddressMapping> ();
|
|
|
+ private int fd;
|
|
|
+
|
|
|
+ public Tunnel(string iface, int mtu, InetSocketAddress server_address, InetAddress subnet, InetAddress subnet_mask) throws Error {
|
|
|
+ this.iface = iface;
|
|
|
+ this.mtu = mtu;
|
|
|
+ proxy_socket_address = server_address;
|
|
|
+ internal_network_ip = subnet;
|
|
|
+ internal_network_mask = subnet_mask;
|
|
|
+
|
|
|
+ var socket = new Socket(proxy_socket_address.family, SocketType.DATAGRAM, SocketProtocol.UDP);
|
|
|
+ socket.bind(proxy_socket_address, true);
|
|
|
+
|
|
|
+ // Open tun device
|
|
|
+ fd = handle_posix_error("opening tun device", Posix.open("/dev/net/tun", Posix.O_RDWR));
|
|
|
+
|
|
|
+ // Create tunnel device
|
|
|
+ var request = generate_ifreq();
|
|
|
+ request.ifr_mtu = mtu;
|
|
|
+ // address_to_posix(internal_network_ip, request.ifr_dstaddr);
|
|
|
+ // address_to_posix(internal_network_mask, request.ifr_netmask);
|
|
|
+ while(true){
|
|
|
+ proxy_internal_ip = allocate_address();
|
|
|
+ print(@"Allocated $(proxy_internal_ip)\n");
|
|
|
+
|
|
|
+ }
|
|
|
+ // address_to_posix(proxy_internal_ip, request.ifr_addr);
|
|
|
+
|
|
|
+ handle_posix_error("Setting ioctl for /dev/net/tun", Linux.ioctl(fd, IfTun.Tun.SETIFF, &request));
|
|
|
+ }
|
|
|
+
|
|
|
+ public int listen_tun() throws Error {
|
|
|
+
|
|
|
+ while(true) {
|
|
|
+ var buffer = new uint8[mtu+4];
|
|
|
+ var size = Posix.read(fd, buffer, buffer.length);
|
|
|
+ if(size < 0) {
|
|
|
+ print(@"Got $(size) from Posix.read.\n");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ buffer.length = (int)size;
|
|
|
+
|
|
|
+ var frame = new TunnelFrame(buffer);
|
|
|
+
|
|
|
+ if(frame.protocol == SocketFamily.INVALID) {
|
|
|
+ print("Ignoring non IP frame");
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!frame.is_tcp) {
|
|
|
+ print("Ignoring non-tcp datagram\n");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!frame.source_ip.equal(proxy_internal_ip)) {
|
|
|
+ print("Ignoring datagram from non-proxy address\n");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ var mapping = address_mapping
|
|
|
+ .where(m => m.internal_address.address.equal(frame.destination_ip))
|
|
|
+ .where(m => m.internal_address.port == frame.destination_port)
|
|
|
+ .first_or_default();
|
|
|
+
|
|
|
+ if(mapping == null) {
|
|
|
+ print("Ignoring non-mapped destination IP and port\n");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ frame.replace_source_ip(mapping.external_address);
|
|
|
+ frame.replace_source_port(mapping.target_port);
|
|
|
+
|
|
|
+ socket.send_to(mapping.forward_address, frame.raw_frame);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Linux.Network.IfReq generate_ifreq() throws TunnelError {
|
|
|
+ var request = Linux.Network.IfReq();
|
|
|
+ request.ifr_flags = IfTun.IfFlag.TUN;
|
|
|
+ if(iface.length > request.ifr_name.length) {
|
|
|
+ throw new TunnelError.IFACE_NAME_TOO_LONG(@"Max length for interface name is $(request.ifr_name.length) characters, provided name is $(iface.length) characters.");
|
|
|
+ }
|
|
|
+ for(int i = 0; i < iface.length; i++){
|
|
|
+ request.ifr_name[i] = iface[i];
|
|
|
+ }
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int handle_posix_error(string task, int result) throws TunnelError {
|
|
|
+ if(result < 0) {
|
|
|
+ var code = Posix.errno;
|
|
|
+ var message = Posix.strerror(code);
|
|
|
+ throw new TunnelError.POSIX_ERROR(@"Got result code $(result) while $(task): ($(code)) $message");
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void address_to_posix(InetAddress address, Posix.SockAddr addr) {
|
|
|
+ switch (address.family) {
|
|
|
+ case SocketFamily.IPV4:
|
|
|
+ addr.sa_family = Posix.AF_INET;
|
|
|
+ break;
|
|
|
+ case SocketFamily.IPV6:
|
|
|
+ addr.sa_family = Posix.AF_INET6;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ assert_not_reached();
|
|
|
+ }
|
|
|
+
|
|
|
+ var data = get_binary_representation(address);
|
|
|
+ for(int i = 0; i < data.length; i++){
|
|
|
+ addr.sa_data[i] = (char)data[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private InetAddress allocate_address() throws TunnelError {
|
|
|
+ int index;
|
|
|
+ lock(address_allocations) {
|
|
|
+ index = address_allocations;
|
|
|
+ address_allocations++;
|
|
|
+ }
|
|
|
+ // First address is reserved
|
|
|
+ index++;
|
|
|
+
|
|
|
+ if(index > get_num_possible_addresses()) {
|
|
|
+ throw new TunnelError.ADDRESS_SPACE_EXHAUSTED("Exhausted IP address space");
|
|
|
+ }
|
|
|
+
|
|
|
+ var address = get_binary_representation(internal_network_ip);
|
|
|
+ var mask = get_binary_representation(internal_network_mask);
|
|
|
+
|
|
|
+ var pos = address.length-1;
|
|
|
+ var carry = index;
|
|
|
+ while(carry != 0) {
|
|
|
+ var remaining = carry % 256;
|
|
|
+ carry = carry / 256;
|
|
|
+ address[pos] = (address[pos] & mask[pos]) | (remaining & ~mask[pos]);
|
|
|
+ pos--;
|
|
|
+ }
|
|
|
+
|
|
|
+ return new InetAddress.from_bytes(address, internal_network_ip.family);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int get_num_possible_addresses() {
|
|
|
+ int set_bits = 0;
|
|
|
+ var subnet_mask = get_binary_representation(internal_network_mask);
|
|
|
+ for (int i = 0; i < subnet_mask.length; i++) {
|
|
|
+ uint8 mask = subnet_mask[i];
|
|
|
+ while (mask != 0) {
|
|
|
+ set_bits += mask & 1;
|
|
|
+ mask >>= 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return (int) Math.pow (2, 32 - set_bits) - 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static uint8[] get_binary_representation(InetAddress address) {
|
|
|
+ var a = new uint8[address.get_native_size()];
|
|
|
+ Memory.copy(a, address.bytes, a.length);
|
|
|
+ return a;
|
|
|
+ }
|
|
|
+
|
|
|
+ public class TunnelAddressMapping {
|
|
|
+ public InetSocketAddress internal_address {get; set;}
|
|
|
+ public InetSocketAddress forward_address {get; set;}
|
|
|
+ public InetAddress external_address {get; set;}
|
|
|
+ public uint16 target_port {get; set;}
|
|
|
+ }
|
|
|
+
|
|
|
+ private class TunnelFrame {
|
|
|
+ public SocketFamily protocol {get; private set;}
|
|
|
+ public InetAddress source_ip {get; private set;}
|
|
|
+ public InetAddress destination_ip {get; private set;}
|
|
|
+ public uint16 source_port {get; private set;}
|
|
|
+ public uint16 destination_port {get; private set;}
|
|
|
+ public uint8[] raw_frame {get; private set;}
|
|
|
+ public bool is_tcp {get; private set;}
|
|
|
+
|
|
|
+ private int payload_start = -1;
|
|
|
+
|
|
|
+ public TunnelFrame(uint8[] raw_frame) {
|
|
|
+ this.raw_frame = raw_frame;
|
|
|
+ if(raw_frame[2] == 0x08 && raw_frame[3] == 0x00) {
|
|
|
+ protocol = SocketFamily.IPV4;
|
|
|
+ print("Reading IPv4 datagram!\n");
|
|
|
+ read_ipv4_datagram();
|
|
|
+ }
|
|
|
+ else if(raw_frame[2] == 0x86 && raw_frame[3] == 0xdd) {
|
|
|
+ protocol = SocketFamily.IPV6;
|
|
|
+ print("IPv6 Not Implemented!\n");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ print("Could not determine protocol type!\n");
|
|
|
+ protocol = SocketFamily.INVALID;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void read_ipv4_datagram() {
|
|
|
+ uint internet_header_length = raw_frame[4] & 0x0F;
|
|
|
+ source_ip = new InetAddress.from_bytes(raw_frame[16:20], SocketFamily.IPV4);
|
|
|
+ destination_ip = new InetAddress.from_bytes(raw_frame[20:22], SocketFamily.IPV4);
|
|
|
+
|
|
|
+ print(@"Src: $(source_ip.to_string()), Dst: $(destination_ip.to_string())\n");
|
|
|
+
|
|
|
+ // TUN header = 4 bytes, internet_header_length = IP header size in 32-bit word sizes
|
|
|
+ payload_start = 4 + (((int)internet_header_length) * 4);
|
|
|
+
|
|
|
+ if(raw_frame[13] == 0x06) {
|
|
|
+ print(@"Is TCP! (offset $payload_start == 4 + $internet_header_length * 4)\n");
|
|
|
+ is_tcp = true;
|
|
|
+ read_tcp_ports();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ print("Is not TCP!\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void read_tcp_ports() {
|
|
|
+ uint16 src = 0;
|
|
|
+ uint16 dst = 0;
|
|
|
+ Memory.copy(&src, raw_frame[payload_start:payload_start+2], sizeof(int16));
|
|
|
+ Memory.copy(&dst, raw_frame[payload_start+2:payload_start+4], sizeof(int16));
|
|
|
+
|
|
|
+ src = src.to_big_endian();
|
|
|
+ dst = dst.to_big_endian();
|
|
|
+
|
|
|
+ print(@"Src: $(src), Dst: $dst\n");
|
|
|
+
|
|
|
+ source_port = src;
|
|
|
+ destination_port = dst;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void write_tcp_ports(uint16 src, uint16 dst) {
|
|
|
+ var s = src.to_little_endian();
|
|
|
+ var d = dst.to_little_endian();
|
|
|
+ var sbytes = new uint8[sizeof(uint16)];
|
|
|
+ var dbytes = new uint8[sizeof(uint16)];
|
|
|
+ Memory.copy(sbytes, &s, sizeof(uint16));
|
|
|
+ Memory.copy(dbytes, &d, sizeof(uint16));
|
|
|
+
|
|
|
+ copy_into(raw_frame, payload_start, 2, sbytes);
|
|
|
+ copy_into(raw_frame, payload_start+2, 2, dbytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void copy_into(uint8[] buffer, int start, int length, uint8[] replacement) {
|
|
|
+ for(int i = start; i < start+length; i++) {
|
|
|
+ buffer[i] = replacement[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void replace_source_ip(InetAddress address) {
|
|
|
+ source_ip = address;
|
|
|
+ if(protocol == SocketFamily.IPV4) {
|
|
|
+ copy_into(raw_frame, 16, 4, get_binary_representation(address));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void replace_source_port(uint16 port) {
|
|
|
+ source_port = port;
|
|
|
+ write_tcp_ports(source_port, destination_port);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|