From c9bcc1f34696a8595590674896e22a70c8e3ea0d Mon Sep 17 00:00:00 2001 From: Dmitry Zolotukhin Date: Mon, 15 Jul 2024 00:58:40 +0200 Subject: [PATCH] Committing current changes. Unfortunately the PPP part turned out to be trickier to implement than expected. Leaving code here for future reference. --- Cargo.lock | 432 +++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/fortivpn.rs | 166 +++++++++++++++++-- src/http.rs | 10 +- src/main.rs | 4 +- src/network.rs | 135 ++++++++++----- 6 files changed, 684 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6499e35..5f43de3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,23 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "backtrace" version = "0.3.73" @@ -56,6 +73,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version", +] + [[package]] name = "cc" version = "1.0.104" @@ -68,6 +94,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags 1.3.2", + "textwrap", + "unicode-width", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -84,6 +130,90 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "criterion" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0363053954f3e679645fc443321ca128b7b950a6fe288cf5f9335cc22ee58394" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "libc", + "num-traits", + "rand_core 0.3.1", + "rand_os", + "rand_xoshiro", + "rayon", + "rayon-core", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f9212ddf2f4a9eb2d401635190600656a1f88a932ef53d06e7fa4c7e02fb8e" +dependencies = [ + "byteorder", + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "defmt" version = "0.3.8" @@ -116,6 +246,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "errno" version = "0.3.9" @@ -126,6 +262,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -147,6 +305,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "getrandom" version = "0.2.15" @@ -173,6 +337,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hdlc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3f85972aa5c8b4f4db88dc323ddd8d00f4f7de46d6ee6c66b19256bf0fe9b4" +dependencies = [ + "criterion", + "failure", +] + [[package]] name = "heapless" version = "0.8.0" @@ -183,6 +357,36 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -250,6 +454,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.1" @@ -364,6 +577,7 @@ dependencies = [ name = "pterodapter" version = "0.0.1" dependencies = [ + "hdlc", "log", "rand", "smoltcp", @@ -388,7 +602,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -398,9 +612,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -410,12 +639,74 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_xoshiro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929" +dependencies = [ + "byteorder", + "rand_core 0.3.1", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" @@ -429,6 +720,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.23" @@ -461,6 +767,43 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.70", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -507,6 +850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", + "quote", "unicode-ident", ] @@ -521,6 +865,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -533,6 +889,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.62" @@ -553,6 +918,16 @@ dependencies = [ "syn 2.0.70", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.38.0" @@ -585,6 +960,18 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "vcpkg" version = "0.2.15" @@ -597,12 +984,53 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index a69997b..7d0eb67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ log = { version = "0.4", default-features = false } tokio = { version = "1.38", default-features = false, features = ["rt", "io-util", "signal", "net", "time", "sync"] } tokio-native-tls = { version = "0.3", default-features = false } smoltcp = { version = "0.11", default-features = false, features = ["std", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-tcp"] } +hdlc = { version = "0.2", default-features = false } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } [profile.release] diff --git a/src/fortivpn.rs b/src/fortivpn.rs index e5be25c..4672326 100644 --- a/src/fortivpn.rs +++ b/src/fortivpn.rs @@ -1,6 +1,8 @@ use std::{ error, fmt, io, net::{IpAddr, Ipv4Addr, SocketAddr}, + str::FromStr, + time::Duration, }; use log::{debug, warn}; @@ -117,7 +119,12 @@ pub async fn get_oauth_cookie(config: &Config) -> Result { Ok(cookie) } -pub struct FortiVPNTunnel {} +pub struct FortiVPNTunnel { + socket: FortiTlsStream, + addr: IpAddr, + first_packet: bool, + tunnel_failed: bool, +} impl FortiVPNTunnel { pub async fn new(config: &Config, cookie: String) -> Result { @@ -127,9 +134,18 @@ impl FortiVPNTunnel { &config.destination_hostport }; let mut socket = FortiVPNTunnel::connect(&config.destination_hostport, domain).await?; - FortiVPNTunnel::request_vpn_allocation(domain, &mut socket, &cookie).await?; + let addr = FortiVPNTunnel::request_vpn_allocation(domain, &mut socket, &cookie).await?; FortiVPNTunnel::start_vpn_tunnel(domain, &mut socket, &cookie).await?; - Ok(FortiVPNTunnel {}) + Ok(FortiVPNTunnel { + socket, + addr, + first_packet: true, + tunnel_failed: true, + }) + } + + pub fn ip_addr(&self) -> IpAddr { + self.addr } async fn connect(hostport: &str, domain: &str) -> Result { @@ -147,17 +163,32 @@ impl FortiVPNTunnel { domain: &str, socket: &mut FortiTlsStream, cookie: &str, - ) -> Result<(), FortiError> { + ) -> Result { let req = build_http_request("GET /remote/fortisslvpn_xml", domain, Some(cookie), 0); - println!("Requesrt is {}", req); socket.write_all(req.as_bytes()).await?; + socket.flush().await?; let headers = read_http_headers(socket).await?; - println!("Headers = {}", headers); let content = read_content(socket, headers.as_str()).await?; - println!("Content = {}", content); - Ok(()) + const IPV4_ADDRESS_PREFIX: &str = " Result<(), FortiError> { let req = build_http_request("GET /remote/sslvpn-tunnel", domain, Some(cookie), 0); - println!("Starting VPN is {}", req); + println!("Starting VPN is {}?", req); socket.write_all(req.as_bytes()).await?; + socket.flush().await?; + + Ok(()) + } + + pub async fn send_packet(&mut self, data: &[u8]) -> Result<(), FortiError> { + println!("About to send packet {:?}", data); + // PPP packets are surprisingly basic. + let mut packet_header = [0u8; 6]; + packet_header[..2].copy_from_slice(&(6 + data.len() as u16).to_be_bytes()); + packet_header[2..4].copy_from_slice(&[0x50, 0x50]); + packet_header[4..].copy_from_slice(&(data.len() as u16).to_be_bytes()); + + // TODO: replace with a version that needs less allocations + /* + let packet_data = Vec::from(data); + let data = match hdlc::encode(&packet_data, hdlc::SpecialChars::default()) { + Ok(encoded) => encoded, + Err(err) => { + debug!("Failed to encode HDLC packet: {}", err); + return Err("Failed to encode HDLC packet".into()); + } + }; + */ - let mut buf = [0u8; 16]; - loop { - let data_read = socket.read(&mut buf).await?; + self.socket.write_all(&packet_header).await?; + Ok(self.socket.write_all(&data).await?) + } - println!("Received packet from FortiVPN {:?}", &buf[..data_read]); + pub async fn read_packet(&mut self, dest: &mut [u8]) -> Result { + let socket = &mut self.socket; + let mut packet_header = [0u8; 6]; + println!("Reading packet"); + // If no data is available, this will return immediately. + match tokio::time::timeout(Duration::from_millis(100), async { + loop { + //println!("stupid loop"); + if let Ok(header) = socket.read_peek(6).await { + if header.len() >= 6 { + println!("Have header {} bytes", header.len()); + return; + } else { + println!("Have {} bytes", header.len()); + } + } else { + return; + }; + } + }) + .await + { + Ok(_) => {} + Err(_) => return Ok(0), } + println!("Packet not ready"); - Ok(()) + socket.read(&mut packet_header).await?; + if self.first_packet { + self.first_packet = false; + if let Err(err) = FortiVPNTunnel::validate_link(socket, &packet_header).await { + self.tunnel_failed = true; + return Err(err); + } + } + let mut ppp_size = [0u8; 2]; + ppp_size.copy_from_slice(&packet_header[..2]); + let ppp_size = u16::from_be_bytes(ppp_size); + let mut data_size = [0u8; 2]; + data_size.copy_from_slice(&packet_header[4..6]); + let data_size = u16::from_be_bytes(data_size); + let magic = &packet_header[2..4]; + if ppp_size != data_size + 6 { + debug!( + "Conflicting packet size data: PPP packet size is {}, data size is {}", + ppp_size, data_size + ); + return Err("Header has conflicting length data".into()); + } + if magic != &[0x50, 0x50] { + debug!( + "Found {:x}{:x} instead of magic", + packet_header[2], packet_header[3] + ); + return Err("Magic not found".into()); + } + let data_size = data_size as usize; + if data_size > dest.len() { + debug!( + "Destination buffer ({} bytes) is smaller than the traferred packet ({} bytes)", + dest.len(), + data_size + ); + return Err("Destination buffer not large enough to fit all data".into()); + } + let mut received_data = 0usize; + while received_data < data_size { + received_data += socket.read(&mut dest[received_data..]).await?; + } + Ok(data_size) + } + + async fn validate_link( + socket: &mut FortiTlsStream, + packet_header: &[u8], + ) -> Result<(), FortiError> { + const FALL_BACK_TO_HTTP: &[u8] = "HTTP/1".as_bytes(); + if packet_header == FALL_BACK_TO_HTTP { + // FortiVPN will return an HTTP response if something goes wrong on setup. + let headers = read_http_headers(socket).await?; + debug!("Tunnel not active, error response: {}", headers); + let content = read_content(socket, headers.as_str()).await?; + debug!("Error contents: {}", content); + Err("Tunnel refused to establish link".into()) + } else { + Ok(()) + } } } diff --git a/src/http.rs b/src/http.rs index 1c88f36..ba0d84d 100644 --- a/src/http.rs +++ b/src/http.rs @@ -23,7 +23,7 @@ where { let mut result = vec![]; loop { - let chunk = socket.read_peek().await?; + let chunk = socket.read_peek(result.len() + 1).await?; let result_len = result.len(); let found = if let Some(header_end) = find_chunk_end(separator, result.as_slice(), chunk) { result.resize(result.len() + header_end, 0u8); @@ -189,7 +189,7 @@ impl Buffer { dest[..source_range.len()].copy_from_slice(&self.data[source_range.clone()]); self.read_start = source_range.end; if self.read_start > self.move_threshold { - // This is not the best ring buffer implementation, hope this trick avoids copying too much data. + // This is not the best circular buffer implementation, hope this trick avoids copying too much data. // Only do a memmove if a smaller portion of data needs to be relocated. // This means that the buffer's capacity needs to be larger than usual. self.data.copy_within(self.read_start..self.write_start, 0); @@ -232,10 +232,10 @@ where } } - pub async fn read_peek(&mut self) -> Result<&[u8], HttpError> { - let read_buffer_empty = self.buffer.peek().is_empty(); + pub async fn read_peek(&mut self, need_bytes: usize) -> Result<&[u8], HttpError> { + let need_more_data = self.buffer.peek().len() < need_bytes; let write_slice = self.buffer.write_slice(); - if read_buffer_empty && !write_slice.is_empty() { + if need_more_data && !write_slice.is_empty() { let bytes_read = self.stream.read(write_slice).await?; self.buffer.advance_write(bytes_read); } diff --git a/src/main.rs b/src/main.rs index c84d132..4193d64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,7 +167,7 @@ fn serve(config: Config) -> Result<(), i32> { 1 })?; - let mut forti_client = rt + let forti_client = rt .block_on(fortivpn::FortiVPNTunnel::new( &config.fortivpn, sslvpn_cookie, @@ -176,7 +176,7 @@ fn serve(config: Config) -> Result<(), i32> { eprintln!("Failed to connect to VPN service: {}", err); 1 })?; - let mut client = network::Network::new().map_err(|err| { + let mut client = network::Network::new(forti_client).map_err(|err| { eprintln!("Failed to start virtual network interface: {}", err); 1 })?; diff --git a/src/network.rs b/src/network.rs index 7303893..699bb31 100644 --- a/src/network.rs +++ b/src/network.rs @@ -7,7 +7,12 @@ use std::{ use log::{debug, warn}; use smoltcp::{iface, phy, socket, wire}; -use tokio::sync::{mpsc, oneshot}; +use tokio::{ + runtime, + sync::{mpsc, oneshot}, +}; + +use crate::fortivpn::FortiVPNTunnel; const MTU_SIZE: usize = 1500; const SOCKET_BUFFER_SIZE: usize = 4096; @@ -24,21 +29,15 @@ pub struct Network<'a> { } impl Network<'_> { - pub fn new<'a>() -> Result, NetworkError> { - // TODO: this should block until the device is ready and the network connection is up and running. - let mut device = PPPDevice { - mtu: MTU_SIZE, - buf: [0u8; MTU_SIZE], - }; + pub fn new<'a>(vpn: FortiVPNTunnel) -> Result, NetworkError> { + // TODO: how to choose the CIDR? + let ip_cidr = wire::IpCidr::new(vpn.ip_addr().into(), 24); + let mut device = PPPDevice::new(vpn); let mut config = iface::Config::new(smoltcp::wire::HardwareAddress::Ip); config.random_seed = rand::random(); let mut iface = iface::Interface::new(config, &mut device, smoltcp::time::Instant::now()); - // TODO: use IP address provided by the VPN server. - let local_addr = IpAddr::V4(Ipv4Addr::new(192, 168, 8, 1)); - let ip_cidr = wire::IpCidr::new(local_addr.into(), 24); - iface.update_ip_addrs(|ip_addrs| { if let Err(err) = ip_addrs.push(ip_cidr) { log::error!("Failed to add IP address to virtual interface: {}", err); @@ -68,6 +67,8 @@ impl Network<'_> { self.copy_all_data(); self.iface .poll(timestamp, &mut self.device, &mut self.sockets); + self.device.receive_data().await?; + self.device.send_data().await?; match self.iface.poll_delay(timestamp, &self.sockets) { Some(poll_delay) => { let timeout_at = tokio::time::Instant::now() @@ -187,11 +188,7 @@ impl Network<'_> { .insert(socket_handle, Some(response)); } Command::Bridge(socket_handle, reader, writer) => { - let socket_tunnel = SocketTunnel { - socket_handle, - reader, - writer, - }; + let socket_tunnel = SocketTunnel { reader, writer }; self.bridges.insert(socket_handle, socket_tunnel); } } @@ -215,7 +212,6 @@ impl Network<'_> { } struct SocketTunnel { - socket_handle: iface::SocketHandle, reader: tokio::net::tcp::OwnedReadHalf, writer: tokio::net::tcp::OwnedWriteHalf, } @@ -237,8 +233,47 @@ pub enum Command { } struct PPPDevice { - mtu: usize, - buf: [u8; 1500], + vpn: FortiVPNTunnel, + read_packet: [u8; MTU_SIZE], + write_packet: [u8; MTU_SIZE], + read_packet_size: usize, + write_packet_size: usize, +} + +impl PPPDevice { + fn new(vpn: FortiVPNTunnel) -> PPPDevice { + PPPDevice { + vpn, + read_packet: [0u8; MTU_SIZE], + write_packet: [0u8; MTU_SIZE], + read_packet_size: 0, + write_packet_size: 0, + } + } +} + +impl PPPDevice { + async fn receive_data(&mut self) -> Result<(), NetworkError> { + if self.read_packet_size > 0 { + // Data is not consumed yet. + return Ok(()); + } + self.read_packet_size = self.vpn.read_packet(&mut self.read_packet).await?; + Ok(()) + } + + async fn send_data(&mut self) -> Result<(), NetworkError> { + if self.write_packet_size == 0 { + // No data to send. + return Ok(()); + } + + let buf = &self.write_packet[..self.write_packet_size]; + self.write_packet_size = 0; + + self.vpn.send_packet(buf).await?; + Ok(()) + } } impl phy::Device for PPPDevice { @@ -252,27 +287,35 @@ impl phy::Device for PPPDevice { fn receive( &mut self, - timestamp: smoltcp::time::Instant, + _timestamp: smoltcp::time::Instant, ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - // TODO: set buffer size to match MTU. - // TODO: check if new data is available, copy into a buffer and return TX/RS tokens. - // See https://docs.rs/smoltcp/latest/src/smoltcp/phy/raw_socket.rs.html for more information. - None - /* + if self.read_packet_size == 0 || self.write_packet_size != 0 { + return None; + } Some(( - PPPDeviceRxToken { buf: &self.buf }, - PPPDeviceTxToken { buf: &self.buf }, + PPPDeviceRxToken { + read_packet: &mut self.read_packet[..self.read_packet_size], + }, + PPPDeviceTxToken { + write_packet: &mut self.write_packet, + write_packet_size: &mut self.write_packet_size, + }, )) - */ } - fn transmit(&mut self, timestamp: smoltcp::time::Instant) -> Option> { - Some(PPPDeviceTxToken { buf: &self.buf }) + fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option> { + if self.write_packet_size != 0 { + return None; + } + Some(PPPDeviceTxToken { + write_packet: &mut self.write_packet, + write_packet_size: &mut self.write_packet_size, + }) } fn capabilities(&self) -> phy::DeviceCapabilities { let mut caps = phy::DeviceCapabilities::default(); - caps.max_transmission_unit = self.mtu; + caps.max_transmission_unit = MTU_SIZE; caps.max_burst_size = Some(1); caps.medium = phy::Medium::Ip; caps @@ -280,23 +323,23 @@ impl phy::Device for PPPDevice { } struct PPPDeviceRxToken<'a> { - buf: &'a [u8], + read_packet: &'a mut [u8], } impl<'a> phy::RxToken for PPPDeviceRxToken<'a> { - fn consume(self, f: F) -> R + fn consume(mut self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - let mut buf = [0u8; 1500]; - let result = f(&mut buf); - //println!("Received packet {:?}", buf); + let result = f(&mut self.read_packet); + println!("Received packet {:?}", self.read_packet); result } } struct PPPDeviceTxToken<'a> { - buf: &'a [u8], + write_packet: &'a mut [u8], + write_packet_size: &'a mut usize, } impl<'a> phy::TxToken for PPPDeviceTxToken<'a> { @@ -304,10 +347,9 @@ impl<'a> phy::TxToken for PPPDeviceTxToken<'a> { where F: FnOnce(&mut [u8]) -> R, { - let mut buf = [0u8; 1500]; - let result = f(&mut buf[..len]); - println!("About to send packet {:?}", &buf[..len]); - result + // f will copy data into a provided buffer (avoid allocations). + *self.write_packet_size = len; + f(&mut self.write_packet[..len]) } } @@ -316,6 +358,7 @@ pub enum NetworkError { Internal(&'static str), Connect(socket::tcp::ConnectError), Io(io::Error), + Forti(crate::fortivpn::FortiError), } impl fmt::Display for NetworkError { @@ -328,6 +371,9 @@ impl fmt::Display for NetworkError { Self::Io(ref e) => { write!(f, "IO error: {}", e) } + Self::Forti(ref e) => { + write!(f, "VPN error: {}", e) + } } } } @@ -338,6 +384,7 @@ impl error::Error for NetworkError { Self::Internal(_msg) => None, Self::Connect(ref err) => Some(err), Self::Io(ref err) => Some(err), + Self::Forti(ref err) => Some(err), } } } @@ -359,3 +406,9 @@ impl From for NetworkError { Self::Io(err) } } + +impl From for NetworkError { + fn from(err: crate::fortivpn::FortiError) -> NetworkError { + Self::Forti(err) + } +}