|
@@ -0,0 +1,326 @@
|
|
|
|
+using Invercargill;
|
|
|
|
+
|
|
|
|
+namespace Astrogate {
|
|
|
|
+
|
|
|
|
+ public errordomain TunnelError {
|
|
|
|
+ IFACE_NAME_TOO_LONG,
|
|
|
|
+ POSIX_ERROR,
|
|
|
|
+ ADDRESS_SPACE_EXHAUSTED,
|
|
|
|
+ PORT_SPACE_EXHAUSTED,
|
|
|
|
+ CLIENT_NOT_REGISTERED,
|
|
|
|
+ NOT_CONFIGURED,
|
|
|
|
+ CLIENT_NOT_WHITELISTED
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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 Vector<InetAddress> allowed_clients = new Vector<InetAddress>();
|
|
|
|
+ private Vector<TunnelClientRegistration> registered_clients = new Vector<TunnelClientRegistration>();
|
|
|
|
+ private bool[] available_ports = new bool[65536];
|
|
|
|
+ private int fd;
|
|
|
|
+ private TunnelServer udp_server;
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+ proxy_internal_ip = allocate_address();
|
|
|
|
+
|
|
|
|
+ var socket = new Socket(proxy_socket_address.family, SocketType.DATAGRAM, SocketProtocol.UDP);
|
|
|
|
+
|
|
|
|
+ // 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();
|
|
|
|
+
|
|
|
|
+ handle_posix_error("creating tun interface", Linux.ioctl(fd, IfTun.Tun.SETIFF, &request));
|
|
|
|
+
|
|
|
|
+ request.ifr_flags = 0;
|
|
|
|
+ request.ifr_mtu = mtu;
|
|
|
|
+ handle_posix_error(@"setting MTU for $iface", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFMTU, &request));
|
|
|
|
+
|
|
|
|
+ request.ifr_flags |= Linux.Network.IfFlag.UP;
|
|
|
|
+ handle_posix_error(@"bringing $iface up", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFFLAGS, &request));
|
|
|
|
+
|
|
|
|
+ address_to_posix(proxy_internal_ip, &request.ifr_addr);
|
|
|
|
+ handle_posix_error(@"setting IP address for $iface", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFADDR, &request));
|
|
|
|
+
|
|
|
|
+ address_to_posix(internal_network_mask, &request.ifr_addr);
|
|
|
|
+ handle_posix_error(@"setting subnet mask for $iface", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFNETMASK, &request));
|
|
|
|
+
|
|
|
|
+ address_to_posix(internal_network_ip, &request.ifr_addr);
|
|
|
|
+ handle_posix_error(@"setting destination network for $iface", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFDSTADDR, &request));
|
|
|
|
+
|
|
|
|
+ udp_server = new TunnelServer(proxy_socket_address, this);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void whitelist_client(InetAddress address) {
|
|
|
|
+ allowed_clients.add(address);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public TunnelAddressMapping map(InetAddress public_ip, InetAddress forward_ip, uint16 port) throws Error {
|
|
|
|
+ var mapping = new TunnelAddressMapping();
|
|
|
|
+ mapping.external_address = public_ip;
|
|
|
|
+ mapping.target_port = port;
|
|
|
|
+
|
|
|
|
+ var client = registered_clients.first_or_default(c => c.forward_address.address.equal(forward_ip));
|
|
|
|
+ if(client == null) {
|
|
|
|
+ throw new TunnelError.CLIENT_NOT_REGISTERED(@"The requested client $(forward_ip) is not registered with the tunnel.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mapping.forward_address = client.forward_address;
|
|
|
|
+ mapping.internal_address = client.internal_address;
|
|
|
|
+ mapping.source_port = allocate_port();
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ mapping.external_address = new InetAddress.from_string("8.8.8.8");
|
|
|
|
+
|
|
|
|
+ address_mapping.add(mapping);
|
|
|
|
+ udp_server.link_ip(mapping.forward_address, mapping.external_address);
|
|
|
|
+ return mapping;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void unmap(TunnelAddressMapping mapping) throws Error {
|
|
|
|
+ print("Unmapping!");
|
|
|
|
+ lock(available_ports) {
|
|
|
|
+ var index = address_mapping.index_of(m => m == mapping);
|
|
|
|
+ if(index == -1) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ address_mapping.remove(index);
|
|
|
|
+ available_ports[mapping.source_port] = false;
|
|
|
|
+
|
|
|
|
+ // Only send unlink to client if there are no other mappings to this address
|
|
|
|
+ if(address_mapping.no(m => m.external_address.equal(mapping.external_address) && m.forward_address.address.equal(mapping.forward_address.address))) {
|
|
|
|
+ udp_server.unlink_ip(mapping.forward_address, mapping.external_address);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void listen_threaded() {
|
|
|
|
+ new Thread<int>("Tunnel interface thread", listen_tun);
|
|
|
|
+ new Thread<int>("Tunnel udp server thread", listen_udp);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public int listen_udp() {
|
|
|
|
+ return udp_server.listen();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public int listen_tun() {
|
|
|
|
+
|
|
|
|
+ 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.equal(frame.destination_ip))
|
|
|
|
+ .where(m => m.source_port == frame.source_port)
|
|
|
|
+ .first_or_default();
|
|
|
|
+
|
|
|
|
+ if(mapping == null) {
|
|
|
|
+ print("Ignoring non-mapped destination IP and port\n");
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ print(@"Remapping $(proxy_internal_ip) to $(mapping.external_address)\n");
|
|
|
|
+ frame.replace_source_ip(mapping.external_address);
|
|
|
|
+ udp_server.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) {
|
|
|
|
+ var addr_str = address.to_string();
|
|
|
|
+ switch (address.family) {
|
|
|
|
+ case SocketFamily.IPV4:
|
|
|
|
+ var sin4 = Posix.SockAddrIn();
|
|
|
|
+ sin4.sin_family = Posix.AF_INET;
|
|
|
|
+ Posix.inet_pton(Posix.AF_INET, addr_str, &sin4.sin_addr.s_addr);
|
|
|
|
+ Memory.copy(addr, &sin4, sizeof(Posix.SockAddr));
|
|
|
|
+ break;
|
|
|
|
+ case SocketFamily.IPV6:
|
|
|
|
+ var sin6 = Posix.SockAddrIn();
|
|
|
|
+ sin6.sin_family = Posix.AF_INET;
|
|
|
|
+ Posix.inet_pton(Posix.AF_INET, addr_str, &sin6.sin_addr.s_addr);
|
|
|
|
+ Memory.copy(addr, &sin6, sizeof(Posix.SockAddr));
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ assert_not_reached();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public TunnelClientRegistration register_client(InetSocketAddress socket_address) throws TunnelError {
|
|
|
|
+ var client = allowed_clients.first_or_default(c => c.equal(socket_address.address));
|
|
|
|
+ if(client == null) {
|
|
|
|
+ throw new TunnelError.CLIENT_NOT_WHITELISTED(@"Cannot register client $(socket_address) as it is not in the allowed clients list");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var registration = registered_clients.first_or_default(c => c.forward_address.address.equal(socket_address.address));
|
|
|
|
+ var pre_registered = registration != null;
|
|
|
|
+ if(!pre_registered) {
|
|
|
|
+ registration = new TunnelClientRegistration();
|
|
|
|
+ registration.internal_address = allocate_address();
|
|
|
|
+ registration.forward_address = socket_address;
|
|
|
|
+ registered_clients.add(registration);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ registration.forward_address = socket_address;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return registration;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public 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("Could not allocate new IP address, IP address space exhausted");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var address = get_binary_representation_of_ip(internal_network_ip);
|
|
|
|
+ var mask = get_binary_representation_of_ip(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 uint16 allocate_port() throws TunnelError {
|
|
|
|
+ lock(available_ports) {
|
|
|
|
+ for(int i = 10; i < available_ports.length; i++) {
|
|
|
|
+ if(available_ports[i] == false) {
|
|
|
|
+ available_ports[i] = true;
|
|
|
|
+ return (uint16)i;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ throw new TunnelError.PORT_SPACE_EXHAUSTED("Could not allocate new port, port space exhausted");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public int get_num_possible_addresses() {
|
|
|
|
+ int set_bits = 0;
|
|
|
|
+ var subnet_mask = get_binary_representation_of_ip(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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void inject(TunnelFrame frame, InetSocketAddress source) throws Error {
|
|
|
|
+
|
|
|
|
+ if(frame.protocol == SocketFamily.INVALID) {
|
|
|
|
+ print("Refusing to inject non IP frame\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(!frame.is_tcp) {
|
|
|
|
+ print("Refusing to inject non-tcp datagram\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var client = registered_clients.first_or_default(c => c.forward_address.address.equal(source.address) && c.forward_address.port == source.port);
|
|
|
|
+ if(client == null) {
|
|
|
|
+ print("Refusing to inject frame from non-registered client.\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(!frame.source_ip.equal(client.internal_address)) {
|
|
|
|
+ print("Refusing to inject frame from an address that does not belong to the sending client.\n");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var mapping = address_mapping
|
|
|
|
+ .where(m => m.internal_address.equal(frame.source_ip))
|
|
|
|
+ .where(m => m.target_port == frame.source_port)
|
|
|
|
+ .first_or_default();
|
|
|
|
+
|
|
|
|
+ if(mapping == null) {
|
|
|
|
+ print("Refusing to inject frame with non-mapped destination IP.\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ print(@"INJECT Remapping $(frame.destination_ip) to $(proxy_internal_ip)\n");
|
|
|
|
+ frame.replace_destination_ip(tunnel.proxy_internal_ip);
|
|
|
|
+ handle_posix_error("injecting frame", (int)Posix.write(fd, frame.raw_frame, frame.raw_frame.length));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static uint8[] get_binary_representation_of_ip(InetAddress address) {
|
|
|
|
+ var a = new uint8[address.get_native_size()];
|
|
|
|
+ Memory.copy(a, address.bytes, a.length);
|
|
|
|
+ return a;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|