diff --git a/.gitignore b/.gitignore index 626fb83..96b825a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ BuildManifest.plist Image.dmg Image.dmg.trustcache .DS_Store +*.pcap diff --git a/Cargo.lock b/Cargo.lock index 0b705c1..052ddc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,12 +138,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.8.0" @@ -184,20 +178,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder_slice" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b294e30387378958e8bf8f4242131b930ea615ff81e8cac2440cea0a6013190" -dependencies = [ - "byteorder", -] - [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "c2rust-bitfields" @@ -205,7 +190,16 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "367e5d1b30f28be590b6b3868da1578361d29d9bfac516d22f497d28ed7c9055" dependencies = [ - "c2rust-bitfields-derive", + "c2rust-bitfields-derive 0.19.0", +] + +[[package]] +name = "c2rust-bitfields" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46dc7d2bffa0d0b3d47eb2dc69973466858281446c2ac9f6d8a10e92ab1017df" +dependencies = [ + "c2rust-bitfields-derive 0.20.0", ] [[package]] @@ -219,6 +213,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "c2rust-bitfields-derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe1117afa5937ce280034e31fa1e84ed1824a252f75380327eed438535333f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cc" version = "1.2.10" @@ -380,38 +385,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "defmt" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "defmt-parser" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" -dependencies = [ - "thiserror 2.0.11", -] - [[package]] name = "deranged" version = "0.3.11" @@ -421,17 +394,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive-into-owned" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d94d81e3819a7b06a8638f448bc6339371ca9b6076a99d4a43eece3c4c923" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "digest" version = "0.10.7" @@ -577,6 +539,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -584,6 +561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -592,6 +570,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -608,6 +597,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -626,10 +626,16 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -648,7 +654,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba121d81ab5ea05b0cd5858516266800bf965531a794f7ac58e3eeb804f364f" dependencies = [ - "bitflags 2.8.0", + "bitflags", "libc", "windows-sys 0.59.0", ] @@ -701,31 +707,12 @@ dependencies = [ "tracing", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.5.0" @@ -970,27 +957,29 @@ dependencies = [ [[package]] name = "idevice" -version = "0.1.22" +version = "0.1.24" dependencies = [ "async-recursion", "base64", "byteorder", + "bytes", "env_logger", + "futures", "indexmap", "json", "log", "ns-keyed-archive", "openssl", - "pcap-file", "plist", + "rand", "reqwest", "serde", "serde_json", "sha2", - "smoltcp", - "thiserror 2.0.11", + "thiserror", "tokio", "tokio-openssl", + "tun-rs 2.0.8", "uuid", ] @@ -1006,7 +995,7 @@ dependencies = [ "plist", "sha2", "tokio", - "tun-rs", + "tun-rs 1.5.0", "ureq", ] @@ -1136,12 +1125,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "managed" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" - [[package]] name = "memchr" version = "2.7.4" @@ -1206,7 +1189,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.8.0", + "bitflags", "cfg-if", "cfg_aliases 0.1.1", "libc", @@ -1219,7 +1202,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -1243,7 +1226,7 @@ checksum = "f237a10fe003123daa55a74b63a0b0a65de1671b2d128711ffe5886891a8f77f" dependencies = [ "clap", "plist", - "thiserror 2.0.11", + "thiserror", ] [[package]] @@ -1273,7 +1256,7 @@ version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ - "bitflags 2.8.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1350,17 +1333,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "pcap-file" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1f139757b058f9f37b76c48501799d12c9aa0aa4c0d4c980b062ee925d1b2" -dependencies = [ - "byteorder_slice", - "derive-into-owned", - "thiserror 1.0.69", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -1416,25 +1388,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.96", + "zerocopy", ] [[package]] @@ -1464,13 +1423,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.8.0", + "bitflags", ] [[package]] @@ -1573,7 +1562,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1654,7 +1643,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.8.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1756,22 +1745,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "smoltcp" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "cfg-if", - "defmt", - "heapless", - "libc", - "log", - "managed", -] - [[package]] name = "socket2" version = "0.5.8" @@ -1854,7 +1827,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.8.0", + "bitflags", "core-foundation", "system-configuration-sys", ] @@ -1883,33 +1856,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", + "thiserror-impl", ] [[package]] @@ -1966,9 +1919,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -2095,11 +2048,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53141e64197ff7e758b8152615e50bb4a3b18c970738876e7906d31f242c7d6e" dependencies = [ - "bitflags 2.8.0", + "bitflags", "blocking", "byteorder", "bytes", - "c2rust-bitfields", + "c2rust-bitfields 0.19.0", "cfg-if", "encoding_rs", "getifaddrs", @@ -2110,13 +2063,41 @@ dependencies = [ "mac_address", "nix 0.29.0", "scopeguard", - "thiserror 2.0.11", + "thiserror", "tokio", "windows-sys 0.59.0", - "winreg", + "winreg 0.52.0", "wintun-bindings", ] +[[package]] +name = "tun-rs" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397f7515d2ab25d87dbb89af39b6813c1df6a89703003d7939c1f6bbc0fbeed9" +dependencies = [ + "bitflags", + "blocking", + "byteorder", + "bytes", + "c2rust-bitfields 0.20.0", + "cfg-if", + "encoding_rs", + "getifaddrs", + "ipnet", + "libc", + "libloading", + "log", + "mac_address", + "nix 0.29.0", + "scopeguard", + "thiserror", + "tokio", + "widestring", + "windows-sys 0.59.0", + "winreg 0.55.0", +] + [[package]] name = "typenum" version = "1.17.0" @@ -2341,6 +2322,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + [[package]] name = "winapi" version = "0.3.9" @@ -2551,16 +2538,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + [[package]] name = "wintun-bindings" version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a02981bed4592bcd271f9bfe154228ddbd2fd69e37a7d358da5d3a1251d696" dependencies = [ - "c2rust-bitfields", + "c2rust-bitfields 0.19.0", "libloading", "log", - "thiserror 2.0.11", + "thiserror", "windows-sys 0.59.0", ] @@ -2570,7 +2567,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.8.0", + "bitflags", ] [[package]] @@ -2609,6 +2606,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index cbcba8f..cb3d76f 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -2,7 +2,7 @@ name = "idevice" description = "A Rust library to interact with services on iOS devices." authors = ["Jackson Coxson"] -version = "0.1.23" +version = "0.1.24" edition = "2021" license = "MIT" documentation = "https://docs.rs/idevice" @@ -32,13 +32,15 @@ json = { version = "0.12", optional = true } byteorder = { version = "1.5", optional = true } reqwest = { version = "0.12", features = ["json"], optional = true } -smoltcp = { version = "0.12", optional = true } -pcap-file = { version = "2.0", optional = true } +rand = { version = "0.9", optional = true } +futures = { version = "0.3", optional = true } sha2 = { version = "0.10", optional = true } [dev-dependencies] tokio = { version = "1.43", features = ["fs"] } +tun-rs = { version = "2.0.8", features = ["async_tokio"] } +bytes = "1.10.1" [features] core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"] @@ -49,7 +51,7 @@ installation_proxy = [] misagent = [] mounter = ["dep:sha2"] tcp = ["tokio/net"] -tunnel_tcp_stack = ["dep:smoltcp", "tokio/sync", "dep:pcap-file"] +tunnel_tcp_stack = ["dep:rand", "dep:futures"] tss = ["dep:uuid", "dep:reqwest"] tunneld = ["dep:serde_json", "dep:json", "dep:reqwest"] usbmuxd = ["tokio/net"] diff --git a/idevice/src/core_device_proxy/device.rs b/idevice/src/core_device_proxy/device.rs deleted file mode 100644 index 94d0ff7..0000000 --- a/idevice/src/core_device_proxy/device.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Jackson Coxson - -use log::{debug, warn}; -use pcap_file::pcapng::PcapNgWriter; -use smoltcp::{ - phy::{self, Device, DeviceCapabilities, Medium}, - time::Instant, -}; -use tokio::sync::{ - mpsc::{error::TryRecvError, unbounded_channel, UnboundedReceiver, UnboundedSender}, - oneshot::{channel, Sender}, -}; - -use crate::IdeviceError; - -use super::CoreDeviceProxy; - -pub struct ProxyDevice { - sender: UnboundedSender<(Vec, Sender>)>, - receiver: UnboundedReceiver, IdeviceError>>, - mtu: usize, -} - -impl ProxyDevice { - pub fn new(mut proxy: CoreDeviceProxy) -> Self { - let (sender, mut stack_recv) = unbounded_channel(); - let (stack_send, receiver) = unbounded_channel(); - let mtu = proxy.mtu as usize; - - tokio::task::spawn(async move { - loop { - tokio::select! { - res = proxy.recv() => { - // debug!("stack recv: {res:02X?}"); - if stack_send.send(res).is_err() { - warn!("Interface failed to recv"); - break; - } - } - pkt = stack_recv.recv() => { - let pkt: (Vec, Sender>) = match pkt { - Some(p) => p, - None => { - warn!("Interface sender closed"); - break; - } - }; - - // debug!("stack send: {:02X?}", pkt.0); - pkt.1.send(proxy.send(&pkt.0).await.err()).ok(); - - } - } - } - }); - - Self { - sender, - receiver, - mtu, - } - } -} - -impl Device for ProxyDevice { - type RxToken<'a> = RxToken; - type TxToken<'a> = TxToken; - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.medium = Medium::Ip; - caps.max_transmission_unit = self.mtu; - caps - } - - fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - match self.receiver.try_recv() { - Ok(Ok(buffer)) => { - let rx = RxToken { buffer }; - let tx = TxToken { - sender: self.sender.clone(), - }; - Some((rx, tx)) - } - Ok(Err(e)) => { - warn!("Failed to recv message: {e:?}"); - None - } - Err(TryRecvError::Disconnected) => { - warn!("Proxy sender is closed"); - None - } - Err(TryRecvError::Empty) => None, - } - } - - fn transmit(&mut self, _timestamp: Instant) -> Option> { - Some(TxToken { - sender: self.sender.clone(), - }) - } -} - -#[doc(hidden)] -pub struct RxToken { - buffer: Vec, -} - -impl phy::RxToken for RxToken { - fn consume(self, f: F) -> R - where - F: FnOnce(&[u8]) -> R, - { - f(&self.buffer[..]) - } -} - -#[doc(hidden)] -pub struct TxToken { - sender: UnboundedSender<(Vec, Sender>)>, -} - -impl phy::TxToken for TxToken { - fn consume(self, len: usize, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - let mut buffer = vec![0; len]; - let result = f(&mut buffer); - let (tx, rx) = channel(); - match self.sender.send((buffer, tx)) { - Ok(_) => { - if let Err(e) = rx.blocking_recv() { - warn!("Failed to send to idevice: {e:?}"); - } - } - Err(err) => warn!("Failed to send: {}", err), - } - result - } -} diff --git a/idevice/src/core_device_proxy/interface.rs b/idevice/src/core_device_proxy/interface.rs deleted file mode 100644 index 0563a92..0000000 --- a/idevice/src/core_device_proxy/interface.rs +++ /dev/null @@ -1,277 +0,0 @@ -// Jackson Coxson - -use std::{ - collections::HashMap, - io::ErrorKind, - net::{IpAddr, Ipv6Addr}, - pin::Pin, - str::FromStr, - task::{Context, Poll}, -}; - -use log::{debug, warn}; -use smoltcp::{ - iface::{Config, Interface, SocketHandle, SocketSet}, - socket::tcp, - time::Instant, - wire::{IpAddress, IpCidr}, -}; -use tokio::{ - io::{self, AsyncRead, AsyncWrite}, - sync::mpsc::{error::TryRecvError, unbounded_channel, UnboundedReceiver, UnboundedSender}, -}; - -use crate::IdeviceError; - -const TCP_BUFFER_SIZE: usize = 1024 * 1024; - -pub struct TunnelInterface { - thread_sender: UnboundedSender, -} - -enum ThreadMessage { - NewTcp((u16, UnboundedSender>, UnboundedReceiver>)), - // left for possible UDP/other in the future?? -} - -struct TunnelSocketHandle { - handle: SocketHandle, - sender: UnboundedSender>, - receiver: UnboundedReceiver>, -} - -#[derive(Debug)] -pub struct Channel { - sender: UnboundedSender>, - receiver: UnboundedReceiver>, -} - -impl TunnelInterface { - pub fn new(proxy: super::CoreDeviceProxy) -> Result { - let ip: IpAddr = match IpAddr::from_str(&proxy.handshake.client_parameters.address) { - Ok(i) => i, - Err(e) => { - warn!("Failed to parse IP as IP address: {e:?}"); - return Err(IdeviceError::UnexpectedResponse); - } - }; - let device_ip: Ipv6Addr = match proxy.handshake.server_address.parse() { - Ok(d) => d, - Err(_) => { - warn!("Device IP isn't parsable as IPv6"); - return Err(IdeviceError::UnexpectedResponse); - } - }; - let mut device = super::device::ProxyDevice::new(proxy); - let config = Config::new(smoltcp::wire::HardwareAddress::Ip); - - // Create the interface - let mut interface = Interface::new(config, &mut device, smoltcp::time::Instant::now()); - - // Add the IPv6 address to the interface - let ip = match ip { - IpAddr::V4(_) => { - warn!("IP was IPv4, not 6"); - return Err(IdeviceError::UnexpectedResponse); - } - IpAddr::V6(ipv6_addr) => ipv6_addr, - }; - interface.update_ip_addrs(|addrs| { - // Add the IPv6 address with a prefix length (e.g., 64 for typical IPv6 subnets) - addrs.push(IpCidr::new(IpAddress::Ipv6(ip), 64)).unwrap(); - }); - - let (thread_sender, mut thread_rx) = unbounded_channel(); - - tokio::task::spawn_blocking(move || { - // based on https://github.com/smoltcp-rs/smoltcp/blob/main/examples/client.rs - let mut sockets = SocketSet::new(vec![]); - let mut handles: HashMap = HashMap::new(); - let mut last_port = 1024; // host to bind and index by - loop { - let timestamp = Instant::now(); - interface.poll(timestamp, &mut device, &mut sockets); - - let mut to_remove = Vec::new(); - let keys: Vec = handles.keys().cloned().collect(); - - for key in keys { - if let Some(handle) = handles.get_mut(&key) { - let socket = sockets.get_mut::(handle.handle); - - if socket.may_recv() { - if let Err(e) = socket.recv(|data| { - if !data.is_empty() && handle.sender.send(data.to_owned()).is_err() - { - warn!("Handle dropped"); - to_remove.push(key); - } - (data.len(), data) - }) { - warn!("Disconnected from socket: {e:?}"); - to_remove.push(key); - } - if socket.can_send() { - match handle.receiver.try_recv() { - Ok(data) => { - let queued = socket.send_slice(&data[..]).unwrap(); - if queued < data.len() { - log::error!("Failed to queue packet for send"); - } - } - Err(TryRecvError::Empty) => {} // wait - Err(TryRecvError::Disconnected) => { - debug!("handle is dropped"); - } - } - } else { - warn!("Can't send!!"); - } - } else if socket.may_send() { - debug!("close"); - socket.close(); - } - } - } - - // Remove the requested threads - for t in to_remove { - if let Some(h) = handles.remove(&t) { - sockets.remove(h.handle); - // When the struct is dropped, the unbounded_channel will close. - // Because the channel is closed, the next time recv or send is called will - // error. - } - } - - match thread_rx.try_recv() { - Ok(msg) => match msg { - ThreadMessage::NewTcp((port, tx, rx)) => { - // Create sockets - let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; TCP_BUFFER_SIZE]); - let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; TCP_BUFFER_SIZE]); - let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - let handle = sockets.add(tcp_socket); - let socket = sockets.get_mut::(handle); - socket - .connect(interface.context(), (device_ip, port), last_port) - .unwrap(); - handles.insert( - last_port, - TunnelSocketHandle { - handle, - sender: tx, - receiver: rx, - }, - ); - - last_port += 1; - } - }, - Err(TryRecvError::Disconnected) => { - debug!("Thread sender dropped"); - break; - } - Err(TryRecvError::Empty) => { - // noop - } - } - - let poll_delay = interface - .poll_delay(timestamp, &sockets) - .unwrap_or(smoltcp::time::Duration::from_millis(100)) - .millis(); - - std::thread::sleep(std::time::Duration::from_millis(poll_delay)); - } - }); - - Ok(Self { thread_sender }) - } - - pub fn connect_tcp(&mut self, port: u16) -> Result { - let (tx, rx) = unbounded_channel(); - let (rtx, rrx) = unbounded_channel(); - if self - .thread_sender - .send(ThreadMessage::NewTcp((port, rtx, rx))) - .is_err() - { - return Err(IdeviceError::TunnelThreadClosed); - } - - Ok(Channel { - sender: tx, - receiver: rrx, - }) - } -} - -impl Channel { - pub async fn send(&self, data: Vec) -> Result<(), IdeviceError> { - if let Err(e) = self.sender.send(data) { - warn!("Failed to send: {e:?}"); - return Err(IdeviceError::ChannelClosed); - } - Ok(()) - } - - pub async fn recv(&mut self) -> Result, IdeviceError> { - match self.receiver.recv().await { - Some(d) => Ok(d), - None => { - warn!("Failed to recv"); - Err(IdeviceError::ChannelClosed) - } - } - } -} - -impl AsyncRead for Channel { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - match Pin::new(&mut self.receiver).poll_recv(cx) { - Poll::Ready(Some(data)) => { - let len = data.len().min(buf.remaining()); - buf.put_slice(&data[..len]); - Poll::Ready(Ok(())) - } - Poll::Ready(None) => { - warn!("unexpected eof"); - Poll::Ready(Err(io::Error::new( - ErrorKind::UnexpectedEof, - "Channel closed", - ))) - } - Poll::Pending => Poll::Pending, - } - } -} - -impl AsyncWrite for Channel { - fn poll_write( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - let data = buf.to_vec(); - match self.sender.send(data) { - Ok(_) => Poll::Ready(Ok(buf.len())), - Err(e) => { - warn!("Failed to send data: {e:?}"); - Poll::Ready(Err(io::Error::new(ErrorKind::BrokenPipe, "Channel closed"))) - } - } - } - - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } -} diff --git a/idevice/src/core_device_proxy/mod.rs b/idevice/src/core_device_proxy/mod.rs index de781dc..aed7381 100644 --- a/idevice/src/core_device_proxy/mod.rs +++ b/idevice/src/core_device_proxy/mod.rs @@ -1,10 +1,5 @@ // Jackson Coxson -#[cfg(feature = "tunnel_tcp_stack")] -mod device; -#[cfg(feature = "tunnel_tcp_stack")] -mod interface; - use crate::{lockdownd::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use byteorder::{BigEndian, WriteBytesExt}; @@ -167,7 +162,17 @@ impl CoreDeviceProxy { } #[cfg(feature = "tunnel_tcp_stack")] - pub fn create_software_tunnel(self) -> Result { - interface::TunnelInterface::new(self) + pub fn create_software_tunnel(self) -> Result { + let our_ip = self + .handshake + .client_parameters + .address + .parse::()?; + let their_ip = self.handshake.server_address.parse::()?; + Ok(crate::tcp::adapter::Adapter::new( + Box::new(self.idevice.socket.unwrap()), + our_ip, + their_ip, + )) } } diff --git a/idevice/src/debug_proxy.rs b/idevice/src/debug_proxy.rs index 5ceddb7..338801a 100644 --- a/idevice/src/debug_proxy.rs +++ b/idevice/src/debug_proxy.rs @@ -9,8 +9,8 @@ use crate::{IdeviceError, ReadWrite}; pub const SERVICE_NAME: &str = "com.apple.internal.dt.remote.debugproxy"; -pub struct DebugProxyClient { - pub socket: Box, +pub struct DebugProxyClient { + pub socket: R, pub noack_mode: bool, } @@ -25,14 +25,18 @@ impl DebugserverCommand { } } -impl DebugProxyClient { - pub fn new(socket: Box) -> Self { +impl DebugProxyClient { + pub fn new(socket: R) -> Self { Self { socket, noack_mode: false, } } + pub fn into_inner(self) -> R { + self.socket + } + pub async fn send_command( &mut self, command: DebugserverCommand, diff --git a/idevice/src/dvt/process_control.rs b/idevice/src/dvt/process_control.rs index 0dcea36..ad45d30 100644 --- a/idevice/src/dvt/process_control.rs +++ b/idevice/src/dvt/process_control.rs @@ -3,18 +3,18 @@ use log::warn; use plist::{Dictionary, Value}; -use crate::{dvt::message::AuxValue, IdeviceError}; +use crate::{dvt::message::AuxValue, IdeviceError, ReadWrite}; use super::remote_server::{Channel, RemoteServerClient}; const IDENTIFIER: &str = "com.apple.instruments.server.services.processcontrol"; -pub struct ProcessControlClient<'a> { - channel: Channel<'a>, +pub struct ProcessControlClient<'a, R: ReadWrite> { + channel: Channel<'a, R>, } -impl<'a> ProcessControlClient<'a> { - pub async fn new(client: &'a mut RemoteServerClient) -> Result { +impl<'a, R: ReadWrite> ProcessControlClient<'a, R> { + pub async fn new(client: &'a mut RemoteServerClient) -> Result { let channel = client.make_channel(IDENTIFIER).await?; // Drop `&mut client` before continuing Ok(Self { channel }) diff --git a/idevice/src/dvt/remote_server.rs b/idevice/src/dvt/remote_server.rs index 7031066..8d859a9 100644 --- a/idevice/src/dvt/remote_server.rs +++ b/idevice/src/dvt/remote_server.rs @@ -14,20 +14,20 @@ use super::message::AuxValue; pub const INSTRUMENTS_MESSAGE_TYPE: u32 = 2; -pub struct RemoteServerClient { - idevice: Box, +pub struct RemoteServerClient { + idevice: R, current_message: u32, new_channel: u32, channels: HashMap>, } -pub struct Channel<'a> { - client: &'a mut RemoteServerClient, +pub struct Channel<'a, R: ReadWrite> { + client: &'a mut RemoteServerClient, channel: u32, } -impl RemoteServerClient { - pub fn new(idevice: Box) -> Result { +impl RemoteServerClient { + pub fn new(idevice: R) -> Result { let mut channels = HashMap::new(); channels.insert(0, VecDeque::new()); Ok(Self { @@ -38,7 +38,7 @@ impl RemoteServerClient { }) } - pub fn root_channel(&mut self) -> Channel { + pub fn root_channel(&mut self) -> Channel { Channel { client: self, channel: 0, @@ -48,7 +48,7 @@ impl RemoteServerClient { pub async fn make_channel( &mut self, identifier: impl Into, - ) -> Result { + ) -> Result, IdeviceError> { let code = self.new_channel; self.new_channel += 1; @@ -78,7 +78,7 @@ impl RemoteServerClient { self.build_channel(code) } - fn build_channel(&mut self, code: u32) -> Result { + fn build_channel(&mut self, code: u32) -> Result, IdeviceError> { Ok(Channel { client: self, channel: code, @@ -135,7 +135,7 @@ impl RemoteServerClient { } } -impl Channel<'_> { +impl Channel<'_, R> { pub async fn read_message(&mut self) -> Result { self.client.read_message(self.channel).await } diff --git a/idevice/src/http2/mod.rs b/idevice/src/http2/mod.rs index 52c2047..3e1391f 100644 --- a/idevice/src/http2/mod.rs +++ b/idevice/src/http2/mod.rs @@ -17,20 +17,22 @@ use h2::{ HTTP2_MAGIC, }; +use crate::ReadWrite; + pub type Channels = HashMap>, Receiver>)>; -pub struct Connection { - stream: crate::IdeviceSocket, +pub const INIT_STREAM: u32 = 0; +pub const ROOT_CHANNEL: u32 = 1; +pub const REPLY_CHANNEL: u32 = 3; + +pub struct Connection { + pub stream: R, channels: Channels, window_size: u32, } -impl Connection { - pub const INIT_STREAM: u32 = 0; - pub const ROOT_CHANNEL: u32 = 1; - pub const REPLY_CHANNEL: u32 = 3; - - pub async fn new(mut stream: crate::IdeviceSocket) -> Result { +impl Connection { + pub async fn new(mut stream: R) -> Result { stream.write_all(HTTP2_MAGIC).await?; Ok(Self { stream, @@ -181,12 +183,12 @@ mod tests { // apart of spec we are allowed to send frames before reading any from the server. // 'INIT_STREAM'/0 applies to all stream_ids. client - .send_frame(WindowUpdateFrame::new(Connection::INIT_STREAM, 983041)) + .send_frame(WindowUpdateFrame::new(INIT_STREAM, 983041)) .await .unwrap(); // We create stream_id '1' by sending Header frame. - let mut frame = Frame::new(Connection::ROOT_CHANNEL, 5, FrameType::Headers); + let mut frame = Frame::new(ROOT_CHANNEL, 5, FrameType::Headers); frame.set_body( [ 0x41, 0x89, 0x2, 0xe0, 0x5c, 0xb, 0x82, 0xe0, 0x40, 0x10, 0x7f, 0x82, 0x84, 0x86, @@ -199,12 +201,12 @@ mod tests { // when server sends 'Settings' on a streamId that the client hasn't sent one on. // then we must send them back one. client - .send_frame(Frame::new(Connection::ROOT_CHANNEL, 1, FrameType::Settings)) + .send_frame(Frame::new(ROOT_CHANNEL, 1, FrameType::Settings)) .await .unwrap(); client - .write_streamid(Connection::ROOT_CHANNEL, b"nibba\x00".to_vec()) + .write_streamid(ROOT_CHANNEL, b"nibba\x00".to_vec()) .await .unwrap(); // 'END_HEADERS' is sent before data. diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 7cbb8e1..7f57294 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -19,6 +19,8 @@ pub mod misagent; pub mod mounter; pub mod pairing_file; pub mod provider; +#[cfg(feature = "tunnel_tcp_stack")] +pub mod tcp; #[cfg(feature = "tss")] pub mod tss; #[cfg(feature = "tunneld")] @@ -313,22 +315,13 @@ pub enum IdeviceError { #[error("unknown channel")] UnknownChannel(u32), + #[error("cannot parse string as IpAddr")] + AddrParseError(#[from] std::net::AddrParseError), + #[cfg(feature = "dvt")] #[error("disable memory limit failed")] DisableMemoryLimitFailed, - #[cfg(feature = "tunnel_tcp_stack")] - #[error("failed to connect to TCP socket")] - ConnectionError(#[from] smoltcp::socket::tcp::ConnectError), - - #[cfg(feature = "tunnel_tcp_stack")] - #[error("tunnel thread is closed")] - TunnelThreadClosed, - - #[cfg(feature = "tunnel_tcp_stack")] - #[error("channel is closed")] - ChannelClosed, - #[error("not enough bytes, expected {1}, got {0}")] NotEnoughBytes(usize, usize), diff --git a/idevice/src/tcp/adapter.rs b/idevice/src/tcp/adapter.rs new file mode 100644 index 0000000..e38791a --- /dev/null +++ b/idevice/src/tcp/adapter.rs @@ -0,0 +1,399 @@ +// Jackson Coxson +// Simpler TCP stack + +use std::{future::Future, net::IpAddr, path::Path, sync::Arc, task::Poll}; + +use log::trace; +use tokio::{ + io::{AsyncRead, AsyncWrite, AsyncWriteExt}, + sync::Mutex, +}; + +use crate::ReadWrite; + +use super::packets::{Ipv4Packet, Ipv6Packet, ProtocolNumber, TcpFlags, TcpPacket}; + +#[derive(Clone, Debug, PartialEq)] +enum AdapterState { + Connected, + None, +} + +/// An extremely naive, limited and dangerous stack +/// Only one connection can be live at a time +/// Acks aren't tracked, and are silently ignored +/// Only use when the underlying transport is 100% reliable +#[derive(Debug)] +pub struct Adapter { + peer: Box, + host_ip: IpAddr, + peer_ip: IpAddr, + state: AdapterState, + + // TCP state + seq: u32, + ack: u32, + host_port: u16, + peer_port: u16, + + // Read buffer to cache unused bytes + read_buffer: Vec, + write_buffer: Vec, + + // Logging + pcap: Option>>, +} + +impl Adapter { + pub fn new(peer: Box, host_ip: IpAddr, peer_ip: IpAddr) -> Self { + Self { + peer, + host_ip, + peer_ip, + state: AdapterState::None, + seq: 0, + ack: 0, + host_port: 1024, + peer_port: 1024, + read_buffer: Vec::new(), + write_buffer: Vec::new(), + pcap: None, + } + } + + pub async fn connect(&mut self, port: u16) -> Result<(), std::io::Error> { + self.read_buffer = Vec::new(); + + // Randomize seq + self.seq = rand::random(); + self.ack = 0; + + // Choose a random port + self.host_port = rand::random(); + self.peer_port = port; + + // Create the TCP packet + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + self.host_port, + self.peer_port, + self.seq, + self.ack, + TcpFlags { + syn: true, + ..Default::default() + }, + u16::MAX - 1, + &[], + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet).await?; + + // Wait for the syn ack + let res = self.read_tcp_packet().await?; + if !(res.flags.syn && res.flags.ack) { + log::error!("Didn't get syn ack"); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "No syn ack", + )); + } + self.seq = self.seq.wrapping_add(1); + + // Ack back + self.ack().await?; + + self.state = AdapterState::Connected; + Ok(()) + } + + pub async fn pcap(&mut self, path: impl AsRef) -> Result<(), std::io::Error> { + let mut file = tokio::fs::File::create(path).await?; + + // https://wiki.wireshark.org/Development/LibpcapFileFormat + file.write_all(&0xa1b2c3d4_u32.to_le_bytes()).await?; // magic + file.write_all(&2_u16.to_le_bytes()).await?; // major version + file.write_all(&4_u16.to_le_bytes()).await?; // minor + file.write_all(&0_i32.to_le_bytes()).await?; // timezone + file.write_all(&0_u32.to_le_bytes()).await?; // accuracy + file.write_all(&(u16::MAX as u32).to_le_bytes()).await?; // snaplen + // https://www.tcpdump.org/linktypes.html + file.write_all(&101_u32.to_le_bytes()).await?; // link type + + self.pcap = Some(Arc::new(Mutex::new(file))); + Ok(()) + } + + async fn log_packet(&mut self, packet: &[u8]) -> Result<(), std::io::Error> { + if let Some(file) = &self.pcap { + super::log_packet(file, packet).await; + } + Ok(()) + } + + pub async fn close(&mut self) -> Result<(), std::io::Error> { + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + self.host_port, + self.peer_port, + self.seq, + self.ack, + TcpFlags { + fin: true, + ..Default::default() + }, + u16::MAX - 1, + &[], + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet).await?; + self.state = AdapterState::None; + Ok(()) + } + + async fn ack(&mut self) -> Result<(), std::io::Error> { + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + self.host_port, + self.peer_port, + self.seq, + self.ack, + TcpFlags { + ack: true, + ..Default::default() + }, + u16::MAX - 1, + &[], + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet).await?; + + Ok(()) + } + + /// Sends a packet + pub async fn psh(&mut self, data: &[u8]) -> Result<(), std::io::Error> { + trace!("pshing {} bytes", data.len()); + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + self.host_port, + self.peer_port, + self.seq, + self.ack, + TcpFlags { + psh: true, + ack: true, + ..Default::default() + }, + u16::MAX - 1, + data, + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet).await?; + + self.seq = self.seq.wrapping_add(data.len() as u32); + + Ok(()) + } + + /// Flushes the packets + async fn write_buffer_flush(&mut self) -> Result<(), std::io::Error> { + if self.write_buffer.is_empty() { + return Ok(()); + } + trace!("Flushing {} bytes", self.write_buffer.len()); + let write_buffer = self.write_buffer.clone(); + self.psh(&write_buffer).await?; + self.write_buffer = Vec::new(); + Ok(()) + } + + pub async fn recv(&mut self) -> Result, std::io::Error> { + loop { + let res = self.read_tcp_packet().await?; + if res.flags.psh || !res.payload.is_empty() { + self.ack().await?; + break Ok(res.payload); + } + if res.flags.rst { + self.state = AdapterState::None; + break Err(std::io::Error::new( + std::io::ErrorKind::ConnectionReset, + "Connection reset", + )); + } + if res.flags.fin { + self.ack().await?; + self.state = AdapterState::None; + break Err(std::io::Error::new( + std::io::ErrorKind::ConnectionReset, + "Connection reset", + )); + } + } + } + + /// Reads a packet and returns the payload + async fn read_ip_packet(&mut self) -> Result, std::io::Error> { + self.write_buffer_flush().await?; + Ok(loop { + match self.host_ip { + IpAddr::V4(_) => { + let packet = Ipv4Packet::from_reader(&mut self.peer, &self.pcap).await?; + trace!("IPv4 packet: {packet:#?}"); + if packet.protocol == 6 { + break packet.payload; + } + } + IpAddr::V6(_) => { + let packet = Ipv6Packet::from_reader(&mut self.peer, &self.pcap).await?; + trace!("IPv6 packet: {packet:#?}"); + if packet.next_header == 6 { + break packet.payload; + } + } + } + }) + } + + async fn read_tcp_packet(&mut self) -> Result { + let ip_packet = self.read_ip_packet().await?; + let tcp_packet = TcpPacket::parse(&ip_packet)?; + trace!("TCP packet: {tcp_packet:#?}"); + self.ack = tcp_packet.sequence_number + + if tcp_packet.payload.is_empty() { + 1 + } else { + tcp_packet.payload.len() as u32 + }; + Ok(tcp_packet) + } + + fn ip_wrap(&self, packet: &[u8]) -> Vec { + match self.host_ip { + IpAddr::V4(host_addr) => match self.peer_ip { + IpAddr::V4(peer_addr) => { + Ipv4Packet::create(host_addr, peer_addr, ProtocolNumber::Tcp, 255, packet) + } + IpAddr::V6(_) => { + panic!("non matching IP versions"); + } + }, + IpAddr::V6(host_addr) => match self.peer_ip { + IpAddr::V4(_) => { + panic!("non matching IP versions") + } + IpAddr::V6(peer_addr) => { + Ipv6Packet::create(host_addr, peer_addr, ProtocolNumber::Tcp, 255, packet) + } + }, + } + } +} + +impl AsyncRead for Adapter { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + // First, check if we have any cached data + if !self.read_buffer.is_empty() { + let to_copy = std::cmp::min(buf.remaining(), self.read_buffer.len()); + buf.put_slice(&self.read_buffer[..to_copy]); + + // Keep any remaining data in the buffer + if to_copy < self.read_buffer.len() { + self.read_buffer = self.read_buffer[to_copy..].to_vec(); + } else { + self.read_buffer.clear(); + } + + return std::task::Poll::Ready(Ok(())); + } + + // If no cached data and not connected, return error + if self.state != AdapterState::Connected { + return std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::NotConnected, + "Adapter not connected", + ))); + } + + // If no cached data, try to receive new data + let future = async { + match self.recv().await { + Ok(data) => { + let len = std::cmp::min(buf.remaining(), data.len()); + buf.put_slice(&data[..len]); + + // If we received more data than needed, cache the rest + if len < data.len() { + self.read_buffer = data[len..].to_vec(); + } + + Ok(()) + } + Err(e) => Err(e), + } + }; + + // Pin the future and poll it + futures::pin_mut!(future); + future.poll(cx) + } +} + +impl AsyncWrite for Adapter { + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + trace!("poll psh {}", buf.len()); + if self.state != AdapterState::Connected { + return std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::NotConnected, + "Adapter not connected", + ))); + } + self.write_buffer.extend_from_slice(buf); + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let future = async { + match self.write_buffer_flush().await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + }; + + // Pin the future and poll it + futures::pin_mut!(future); + future.poll(cx) + } + + fn poll_shutdown( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + // Create a future that can be polled + let future = async { self.close().await }; + + // Pin the future and poll it + futures::pin_mut!(future); + future.poll(cx) + } +} diff --git a/idevice/src/tcp/mod.rs b/idevice/src/tcp/mod.rs new file mode 100644 index 0000000..14ac081 --- /dev/null +++ b/idevice/src/tcp/mod.rs @@ -0,0 +1,231 @@ +// Jackson Coxson + +use std::{ + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +use log::debug; +use tokio::io::AsyncWriteExt; + +pub mod adapter; +pub mod packets; + +pub(crate) async fn log_packet(file: &Arc>, packet: &[u8]) { + debug!("Logging {} byte packet", packet.len()); + let packet = packet.to_vec(); + let file = file.to_owned(); + tokio::task::spawn(async move { + let mut file = file.lock().await; + file.write_all( + &(SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as u32) + .to_le_bytes(), + ) + .await + .unwrap(); + let micros = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_micros() + % 1_000_000_000; + file.write_all(&(micros as u32).to_le_bytes()) + .await + .unwrap(); + file.write_all(&(packet.len() as u32).to_le_bytes()) + .await + .unwrap(); + file.write_all(&(packet.len() as u32).to_le_bytes()) + .await + .unwrap(); + file.write_all(&packet).await.unwrap(); + }); +} + +#[cfg(test)] +mod tests { + use std::{ + net::{IpAddr, Ipv6Addr}, + str::FromStr, + }; + + use super::*; + + use adapter::Adapter; + use std::{ + pin::Pin, + task::{Context, Poll}, + }; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tun_rs::DeviceBuilder; + + use bytes::BytesMut; + use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + use tun_rs::AsyncDevice; + + pub struct AsyncDeviceWrapper { + device: AsyncDevice, + // Buffer to store unread data + buffer: BytesMut, + } + + impl std::fmt::Debug for AsyncDeviceWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsyncDeviceWrapper") + .field("buffer", &self.buffer) + .finish() + } + } + + impl AsyncRead for AsyncDeviceWrapper { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + // First, check if we have data in our buffer + let this = self.get_mut(); + + if !this.buffer.is_empty() { + // We have buffered data, copy as much as possible to the output buffer + let bytes_to_copy = std::cmp::min(this.buffer.len(), buf.remaining()); + let data_to_copy = this.buffer.split_to(bytes_to_copy); + buf.put_slice(&data_to_copy); + + return Poll::Ready(Ok(())); + } + + // If our buffer is empty, try to read more data + let mut temp_buf = vec![0u8; 4096]; // Temporary buffer with reasonable size + + match this.device.poll_recv(cx, &mut temp_buf) { + Poll::Ready(Ok(n)) => { + if n > 0 { + // Got some data, first fill the output buffer + let bytes_to_copy = std::cmp::min(n, buf.remaining()); + buf.put_slice(&temp_buf[..bytes_to_copy]); + + // If we have more data than fits in the output buffer, store in our internal buffer + if n > bytes_to_copy { + this.buffer.extend_from_slice(&temp_buf[bytes_to_copy..n]); + } + + Poll::Ready(Ok(())) + } else { + // Zero bytes read + Poll::Ready(Ok(())) + } + } + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } + } + + impl AsyncWrite for AsyncDeviceWrapper { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + self.device.poll_send(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + } + + const SERVER_PORT: u16 = 5555; + + #[tokio::test] + async fn local_tcp() { + env_logger::init(); + + let our_ip = Ipv6Addr::from_str("fd12:3456:789a::1").unwrap(); + let their_ip = Ipv6Addr::from_str("fd12:3456:789a::2").unwrap(); + let dev = DeviceBuilder::new() + .ipv6(their_ip, "ffff:ffff:ffff:ffff::") + .mtu(1420) + .build_async() + .expect("Failed to create tunnel. Are you root?"); + + println!("Created tunnel [{:?}] {}", dev.name(), their_ip); + + let mut adapter = Adapter::new( + Box::new(AsyncDeviceWrapper { + device: dev, + buffer: BytesMut::new(), + }), + IpAddr::V6(our_ip), + IpAddr::V6(their_ip), + ); + adapter.pcap("./local_tcp.pcap").await.expect("no pcap"); + + tokio::task::spawn(async move { + let listener = tokio::net::TcpListener::bind(format!("[::0]:{SERVER_PORT}")) + .await + .unwrap(); + while let Ok((mut stream, addr)) = listener.accept().await { + println!("Accepted connection from {addr:?}"); + + tokio::task::spawn(async move { + loop { + let mut buf = [0; 1024]; + let read_len = stream.read(&mut buf).await.unwrap(); + stream.write_all(&buf[..read_len]).await.unwrap(); + } + }); + } + }); + + println!("Attach Wireshark, press enter to continue\n"); + let mut buf = Vec::new(); + let _ = tokio::io::stdin().read(&mut buf).await.unwrap(); + + if let Err(e) = adapter.connect(SERVER_PORT).await { + println!("no connect: {e:?}"); + } + + if let Err(e) = adapter.write_all(&[1, 2, 3, 4, 5]).await { + println!("no send: {e:?}"); + } else { + let mut buf = [0u8; 4]; + match adapter.read_exact(&mut buf).await { + Ok(_) => println!("recv'd {buf:?}"), + Err(e) => println!("no recv: {e:?}"), + } + } + + if let Err(e) = adapter.write_all(&[69, 69, 42, 0, 1]).await { + println!("no send: {e:?}"); + } else { + let mut buf = [0u8; 6]; + match adapter.read_exact(&mut buf).await { + Ok(_) => println!("recv'd {buf:?}"), + Err(e) => println!("no recv: {e:?}"), + } + } + + if let Err(e) = adapter.close().await { + println!("no close: {e:?}"); + } + + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + println!("\n\npress enter"); + let mut buf = Vec::new(); + let _ = tokio::io::stdin().read(&mut buf).await.unwrap(); + } +} diff --git a/idevice/src/tcp/packets.rs b/idevice/src/tcp/packets.rs new file mode 100644 index 0000000..83f64eb --- /dev/null +++ b/idevice/src/tcp/packets.rs @@ -0,0 +1,716 @@ +// Jackson Coxson +// I couldn't find a lib that parses IP/TCP, so I guess we'll write our own + +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + sync::Arc, +}; + +use tokio::{ + io::{AsyncRead, AsyncReadExt}, + sync::Mutex, +}; + +pub enum ProtocolNumber { + Tcp = 6, +} + +#[derive(Debug)] +pub struct Ipv4Packet { + pub version: u8, // always 4 for IPv4 + pub ihl: u8, // len of header / 4 + pub tos: u8, // nobody can agree what this is for + pub total_length: u16, // length of packet in bytes + pub identification: u16, // ID from sender to help assemble datagram + pub flags: u8, // 3 bits; reserved: 0, may fragment: 0/1, last fragment 0 / more fragments 1 + pub fragment_offset: u16, // where in the datagram this belongs + // If Google can ignore fragments, so can we + pub ttl: u8, // max amount of time this packet can live + pub protocol: u8, // protocol number, 6 for TCP + pub header_checksum: u16, // wrapping add all the u16 in header, then invert all bits + pub source: Ipv4Addr, + pub destination: Ipv4Addr, + pub options: Vec, + pub payload: Vec, // if smoltcp can ignore options, so can we +} + +impl Ipv4Packet { + pub fn parse(packet: &[u8]) -> Option { + if packet.len() < 20 { + return None; + } + + let version_ihl = packet[0]; + let version = version_ihl >> 4; + let ihl = (version_ihl & 0x0F) * 4; // send help I don't understand bitwise ops + + if version != 4 || packet.len() < ihl as usize { + return None; + } + + let tos = packet[1]; + let total_length = u16::from_be_bytes([packet[2], packet[3]]); + let identification = u16::from_be_bytes([packet[4], packet[5]]); + let flags_fragment = u16::from_be_bytes([packet[6], packet[7]]); + let flags = (flags_fragment >> 13) as u8; + let fragment_offset = flags_fragment & 0x1FFF; + let ttl = packet[8]; + let protocol = packet[9]; + let header_checksum = u16::from_be_bytes([packet[10], packet[11]]); + let source = Ipv4Addr::new(packet[12], packet[13], packet[14], packet[15]); + let destination = Ipv4Addr::new(packet[16], packet[17], packet[18], packet[19]); + + let options_end = ihl as usize; + let options = if options_end > 20 { + packet[20..options_end].to_vec() + } else { + Vec::new() + }; + + let payload = if total_length as usize > options_end { + packet[options_end..total_length as usize].to_vec() + } else { + Vec::new() + }; + + Some(Self { + version, + ihl, + tos, + total_length, + identification, + flags, + fragment_offset, + ttl, + protocol, + header_checksum, + source, + destination, + options, + payload, + }) + } + + /// Asynchronously read an IPv4 packet from a Tokio AsyncRead source. + pub async fn from_reader( + reader: &mut R, + log: &Option>>, + ) -> Result { + let mut log_packet = Vec::new(); + + let mut header = [0u8; 20]; // Minimum IPv4 header size + reader.read_exact(&mut header).await?; + if log.is_some() { + log_packet.extend_from_slice(&header); + } + + let version_ihl = header[0]; + let version = version_ihl >> 4; + let ihl = (version_ihl & 0x0F) * 4; + + if version != 4 || ihl < 20 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid IPv4 header", + )); + } + + let tos = header[1]; + let total_length = u16::from_be_bytes([header[2], header[3]]); + let identification = u16::from_be_bytes([header[4], header[5]]); + let flags_fragment = u16::from_be_bytes([header[6], header[7]]); + let flags = (flags_fragment >> 13) as u8; + let fragment_offset = flags_fragment & 0x1FFF; + let ttl = header[8]; + let protocol = header[9]; + let header_checksum = u16::from_be_bytes([header[10], header[11]]); + let source = Ipv4Addr::new(header[12], header[13], header[14], header[15]); + let destination = Ipv4Addr::new(header[16], header[17], header[18], header[19]); + + // Read options if the header is larger than 20 bytes + let options_len = ihl as usize - 20; + let mut options = vec![0u8; options_len]; + if options_len > 0 { + reader.read_exact(&mut options).await?; + if log.is_some() { + log_packet.extend_from_slice(&options); + } + } + + // Read the payload + let payload_len = total_length as usize - ihl as usize; + let mut payload = vec![0u8; payload_len]; + reader.read_exact(&mut payload).await?; + if let Some(log) = log { + log_packet.extend_from_slice(&payload); + super::log_packet(log, &log_packet).await; + } + + Ok(Self { + version, + ihl, + tos, + total_length, + identification, + flags, + fragment_offset, + ttl, + protocol, + header_checksum, + source, + destination, + options, + payload, + }) + } + + pub fn create( + source: Ipv4Addr, + destination: Ipv4Addr, + protocol: ProtocolNumber, + ttl: u8, + payload: &[u8], + ) -> Vec { + let ihl: u8 = 5; + let total_length = (ihl as usize * 4 + payload.len()) as u16; + let identification: u16 = 0; + let flags_fragment: u16 = 0; + let header_checksum: u16 = 0; + + let mut packet = vec![0; total_length as usize]; + packet[0] = (4 << 4) | (ihl & 0x0F); + packet[1] = 0; + packet[2..4].copy_from_slice(&total_length.to_be_bytes()); + packet[4..6].copy_from_slice(&identification.to_be_bytes()); + packet[6..8].copy_from_slice(&flags_fragment.to_be_bytes()); + packet[8] = ttl; + packet[9] = protocol as u8; + packet[10..12].copy_from_slice(&header_checksum.to_be_bytes()); + packet[12..16].copy_from_slice(&source.octets()); + packet[16..20].copy_from_slice(&destination.octets()); + packet[20..].copy_from_slice(payload); + + Self::apply_checksum(&mut packet); + packet + } + + fn apply_checksum(packet: &mut [u8]) { + packet[10] = 0; + packet[11] = 0; + let mut checksum: u16 = 0; + for i in 0..packet.len() / 2 { + let word = u16::from_be_bytes([packet[i * 2], packet[(i * 2) + 1]]); + checksum = checksum.wrapping_add(word); + } + let checksum = checksum.to_be_bytes(); + packet[10] = checksum[0]; + packet[11] = checksum[1]; + } +} + +pub struct Ipv6Packet { + pub version: u8, + pub traffic_class: u8, + pub flow_label: u32, + pub payload_length: u16, + pub next_header: u8, + pub hop_limit: u8, + pub source: Ipv6Addr, + pub destination: Ipv6Addr, + pub payload: Vec, +} + +impl Ipv6Packet { + pub fn parse(packet: &[u8]) -> Option { + if packet.len() < 40 { + return None; + } + + let version = packet[0] >> 4; + if version != 6 { + return None; + } + + let traffic_class = ((packet[0] & 0x0F) << 4) | (packet[1] >> 4); + let flow_label = + ((packet[1] as u32 & 0x0F) << 16) | ((packet[2] as u32) << 8) | packet[3] as u32; + let payload_length = u16::from_be_bytes([packet[4], packet[5]]); + let next_header = packet[6]; + let hop_limit = packet[7]; + let source = Ipv6Addr::new( + u16::from_be_bytes([packet[8], packet[9]]), + u16::from_be_bytes([packet[10], packet[11]]), + u16::from_be_bytes([packet[12], packet[13]]), + u16::from_be_bytes([packet[14], packet[15]]), + u16::from_be_bytes([packet[16], packet[17]]), + u16::from_be_bytes([packet[18], packet[19]]), + u16::from_be_bytes([packet[20], packet[21]]), + u16::from_be_bytes([packet[22], packet[23]]), + ); + + let destination = Ipv6Addr::new( + u16::from_be_bytes([packet[24], packet[25]]), + u16::from_be_bytes([packet[26], packet[27]]), + u16::from_be_bytes([packet[28], packet[29]]), + u16::from_be_bytes([packet[30], packet[31]]), + u16::from_be_bytes([packet[32], packet[33]]), + u16::from_be_bytes([packet[34], packet[35]]), + u16::from_be_bytes([packet[36], packet[37]]), + u16::from_be_bytes([packet[38], packet[39]]), + ); + let payload = packet[40..].to_vec(); + + Some(Self { + version, + traffic_class, + flow_label, + payload_length, + next_header, + hop_limit, + source, + destination, + payload, + }) + } + + pub async fn from_reader( + reader: &mut R, + log: &Option>>, + ) -> Result { + let mut log_packet = Vec::new(); + let mut header = [0u8; 40]; // IPv6 header size is fixed at 40 bytes + reader.read_exact(&mut header).await?; + if log.is_some() { + log_packet.extend_from_slice(&header); + } + + let version = header[0] >> 4; + if version != 6 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid IPv6 header", + )); + } + + let traffic_class = ((header[0] & 0x0F) << 4) | ((header[1] & 0xF0) >> 4); + let flow_label = + ((header[1] as u32 & 0x0F) << 16) | ((header[2] as u32) << 8) | (header[3] as u32); + let payload_length = u16::from_be_bytes([header[4], header[5]]); + let next_header = header[6]; + let hop_limit = header[7]; + let source = Ipv6Addr::new( + u16::from_be_bytes([header[8], header[9]]), + u16::from_be_bytes([header[10], header[11]]), + u16::from_be_bytes([header[12], header[13]]), + u16::from_be_bytes([header[14], header[15]]), + u16::from_be_bytes([header[16], header[17]]), + u16::from_be_bytes([header[18], header[19]]), + u16::from_be_bytes([header[20], header[21]]), + u16::from_be_bytes([header[22], header[23]]), + ); + let destination = Ipv6Addr::new( + u16::from_be_bytes([header[24], header[25]]), + u16::from_be_bytes([header[26], header[27]]), + u16::from_be_bytes([header[28], header[29]]), + u16::from_be_bytes([header[30], header[31]]), + u16::from_be_bytes([header[32], header[33]]), + u16::from_be_bytes([header[34], header[35]]), + u16::from_be_bytes([header[36], header[37]]), + u16::from_be_bytes([header[38], header[39]]), + ); + + // Read the payload + let mut payload = vec![0u8; payload_length as usize]; + reader.read_exact(&mut payload).await?; + if let Some(log) = log { + log_packet.extend_from_slice(&payload); + super::log_packet(log, &log_packet).await; + } + + Ok(Self { + version, + traffic_class, + flow_label, + payload_length, + next_header, + hop_limit, + source, + destination, + payload, + }) + } + + pub fn create( + source: Ipv6Addr, + destination: Ipv6Addr, + next_header: ProtocolNumber, + hop_limit: u8, + payload: &[u8], + ) -> Vec { + let mut packet = Vec::with_capacity(40 + payload.len()); + + // Version (6) and Traffic Class (0) + let version_traffic_class = 6 << 4; + packet.push(version_traffic_class); + packet.push(0); // The rest of the Traffic Class and the start of the Flow Label + + // Flow Label (0) + let flow_label = 0u16; + packet.extend_from_slice(&flow_label.to_be_bytes()[..]); + + // Payload Length (length of the payload only) + packet.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + + // Next Header and Hop Limit + packet.push(next_header as u8); + packet.push(hop_limit); + + // Source and Destination Addresses + packet.extend_from_slice(&source.octets()); + packet.extend_from_slice(&destination.octets()); + + // Payload + packet.extend_from_slice(payload); + + packet + } +} + +impl std::fmt::Debug for Ipv6Packet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Ipv6Packet") + .field("version", &self.version) + .field("traffic_class", &self.traffic_class) + .field("flow_label", &self.flow_label) + .field("payload_length", &self.payload_length) + .field("next_header", &self.next_header) + .field("hop_limit", &self.hop_limit) + .field("source", &self.source) + .field("destination", &self.destination) + .field("payload len", &self.payload.len()) + .finish() + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct TcpFlags { + pub urg: bool, // Urgent pointer flag + pub ack: bool, // Acknowledgment flag + pub psh: bool, // Push flag + pub rst: bool, // Reset flag + pub syn: bool, // Synchronize flag + pub fin: bool, // Finish flag +} + +impl TcpFlags { + /// Create a new `TcpFlags` struct from a raw byte. + pub fn from_byte(flags: u8) -> Self { + Self { + urg: (flags & 0b0010_0000) != 0, // URG flag (bit 5) + ack: (flags & 0b0001_0000) != 0, // ACK flag (bit 4) + psh: (flags & 0b0000_1000) != 0, // PSH flag (bit 3) + rst: (flags & 0b0000_0100) != 0, // RST flag (bit 2) + syn: (flags & 0b0000_0010) != 0, // SYN flag (bit 1) + fin: (flags & 0b0000_0001) != 0, // FIN flag (bit 0) + } + } + + /// Convert the `TcpFlags` struct into a raw byte. + pub fn to_byte(&self) -> u8 { + let mut flags = 0u8; + if self.urg { + flags |= 0b0010_0000; + } + if self.ack { + flags |= 0b0001_0000; + } + if self.psh { + flags |= 0b0000_1000; + } + if self.rst { + flags |= 0b0000_0100; + } + if self.syn { + flags |= 0b0000_0010; + } + if self.fin { + flags |= 0b0000_0001; + } + flags + } +} + +pub struct TcpPacket { + pub source_port: u16, + pub destination_port: u16, + pub sequence_number: u32, + pub acknowledgment_number: u32, + pub data_offset: u8, // Header length in 32-bit words + pub flags: TcpFlags, // TCP flags + pub window_size: u16, + pub checksum: u16, + pub urgent_pointer: u16, + pub options: Vec, // Optional TCP options + pub payload: Vec, // TCP payload +} + +impl TcpPacket { + pub fn parse(packet: &[u8]) -> Result { + if packet.len() < 20 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Not enough bytes for TCP header", + )); + } + + let source_port = u16::from_be_bytes([packet[0], packet[1]]); + let destination_port = u16::from_be_bytes([packet[2], packet[3]]); + let sequence_number = u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]); + let acknowledgment_number = + u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]); + let data_offset = (packet[12] >> 4) * 4; // Convert from 32-bit words to bytes + let flags = TcpFlags::from_byte(packet[13]); // Parse flags + let window_size = u16::from_be_bytes([packet[14], packet[15]]); + let checksum = u16::from_be_bytes([packet[16], packet[17]]); + let urgent_pointer = u16::from_be_bytes([packet[18], packet[19]]); + + // Parse options if the header is longer than 20 bytes + let options_end = data_offset as usize; + let options = if options_end > 20 { + // packet[20..options_end].to_vec() + Vec::new() + } else { + Vec::new() + }; + + // Payload starts after the header + let payload = if packet.len() > options_end { + packet[options_end..].to_vec() + } else { + Vec::new() + }; + + Ok(Self { + source_port, + destination_port, + sequence_number, + acknowledgment_number, + data_offset, + flags, + window_size, + checksum, + urgent_pointer, + options, + payload, + }) + } + + #[allow(clippy::too_many_arguments)] + pub fn create( + source_ip: IpAddr, + destination_ip: IpAddr, + source_port: u16, + destination_port: u16, + sequence_number: u32, + acknowledgment_number: u32, + flags: TcpFlags, + window_size: u16, + payload: &[u8], + ) -> Vec { + let data_offset = 5_u8; // Header length in 32-bit words + let mut packet = Vec::with_capacity(20 + payload.len()); + + // Source and Destination Ports + packet.extend_from_slice(&source_port.to_be_bytes()); + packet.extend_from_slice(&destination_port.to_be_bytes()); + + // Sequence and Acknowledgment Numbers + packet.extend_from_slice(&sequence_number.to_be_bytes()); + packet.extend_from_slice(&acknowledgment_number.to_be_bytes()); + + // Data Offset and Flags + packet.push(data_offset << 4); // Data offset (4 bits) and reserved bits (4 bits) + packet.push(flags.to_byte()); // Flags byte + + // Window Size, Checksum (set to zero first), and Urgent Pointer + packet.extend_from_slice(&window_size.to_be_bytes()); + packet.extend_from_slice(&[0, 0]); // Checksum placeholder + packet.extend_from_slice(&[0, 0]); // Urgent pointer + + // No options, keeping it simple + packet.extend_from_slice(payload); + + // Compute checksum with the appropriate pseudo-header + let checksum = match (source_ip, destination_ip) { + (IpAddr::V4(src), IpAddr::V4(dest)) => { + let src_bytes = src.octets(); + let dest_bytes = dest.octets(); + Self::calculate_checksum(&packet, &src_bytes, &dest_bytes, false) + } + (IpAddr::V6(src), IpAddr::V6(dest)) => { + let src_bytes = src.octets(); + let dest_bytes = dest.octets(); + Self::calculate_checksum(&packet, &src_bytes, &dest_bytes, true) + } + _ => panic!("Source and destination IP versions must match"), + }; + + packet[16..18].copy_from_slice(&checksum.to_be_bytes()); + + packet + } + + fn calculate_checksum( + packet: &[u8], + source_ip: &[u8], + destination_ip: &[u8], + is_ipv6: bool, + ) -> u16 { + let mut sum = 0u32; + + if is_ipv6 { + // IPv6 pseudo-header + // Add source and destination addresses (128 bits each) + for chunk in source_ip.chunks(2) { + sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32; + } + for chunk in destination_ip.chunks(2) { + sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32; + } + + // Upper layer packet length (32 bits for IPv6) + let tcp_length = packet.len() as u32; + sum += (tcp_length >> 16) & 0xFFFF; + sum += tcp_length & 0xFFFF; + + // Next Header value (8 bits of zeros + 8 bits of protocol value) + sum += 6u32; // TCP protocol number + } else { + // IPv4 pseudo-header + // Add source and destination addresses (32 bits each) + for chunk in source_ip.chunks(2) { + sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32; + } + for chunk in destination_ip.chunks(2) { + sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32; + } + + // Zero byte + Protocol byte + sum += 6u32; // TCP protocol number + + // TCP segment length (16 bits) + sum += packet.len() as u32; + } + + // Create a copy of the packet with checksum field zeroed out + let mut packet_copy = packet.to_vec(); + if packet_copy.len() >= 18 { + packet_copy[16] = 0; + packet_copy[17] = 0; + } + + // Sum all 16-bit words in the packet + for chunk in packet_copy.chunks(2) { + let word = if chunk.len() == 2 { + u16::from_be_bytes([chunk[0], chunk[1]]) + } else { + u16::from_be_bytes([chunk[0], 0]) // Padding for odd-length packets + }; + sum += word as u32; + } + + // Fold sum to 16 bits + while sum >> 16 != 0 { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + // One's complement + !(sum as u16) + } +} + +impl std::fmt::Debug for TcpPacket { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TcpPacket") + .field("source_port", &self.source_port) + .field("destination_port", &self.destination_port) + .field("sequence_number", &self.sequence_number) + .field("acknowledgment_number", &self.acknowledgment_number) + .field("data_offset", &self.data_offset) + .field("flags", &self.flags) + .field("window_size", &self.window_size) + .field("checksum", &self.checksum) + .field("urgent_pointer", &self.urgent_pointer) + .field("options", &self.options) + .field("payload len", &self.payload.len()) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ipv4() { + let b1 = Ipv4Packet::create( + Ipv4Addr::new(127, 0, 0, 1), + Ipv4Addr::new(1, 1, 1, 1), + ProtocolNumber::Tcp, + 255, + &[1, 2, 3, 4, 5], + ); + println!("{b1:02X?}"); + + let ip1 = Ipv4Packet::parse(&b1); + println!("{ip1:#?}"); + } + + #[test] + fn ipv6() { + let b1 = Ipv6Packet::create( + Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), + Ipv6Addr::new(9, 10, 11, 12, 13, 14, 15, 16), + ProtocolNumber::Tcp, + 255, + &[1, 2, 3, 4, 5], + ); + println!("{b1:02X?}"); + + let ip1 = Ipv6Packet::parse(&b1); + println!("{ip1:#?}"); + } + + #[test] + fn tcp() { + let b1 = TcpPacket::create( + IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), + IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), + 1234, + 5678, + 420, + 6969, + TcpFlags { + urg: false, + ack: false, + psh: true, + rst: false, + syn: false, + fin: false, + }, + 5555, + &[1, 2, 3, 4, 5], + ); + let i1 = Ipv6Packet::create( + Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), + Ipv6Addr::new(9, 10, 11, 12, 13, 14, 15, 16), + ProtocolNumber::Tcp, + 255, + &b1, + ); + println!("{i1:02X?}"); + + let t1 = TcpPacket::parse(&b1); + println!("{t1:#?}"); + } +} diff --git a/idevice/src/xpc/mod.rs b/idevice/src/xpc/mod.rs index 82ee865..cb04424 100644 --- a/idevice/src/xpc/mod.rs +++ b/idevice/src/xpc/mod.rs @@ -7,7 +7,7 @@ use crate::{ self, h2::{SettingsFrame, WindowUpdateFrame}, }, - IdeviceError, + IdeviceError, ReadWrite, }; use error::XPCError; use format::{XPCFlag, XPCMessage, XPCObject}; @@ -18,8 +18,8 @@ pub mod cdtunnel; pub mod error; pub mod format; -pub struct XPCDevice { - pub connection: XPCConnection, +pub struct XPCDevice { + pub connection: XPCConnection, pub services: HashMap, } @@ -32,19 +32,17 @@ pub struct XPCService { pub service_version: Option, } -pub struct XPCConnection { - inner: http2::Connection, +pub struct XPCConnection { + pub(crate) inner: http2::Connection, root_message_id: u64, reply_message_id: u64, } -impl XPCDevice { - pub async fn new(stream: crate::IdeviceSocket) -> Result { +impl XPCDevice { + pub async fn new(stream: R) -> Result { let mut connection = XPCConnection::new(stream).await?; - let data = connection - .read_message(http2::Connection::ROOT_CHANNEL) - .await?; + let data = connection.read_message(http2::ROOT_CHANNEL).await?; let data = match data.message { Some(d) => match d @@ -132,14 +130,18 @@ impl XPCDevice { services, }) } + + pub fn into_inner(self) -> R { + self.connection.inner.stream + } } -impl XPCConnection { - pub const ROOT_CHANNEL: u32 = http2::Connection::ROOT_CHANNEL; - pub const REPLY_CHANNEL: u32 = http2::Connection::REPLY_CHANNEL; - const INIT_STREAM: u32 = http2::Connection::INIT_STREAM; +impl XPCConnection { + pub const ROOT_CHANNEL: u32 = http2::ROOT_CHANNEL; + pub const REPLY_CHANNEL: u32 = http2::REPLY_CHANNEL; + const INIT_STREAM: u32 = http2::INIT_STREAM; - pub async fn new(stream: crate::IdeviceSocket) -> Result { + pub async fn new(stream: R) -> Result { let mut client = http2::Connection::new(stream).await?; client .send_frame(SettingsFrame::new( diff --git a/tools/src/core_device_proxy_tun.rs b/tools/src/core_device_proxy_tun.rs index ac9e85f..2f9b457 100644 --- a/tools/src/core_device_proxy_tun.rs +++ b/tools/src/core_device_proxy_tun.rs @@ -3,7 +3,6 @@ use clap::{Arg, Command}; use idevice::{ core_device_proxy::{self}, - xpc::XPCDevice, IdeviceService, }; use tun_rs::AbstractDevice; @@ -63,57 +62,51 @@ async fn main() { let mut tun_proxy = core_device_proxy::CoreDeviceProxy::connect(&*provider) .await .expect("Unable to connect"); - let rsd_port = tun_proxy.handshake.server_rsd_port; - let mut tunnel = tun_proxy.create_software_tunnel().unwrap(); - let channel = tunnel.connect_tcp(rsd_port).unwrap(); - let client = XPCDevice::new(Box::new(channel)).await.expect("no xpc??"); - todo!(); + let dev = tun_rs::create(&tun_rs::Configuration::default()).unwrap(); + dev.add_address_v6( + tun_proxy + .handshake + .client_parameters + .address + .parse() + .unwrap(), + 32, + ) + .unwrap(); + dev.set_mtu(tun_proxy.handshake.client_parameters.mtu) + .unwrap(); + dev.set_network_address( + tun_proxy.handshake.client_parameters.address.clone(), + tun_proxy + .handshake + .client_parameters + .netmask + .parse() + .unwrap(), + Some(tun_proxy.handshake.server_address.parse().unwrap()), + ) + .unwrap(); - // let dev = tun_rs::create(&tun_rs::Configuration::default()).unwrap(); - // dev.add_address_v6( - // tun_proxy - // .handshake - // .client_parameters - // .address - // .parse() - // .unwrap(), - // 32, - // ) - // .unwrap(); - // dev.set_mtu(tun_proxy.handshake.client_parameters.mtu) - // .unwrap(); - // dev.set_network_address( - // tun_proxy.handshake.client_parameters.address.clone(), - // tun_proxy - // .handshake - // .client_parameters - // .netmask - // .parse() - // .unwrap(), - // Some(tun_proxy.handshake.server_address.parse().unwrap()), - // ) - // .unwrap(); - // - // let async_dev = tun_rs::AsyncDevice::new(dev).unwrap(); - // async_dev.enabled(true).unwrap(); - // println!("-----------------------------"); - // println!("tun device created: {:?}", async_dev.name()); - // println!("server address: {}", tun_proxy.handshake.server_address); - // println!("rsd port: {}", tun_proxy.handshake.server_rsd_port); - // println!("-----------------------------"); - // - // let mut buf = vec![0; 1500]; - // loop { - // tokio::select! { - // Ok(len) = async_dev.recv(&mut buf) => { - // println!("tun pkt: {:?}", &buf[..len]); - // tun_proxy.send(&buf[..len]).await.unwrap(); - // } - // Ok(res) = tun_proxy.recv() => { - // println!("dev pkt: {:?}", &res); - // async_dev.send(&res).await.unwrap(); - // } - // } - // } + let async_dev = tun_rs::AsyncDevice::new(dev).unwrap(); + async_dev.enabled(true).unwrap(); + println!("-----------------------------"); + println!("tun device created: {:?}", async_dev.name()); + println!("server address: {}", tun_proxy.handshake.server_address); + println!("rsd port: {}", tun_proxy.handshake.server_rsd_port); + println!("-----------------------------"); + + let mut buf = vec![0; 1500]; + loop { + tokio::select! { + Ok(len) = async_dev.recv(&mut buf) => { + println!("tun pkt: {:?}", &buf[..len]); + tun_proxy.send(&buf[..len]).await.unwrap(); + } + Ok(res) = tun_proxy.recv() => { + println!("dev pkt: {:?}", &res); + async_dev.send(&res).await.unwrap(); + } + } + } } diff --git a/tools/src/debug_proxy.rs b/tools/src/debug_proxy.rs index bfb14cc..f91011d 100644 --- a/tools/src/debug_proxy.rs +++ b/tools/src/debug_proxy.rs @@ -1,14 +1,12 @@ // Jackson Coxson -use std::{ - io::Write, - net::{IpAddr, SocketAddr}, - str::FromStr, -}; +use std::io::Write; use clap::{Arg, Command}; -use idevice::{debug_proxy::DebugProxyClient, tunneld::get_tunneld_devices, xpc::XPCDevice}; -use tokio::net::TcpStream; +use idevice::{ + core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, xpc::XPCDevice, + IdeviceService, +}; mod common; @@ -18,6 +16,18 @@ async fn main() { let matches = Command::new("remotexpc") .about("Get services from RemoteXPC") + .arg( + Arg::new("host") + .long("host") + .value_name("HOST") + .help("IP address of the device"), + ) + .arg( + Arg::new("pairing_file") + .long("pairing-file") + .value_name("PATH") + .help("Path to the pairing file"), + ) .arg( Arg::new("udid") .value_name("UDID") @@ -39,46 +49,41 @@ async fn main() { } let udid = matches.get_one::("udid"); + let pairing_file = matches.get_one::("pairing_file"); + let host = matches.get_one::("host"); - let socket = SocketAddr::new( - IpAddr::from_str("127.0.0.1").unwrap(), - idevice::tunneld::DEFAULT_PORT, - ); - let mut devices = get_tunneld_devices(socket) + let provider = + match common::get_provider(udid, host, pairing_file, "debug-proxy-jkcoxson").await { + Ok(p) => p, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + let proxy = CoreDeviceProxy::connect(&*provider) .await - .expect("Failed to get tunneld devices"); + .expect("no core proxy"); + let rsd_port = proxy.handshake.server_rsd_port; - let (_udid, device) = match udid { - Some(u) => ( - u.to_owned(), - devices.remove(u).expect("Device not in tunneld"), - ), - None => devices.into_iter().next().expect("No devices"), - }; + let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); + adapter.connect(rsd_port).await.expect("no RSD connect"); // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new( - TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port)) - .await - .unwrap(), - )) - .await - .unwrap(); + let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); // Get the debug proxy let service = client .services .get(idevice::debug_proxy::SERVICE_NAME) - .expect("Client did not contain debug proxy service"); + .expect("Client did not contain debug proxy service") + .to_owned(); - let stream = TcpStream::connect(SocketAddr::new( - IpAddr::from_str(&device.tunnel_address).unwrap(), - service.port, - )) - .await - .expect("Failed to connect"); + let mut adapter = client.into_inner(); + adapter.close().await.unwrap(); + adapter.connect(service.port).await.unwrap(); - let mut dp = DebugProxyClient::new(Box::new(stream)); + let mut dp = DebugProxyClient::new(Box::new(adapter)); println!("Shell connected!"); loop { diff --git a/tools/src/misagent.rs b/tools/src/misagent.rs index fc0d421..869d475 100644 --- a/tools/src/misagent.rs +++ b/tools/src/misagent.rs @@ -1,12 +1,7 @@ // Jackson Coxson -use std::path::PathBuf; - -use clap::{arg, value_parser, Arg, Command}; -use idevice::{ - lockdownd::LockdowndClient, misagent::MisagentClient, mounter::ImageMounter, - pretty_print_plist, IdeviceService, -}; +use clap::{Arg, Command}; +use idevice::{misagent::MisagentClient, pretty_print_plist, IdeviceService}; mod common; diff --git a/tools/src/process_control.rs b/tools/src/process_control.rs index bcfa49a..b7b607c 100644 --- a/tools/src/process_control.rs +++ b/tools/src/process_control.rs @@ -1,13 +1,7 @@ // Jackson Coxson -use std::{ - net::{IpAddr, SocketAddr}, - str::FromStr, -}; - use clap::{Arg, Command}; -use idevice::{tunneld::get_tunneld_devices, xpc::XPCDevice}; -use tokio::net::TcpStream; +use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService}; mod common; @@ -17,6 +11,18 @@ async fn main() { let matches = Command::new("process_control") .about("Query process control") + .arg( + Arg::new("host") + .long("host") + .value_name("HOST") + .help("IP address of the device"), + ) + .arg( + Arg::new("pairing_file") + .long("pairing-file") + .value_name("PATH") + .help("Path to the pairing file"), + ) .arg( Arg::new("udid") .value_name("UDID") @@ -44,50 +50,44 @@ async fn main() { } let udid = matches.get_one::("udid"); + let pairing_file = matches.get_one::("pairing_file"); + let host = matches.get_one::("host"); + + let provider = + match common::get_provider(udid, host, pairing_file, "heartbeat_client-jkcoxson").await { + Ok(p) => p, + Err(e) => { + eprintln!("{e}"); + return; + } + }; let bundle_id = matches .get_one::("bundle_id") .expect("No bundle ID specified"); - let socket = SocketAddr::new( - IpAddr::from_str("127.0.0.1").unwrap(), - idevice::tunneld::DEFAULT_PORT, - ); - let mut devices = get_tunneld_devices(socket) + let proxy = CoreDeviceProxy::connect(&*provider) .await - .expect("Failed to get tunneld devices"); + .expect("no core proxy"); + let rsd_port = proxy.handshake.server_rsd_port; - let (_udid, device) = match udid { - Some(u) => ( - u.to_owned(), - devices.remove(u).expect("Device not in tunneld"), - ), - None => devices.into_iter().next().expect("No devices"), - }; + let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); + adapter.connect(rsd_port).await.expect("no RSD connect"); // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new( - TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port)) - .await - .unwrap(), - )) - .await - .unwrap(); + let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); // Get the debug proxy let service = client .services .get(idevice::dvt::SERVICE_NAME) - .expect("Client did not contain DVT service"); + .expect("Client did not contain DVT service") + .to_owned(); - let stream = TcpStream::connect(SocketAddr::new( - IpAddr::from_str(&device.tunnel_address).unwrap(), - service.port, - )) - .await - .expect("Failed to connect"); + let mut adapter = client.into_inner(); + adapter.connect(service.port).await.unwrap(); let mut rs_client = - idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream)).unwrap(); + idevice::dvt::remote_server::RemoteServerClient::new(Box::new(adapter)).unwrap(); rs_client.read_message(0).await.expect("no read??"); let mut pc_client = idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) .await diff --git a/tools/src/remotexpc.rs b/tools/src/remotexpc.rs index 4526205..1b1be65 100644 --- a/tools/src/remotexpc.rs +++ b/tools/src/remotexpc.rs @@ -1,14 +1,8 @@ // Jackson Coxson // Print out all the RemoteXPC services -use std::{ - net::{IpAddr, SocketAddr}, - str::FromStr, -}; - use clap::{Arg, Command}; -use idevice::{tunneld::get_tunneld_devices, xpc::XPCDevice}; -use tokio::net::TcpStream; +use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService}; mod common; @@ -18,6 +12,18 @@ async fn main() { let matches = Command::new("remotexpc") .about("Get services from RemoteXPC") + .arg( + Arg::new("host") + .long("host") + .value_name("HOST") + .help("IP address of the device"), + ) + .arg( + Arg::new("pairing_file") + .long("pairing-file") + .value_name("PATH") + .help("Path to the pairing file"), + ) .arg( Arg::new("udid") .value_name("UDID") @@ -39,31 +45,28 @@ async fn main() { } let udid = matches.get_one::("udid"); + let pairing_file = matches.get_one::("pairing_file"); + let host = matches.get_one::("host"); - let socket = SocketAddr::new( - IpAddr::from_str("127.0.0.1").unwrap(), - idevice::tunneld::DEFAULT_PORT, - ); - let mut devices = get_tunneld_devices(socket) - .await - .expect("Failed to get tunneld devices"); - - let (_udid, device) = match udid { - Some(u) => ( - u.to_owned(), - devices.remove(u).expect("Device not in tunneld"), - ), - None => devices.into_iter().next().expect("No devices"), + let provider = match common::get_provider(udid, host, pairing_file, "remotexpc-jkcoxson").await + { + Ok(p) => p, + Err(e) => { + eprintln!("{e}"); + return; + } }; + let proxy = CoreDeviceProxy::connect(&*provider) + .await + .expect("no core proxy"); + let rsd_port = proxy.handshake.server_rsd_port; + + let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); + adapter.connect(rsd_port).await.expect("no RSD connect"); + // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new( - TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port)) - .await - .unwrap(), - )) - .await - .unwrap(); + let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); println!("{:#?}", client.services); }