Selaa lähdekoodia

Begin work on TUN interface

Billy Barrow 1 vuosi sitten
vanhempi
sitoutus
784a046e20
6 muutettua tiedostoa jossa 405 lisäystä ja 8 poistoa
  1. 2 0
      config
  2. 19 0
      src/configuration.vala
  3. 6 1
      src/meson.build
  4. 24 7
      src/server.vala
  5. 286 0
      src/tunnel.vala
  6. 68 0
      src/vapi/if_tun.vapi

+ 2 - 0
config

@@ -1,3 +1,5 @@
+BIND-TO 127.0.0.1
+BIND-TO 200:3785:eb78:5234:e4f1:d453:86d3:59ff
 
 TLS billy.barrow.nz 443 ROUND-ROBIN {
     192.168.1.232 443

+ 19 - 0
src/configuration.vala

@@ -22,8 +22,14 @@ namespace Astrogate {
             return config_items;
         }}
 
+        private Vector<InetAddress> bind_addresses;
+        public Enumerable<InetAddress> bind_to { get {
+            return bind_addresses;
+        }}
+
         public Configuration() {
             config_items = new Vector<ConfigurationItem>();
+            bind_addresses = new Vector<InetAddress>();
         }
 
         public async void read_file(File file) throws Error {
@@ -37,7 +43,20 @@ namespace Astrogate {
                 if(line.chomp().chug() == "") {
                     continue;
                 }
+                if(line.chomp().chug().has_prefix("#")) {
+                    continue;
+                }
+
                 var type = read_field(line, 0);
+                if(type == "BIND-TO") {
+                    var address = read_field(line, 1);
+                    if(address == null) {
+                        throw new ConfigurationFormatError.MALFORMED_ENTRY("One or more required configuration entry fields are missing");
+                    }
+                    bind_addresses.add(new InetAddress.from_string(address));
+                    continue;
+                }
+
                 var host = read_field(line, 1);
                 var port = read_field(line, 2);
                 var mode = read_field(line, 3);

+ 6 - 1
src/meson.build

@@ -1,4 +1,5 @@
 project('astrogate', 'vala', 'c')
+vapi_dir = meson.current_source_dir() / 'vapi'
 
 add_project_arguments(['--disable-warnings', '--enable-checking'], language: 'vala')
 
@@ -6,6 +7,7 @@ add_project_arguments(['--disable-warnings', '--enable-checking'], language: 'va
 sources = files('server.vala')
 sources += files('header_reader.vala')
 sources += files('configuration.vala')
+sources += files('tunnel.vala')
 
 
 dependencies = [
@@ -15,7 +17,10 @@ dependencies = [
     dependency('gee-0.8'),
     dependency('libxml-2.0'),
     dependency('invercargill'),
-    meson.get_compiler('c').find_library('m', required: false),
+    meson.get_compiler('vala').find_library('posix'),
+    meson.get_compiler('vala').find_library('linux'),
+    meson.get_compiler('vala').find_library('if_tun', dirs: vapi_dir),
+    meson.get_compiler('c').find_library('m')
 ]
 
 executable('astrogate', sources, dependencies: dependencies, install: true)

+ 24 - 7
src/server.vala

@@ -5,6 +5,22 @@ namespace Astrogate {
     private static Configuration config;
 
     public static int main(string[] args) {
+
+        var tunnel = new Tunnel(
+            args[1],
+            1500,
+            new InetSocketAddress(new InetAddress.from_string("127.0.0.1"), 8080),
+            new InetAddress.from_string("10.60.0.64"),
+            new InetAddress.from_string("255.255.255.192"));
+
+        var res = tunnel.listen_tun();
+        print(@"EXIT: $res\n");
+        return res;
+
+        /// Normal program below
+
+
+
         var config_path = args.length > 1 ? args[1] : "/etc/astrogate.conf";
         run.begin(config_path);
 
@@ -17,14 +33,15 @@ namespace Astrogate {
         print("Reading config...\n");
         yield config.read_file(File.new_for_commandline_arg(config_path));
 
-        var service = new SocketService ();
-        var ports = config.entries
-            .select<uint16>(e => e.source_port)
-            .unique((a, b) => a == b );
+        var service = new SocketService();
+        var service_ports = config.entries.group_by<uint16>(s => s.source_port, (a, b) => a == b);
 
-        foreach (var port in ports) {
-            print(@"Adding port $(port)\n");
-            service.add_inet_port (port, null);
+        foreach (var address in config.bind_to) {
+            foreach (var port in service_ports) {
+                print(@"Binding to $(address):$(port.key) for hosts: $(port.items.to_string(i => i.hostname, ", "))\n");
+                var socket_addr = new InetSocketAddress(address, port.key);
+                service.add_address(socket_addr, SocketType.STREAM, SocketProtocol.TCP, null, null);
+            }
         }
 
         service.incoming.connect((c, o) => {

+ 286 - 0
src/tunnel.vala

@@ -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);
+        }
+
+    }
+
+}

+ 68 - 0
src/vapi/if_tun.vapi

@@ -0,0 +1,68 @@
+/* if_tun.vapi
+ *
+ * Copyright (C) 2024 Billy Barrow
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * 	Billy Barrow <billy@barrow.nz>
+ */
+
+ [CCode (cheader_filename = "linux/if_tun.h")]
+ namespace IfTun {
+    [CCode (cname = "int", cprefix = "IFF_", has_type_id = false)]
+    public enum IfFlag {
+        TUN,
+        TAP,
+        NAPI,
+        NAPI_FRAGS,
+        NO_CARRIER,
+        NO_PI,
+        ONE_QUEUE,
+        VNET_HDR,
+        TUN_EXCL,
+        MULTI_QUEUE,
+        ATTACH_QUEUE,
+        DETACH_QUEUE,
+        PERSIST,
+        NOFILTER
+    }
+
+    [CCode (cname = "int", cprefix = "TUN", has_type_id = false)]
+    public enum Tun {
+        SETNOCSUM,
+        SETDEBUG,
+        SETIFF,
+        SETPERSIST,
+        SETOWNER,
+        SETLINK,
+        SETGROUP,
+        GETFEATURES,
+        SETOFFLOAD,
+        SETTXFILTER,
+        GETIFF,
+        GETSNDBUF,
+        SETSNDBUF,
+        ATTACHFILTER,
+        DETACHFILTER,
+        GETVNETHDRSZ,
+        SETVNETHDRSZ,
+        SETQUEUE,
+        SETIFINDEX,
+        GETFILTER,
+        SETVNETLE,
+        GETVNETLE,
+    }
+ }
+