namespace AstrogateTunnel { private static Socket socket; private static int tun_fd; private static string ifname; private static InetSocketAddress server_address; private static InetSocketAddress client_address; private static InetAddress vpn_network; private static InetAddress vpn_netmask; private static InetAddress vpn_ip; private static int mtu = 1500; public static int main(string[] args) { int client_port = 0; int server_port = 0; if(args.length < 6 || !int.try_parse(args[3], out client_port) || !int.try_parse(args[5], out server_port)) { print(@"USAGE: $(args[0]) interface client_ip client_port server_ip server_port\n"); return -1; } try { server_address = new InetSocketAddress.from_string(args[4], server_port); client_address = new InetSocketAddress.from_string(args[2], client_port); ifname = args[1]; socket = new Socket(server_address.family, SocketType.DATAGRAM, SocketProtocol.UDP); socket.bind(client_address, false); // Open tun device tun_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(tun_fd, IfTun.Tun.SETIFF, &request)); // Register with server var registration_message = new uint8[] { 'R' }; socket.connect(server_address); socket.send(registration_message); print(@"Waiting for $(server_address)...\n"); var ack_message = new uint8[1024]; socket.receive(ack_message); if(ack_message[0] != 'A') { print("Received invalid reply from server. Exiting.\n"); return -2; } var mtu_dat = new uint8[2]; if(ack_message[1] == '4') { vpn_network = new InetAddress.from_bytes(ack_message[2:6], SocketFamily.IPV4); vpn_netmask = new InetAddress.from_bytes(ack_message[6:10], SocketFamily.IPV4); vpn_ip = new InetAddress.from_bytes(ack_message[10:14], SocketFamily.IPV4); mtu_dat = ack_message[14:16]; } else if(ack_message[1] == '6') { vpn_network = new InetAddress.from_bytes(ack_message[2:18], SocketFamily.IPV6); vpn_netmask = new InetAddress.from_bytes(ack_message[18:34], SocketFamily.IPV6); vpn_ip = new InetAddress.from_bytes(ack_message[34:50], SocketFamily.IPV6); mtu_dat = ack_message[50:52]; } else { print("Received unsupported network type from server. Exiting.\n"); return -3; } uint16 mtu_val = 0; Memory.copy(&mtu_val, mtu_dat, sizeof(uint16)); mtu = mtu_val.to_big_endian(); print(@"Registered with Astrogate server at $server_address:\n"); print(@" Network: $vpn_network\n"); print(@" Subnet mask: $vpn_netmask\n"); print(@" Allocated IP: $vpn_ip\n"); print(@" MTU: $mtu\n\n"); request.ifr_flags = 0; request.ifr_mtu = mtu; handle_posix_error(@"setting MTU for $ifname", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFMTU, &request)); request.ifr_flags |= Linux.Network.IfFlag.UP; handle_posix_error(@"bringing $ifname up", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFFLAGS, &request)); address_to_posix(vpn_ip, &request.ifr_addr); handle_posix_error(@"setting IP address for $ifname", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFADDR, &request)); address_to_posix(vpn_netmask, &request.ifr_addr); handle_posix_error(@"setting subnet mask for $ifname", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFNETMASK, &request)); address_to_posix(vpn_network, &request.ifr_addr); handle_posix_error(@"setting destination network for $ifname", Linux.ioctl(socket.fd, Linux.Network.SIOCSIFDSTADDR, &request)); } catch(Error e) { print(@"Fatal error: $(e.message)\n"); return -999; } new Thread("udp listener", listen_udp); return listen_tun(); } private int listen_udp() { while(true) { try { // Receive the next datagram var buffer = new uint8[mtu+5]; var size = socket.receive(buffer); buffer.length = (int)size; if(buffer[0] == 'L') { link_ip(buffer); continue; } else if(buffer[0] == 'U') { unlink_ip(buffer); continue; } else if(buffer[0] != 'D') { print("Ignoring invalid datagram from server\n"); continue; } handle_posix_error(@"piping datagram to $ifname", (int)Posix.write(tun_fd, buffer[1:buffer.length], buffer.length)); } catch(Error e) { print(@"Exception handling incoming tunnel server datagram: $(e.message)\n"); } } } private int listen_tun() { while(true) { var buffer = new uint8[mtu+4]; var size = Posix.read(tun_fd, buffer, buffer.length); if(size < 0) { print(@"Got $(size) from Posix.read.\n"); continue; } buffer.length = (int)size; try { var message = new uint8[buffer.length + 1]; message[0] = 'D'; Memory.copy(message[1:], buffer, buffer.length); socket.send(message); } catch (Error e) { print(@"Error forwarding datagram to server: $(e.message)\n"); } } } private void link_ip(uint8[] buffer) throws Error { var address = new InetAddress.from_bytes(buffer[1:buffer.length], vpn_ip.family); print(@"Linking IP $(address)\n"); var route = build_rtentry(address); var res = Linux.ioctl(socket.fd, Linux.Network.SIOCADDRT, &route); if(res < 0 && errno != Posix.EEXIST) { handle_posix_error(@"adding route to $address", res); } else if(errno == Posix.EEXIST) { print(@"Already linked to $(address)\n"); } } private void unlink_ip(uint8[] buffer) throws Error { var address = new InetAddress.from_bytes(buffer[1:buffer.length], vpn_ip.family); print(@"Uninking IP $(address)\n"); var route = build_rtentry(address); handle_posix_error(@"adding route to $address", Linux.ioctl(socket.fd, Linux.Network.SIOCDELRT, &route)); } private Linux.Network.RtEntry build_rtentry(InetAddress address) { var route = Linux.Network.RtEntry(); route.rt_dev = ifname; address_to_posix(address, &route.rt_dst); address_to_posix(new InetAddress.from_string("255.255.255.255"), &route.rt_genmask); route.rt_flags = Linux.Network.RtFlag.UP; return route; } private int handle_posix_error(string task, int result) throws Error { if(result < 0) { var code = Posix.errno; var message = Posix.strerror(code); throw new Error.literal(Quark.from_string("posix-error"), code, @"Got result $(result) while $(task): $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(); } } private Linux.Network.IfReq generate_ifreq() throws Error { var request = Linux.Network.IfReq(); request.ifr_flags = IfTun.IfFlag.TUN; if(ifname.length > request.ifr_name.length) { error(@"Max length for interface name is $(request.ifr_name.length) characters, provided name is $(ifname.length) characters."); } for(int i = 0; i < ifname.length; i++){ request.ifr_name[i] = ifname[i]; } return request; } }