Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d544cf6c3 |
6 changed files with 684 additions and 43 deletions
127
Cargo.lock
generated
127
Cargo.lock
generated
|
|
@ -8,6 +8,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"crc32fast",
|
||||||
"nix",
|
"nix",
|
||||||
"notify",
|
"notify",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -15,10 +16,26 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
@ -34,6 +51,21 @@ version = "1.0.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.75"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
|
@ -64,6 +96,15 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
|
|
@ -73,6 +114,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.31.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -93,6 +140,17 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-uring"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.3",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
|
@ -143,6 +201,15 @@ version = "2.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -201,6 +268,15 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
|
|
@ -266,6 +342,12 @@ version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
|
|
@ -322,12 +404,28 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
|
|
@ -368,6 +466,35 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.47.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"bytes",
|
||||||
|
"io-uring",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.41"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.99"
|
anyhow = "1.0.99"
|
||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
|
crc32fast = "1.5.0"
|
||||||
nix = { version = "0.30.1", features = ["ptrace", "process", "uio", "signal"] }
|
nix = { version = "0.30.1", features = ["ptrace", "process", "uio", "signal"] }
|
||||||
notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
|
@ -13,9 +14,11 @@ regex = "1.11.2"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.143"
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
|
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "time", "net", "io-util", "fs"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["fmt", "ansi"] }
|
tracing-subscriber = { version = "0.3.19", features = ["fmt", "ansi"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ptrace"]
|
default = ["ffs_proxy"]
|
||||||
ptrace = []
|
ptrace = []
|
||||||
|
ffs_proxy = []
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
|
#[cfg(feature = "ptrace")]
|
||||||
pub mod ptrace;
|
pub mod ptrace;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffs_proxy")]
|
||||||
|
pub mod proxy;
|
||||||
|
|
|
||||||
510
src/backend/proxy.rs
Normal file
510
src/backend/proxy.rs
Normal file
|
|
@ -0,0 +1,510 @@
|
||||||
|
use std::{
|
||||||
|
ffi::CString,
|
||||||
|
fs::OpenOptions,
|
||||||
|
io::Write,
|
||||||
|
iter::Enumerate,
|
||||||
|
os::fd::{AsRawFd, FromRawFd},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, anyhow};
|
||||||
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
|
use crc32fast::Hasher;
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
|
net::TcpStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::traits::EnumerateValue;
|
||||||
|
|
||||||
|
pub const FFS_EP0: &str = "/dev/usb-ffs/adb/ep0";
|
||||||
|
pub const FFS_EP1: &str = "/dev/usb-ffs/adb/ep1";
|
||||||
|
pub const FFS_EP2: &str = "/dev/usb-ffs/adb/ep2";
|
||||||
|
|
||||||
|
pub const ENDPOINT_XFER_BULK: u8 = 2;
|
||||||
|
|
||||||
|
// FFS Descriptors
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct FfsDescHeadV2 {
|
||||||
|
magic: u32, // FUNCTIONFS_DESCRIPTORS_MAGIC_V2 = 0xB8 0x4B 0x3F 0x94
|
||||||
|
length: u32,
|
||||||
|
flags: u32, // FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct UsbInterfaceDescriptor {
|
||||||
|
bLength: u8,
|
||||||
|
bDescriptiorType: u8,
|
||||||
|
bInterfaceNumber: u8,
|
||||||
|
bAlternateSetting: u8,
|
||||||
|
bNumEndpoints: u8,
|
||||||
|
bInterfaceClass: u8,
|
||||||
|
bInterfaceSubClass: u8,
|
||||||
|
bInterfaceProtocol: u8,
|
||||||
|
iInterface: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// REF: https://vovkos.github.io/doxyrest/samples/libusb/struct_libusb_endpoint_descriptor.html
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct UsbEndpointDescriptor {
|
||||||
|
/// Size of descriptor (bytes)
|
||||||
|
bLength: u8,
|
||||||
|
/// Descriptor type
|
||||||
|
bDescriptorType: u8,
|
||||||
|
/// Address of endpoint
|
||||||
|
/// Bit 0-3 = endpoint number
|
||||||
|
/// Bit 4-6 = reserved
|
||||||
|
/// Bit 7 = direction
|
||||||
|
bEndpointAddress: u8,
|
||||||
|
/// Attributes apply to endpoint
|
||||||
|
/// Bit 0-1 = transfer type
|
||||||
|
/// Bit 2-3 = isochronus endpoint
|
||||||
|
/// Bit 4-5 = isochronus endpoint
|
||||||
|
/// Bit 6-7 = reserved
|
||||||
|
bmAttributes: u8,
|
||||||
|
/// Packet size capable of send/receive
|
||||||
|
wMaxPacketSize: u16,
|
||||||
|
/// Interval for polling endpoint for data transfer
|
||||||
|
bInterval: u8,
|
||||||
|
/// Audio only: rate at which synchronization feedback is provided
|
||||||
|
bRefresh: u8, // not used for bulk
|
||||||
|
/// Audio only: address if synch endpoint
|
||||||
|
bSynchAddress: u8, // not used for bulk
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum DescriptorType {
|
||||||
|
LIBUSB_DT_DEVICE = 0x01,
|
||||||
|
LIBUSB_DT_CONFIG,
|
||||||
|
LIBUSB_DT_STRING,
|
||||||
|
LIBUSB_DT_INTERFACE,
|
||||||
|
LIBUSB_DT_ENDPOINT,
|
||||||
|
LIBUSB_DT_BOS = 0x0f,
|
||||||
|
LIBUSB_DT_DEVICE_CAPABILITY,
|
||||||
|
LIBUSB_DT_HID = 0x21,
|
||||||
|
LIBUSB_DT_REPORT,
|
||||||
|
LIBUSB_DT_PHYSICAL,
|
||||||
|
LIBUSB_DT_HUB = 0x29,
|
||||||
|
LIBUSB_DT_SUPERSPEED_HUB,
|
||||||
|
LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumerateValue<u8> for DescriptorType {
|
||||||
|
fn name(&self) -> String
|
||||||
|
where
|
||||||
|
Self: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u8
|
||||||
|
where
|
||||||
|
Self: Clone,
|
||||||
|
{
|
||||||
|
self.clone() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(val: u8) -> Self {
|
||||||
|
return unsafe { std::mem::transmute::<_, Self>(val) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum EndpointDirection {
|
||||||
|
ENDPOINT_IN = 0x80, // device -> host
|
||||||
|
ENDPOINT_OUT = 0x00, // host -> device
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumerateValue<u8> for EndpointDirection {
|
||||||
|
fn name(&self) -> String
|
||||||
|
where
|
||||||
|
Self: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u8
|
||||||
|
where
|
||||||
|
Self: Clone,
|
||||||
|
{
|
||||||
|
self.clone() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(val: u8) -> Self {
|
||||||
|
return unsafe { std::mem::transmute::<_, Self>(val) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn any_as_bytes<T: Sized>(p: &T) -> &[u8] {
|
||||||
|
std::slice::from_raw_parts((p as *const T) as *const u8, size_of::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build tiny FS+HS descriptor set for ADB
|
||||||
|
/// class 0xFF
|
||||||
|
/// subclass 0x42
|
||||||
|
/// proto 0x01
|
||||||
|
fn write_ffs_descriptors(ep0: &mut std::fs::File) -> anyhow::Result<()> {
|
||||||
|
let interface = UsbInterfaceDescriptor {
|
||||||
|
bLength: size_of::<UsbInterfaceDescriptor>() as u8,
|
||||||
|
bDescriptiorType: DescriptorType::LIBUSB_DT_INTERFACE.value(),
|
||||||
|
bInterfaceNumber: 0,
|
||||||
|
bAlternateSetting: 0,
|
||||||
|
bNumEndpoints: 2,
|
||||||
|
bInterfaceClass: 0xFF,
|
||||||
|
bInterfaceSubClass: 0x42,
|
||||||
|
bInterfaceProtocol: 0x01,
|
||||||
|
iInterface: 1,
|
||||||
|
};
|
||||||
|
let fs_out = UsbEndpointDescriptor {
|
||||||
|
bLength: 7,
|
||||||
|
bDescriptorType: DescriptorType::LIBUSB_DT_ENDPOINT.value(),
|
||||||
|
bEndpointAddress: 0x01 | EndpointDirection::ENDPOINT_OUT.value(),
|
||||||
|
bmAttributes: ENDPOINT_XFER_BULK,
|
||||||
|
wMaxPacketSize: 64u16.to_le(),
|
||||||
|
bInterval: 0,
|
||||||
|
bRefresh: 0,
|
||||||
|
bSynchAddress: 0,
|
||||||
|
};
|
||||||
|
let fs_in = UsbEndpointDescriptor {
|
||||||
|
bLength: 7,
|
||||||
|
bDescriptorType: DescriptorType::LIBUSB_DT_ENDPOINT.value(),
|
||||||
|
bEndpointAddress: 0x82 | EndpointDirection::ENDPOINT_IN.value(),
|
||||||
|
bmAttributes: ENDPOINT_XFER_BULK,
|
||||||
|
wMaxPacketSize: 64u16.to_le(),
|
||||||
|
bInterval: 0,
|
||||||
|
bRefresh: 0,
|
||||||
|
bSynchAddress: 0,
|
||||||
|
};
|
||||||
|
let hs_out = UsbEndpointDescriptor {
|
||||||
|
wMaxPacketSize: 512u16.to_le(),
|
||||||
|
..fs_out
|
||||||
|
};
|
||||||
|
let hs_in = UsbEndpointDescriptor {
|
||||||
|
wMaxPacketSize: 512u16.to_le(),
|
||||||
|
..fs_in
|
||||||
|
};
|
||||||
|
|
||||||
|
// header
|
||||||
|
const MAGIC_V2: u32 = 0xB84B3F94;
|
||||||
|
// body
|
||||||
|
let body_len = size_of::<UsbInterfaceDescriptor>()
|
||||||
|
+ 2 * size_of::<UsbEndpointDescriptor>()
|
||||||
|
+ size_of::<UsbInterfaceDescriptor>()
|
||||||
|
+ 2 * size_of::<UsbEndpointDescriptor>();
|
||||||
|
|
||||||
|
let header = FfsDescHeadV2 {
|
||||||
|
magic: MAGIC_V2,
|
||||||
|
length: (size_of::<FfsDescHeadV2>() + body_len) as u32,
|
||||||
|
flags: 0x3,
|
||||||
|
};
|
||||||
|
|
||||||
|
// write descriptors
|
||||||
|
let mut buf = Vec::with_capacity(header.length as usize);
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&header) });
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&interface) });
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&fs_out) });
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&fs_in) });
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&interface) });
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&hs_out) });
|
||||||
|
buf.extend_from_slice(unsafe { any_as_bytes(&hs_in) });
|
||||||
|
|
||||||
|
ep0.write_all(&buf)?;
|
||||||
|
|
||||||
|
// header v2
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct FfsStrHeadV2 {
|
||||||
|
magic: u32,
|
||||||
|
length: u32,
|
||||||
|
str_count: u32,
|
||||||
|
lang_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const STR_MAGIC_V2: u32 = 0xC6 | (0x85 << 8) | (0x93 << 16) | (0x9F << 24);
|
||||||
|
let sh = FfsStrHeadV2 {
|
||||||
|
magic: STR_MAGIC_V2,
|
||||||
|
length: 0,
|
||||||
|
str_count: 1,
|
||||||
|
lang_count: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let s0 = CString::new("ADB Interface").unwrap();
|
||||||
|
let mut sbuf = Vec::new();
|
||||||
|
sbuf.extend_from_slice(unsafe { any_as_bytes(&sh) });
|
||||||
|
sbuf.extend_from_slice(&0x0409u16.to_le_bytes());
|
||||||
|
sbuf.extend_from_slice(s0.as_bytes_with_nul());
|
||||||
|
|
||||||
|
let len_le = (sbuf.len() as u32).to_le_bytes();
|
||||||
|
sbuf[4..8].copy_from_slice(&len_le);
|
||||||
|
ep0.write_all(&sbuf)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADB Helper
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct AdbHdr {
|
||||||
|
cmd: u32,
|
||||||
|
arg0: u32,
|
||||||
|
arg1: u32,
|
||||||
|
data_len: u32,
|
||||||
|
data_crc32: u32,
|
||||||
|
magic: u32,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum ADB_COMMAND {
|
||||||
|
CNXN = 0x4E584E43,
|
||||||
|
AUTH = 0x48545541,
|
||||||
|
OPEN = 0x4E45504F,
|
||||||
|
OKAY = 0x59414B4F,
|
||||||
|
CLSE = 0x45534C43,
|
||||||
|
WRTE = 0x45545257,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumerateValue<u32> for ADB_COMMAND {
|
||||||
|
fn name(&self) -> String
|
||||||
|
where
|
||||||
|
Self: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u32
|
||||||
|
where
|
||||||
|
Self: Clone,
|
||||||
|
{
|
||||||
|
self.clone() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(val: u32) -> Self {
|
||||||
|
return unsafe { std::mem::transmute::<_, Self>(val) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hdr_ok(h: &AdbHdr) -> bool {
|
||||||
|
h.magic == (h.cmd ^ 0xffffffff)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_frame_r<R: AsyncReadExt + Unpin>(
|
||||||
|
r: &mut R,
|
||||||
|
buf: &mut BytesMut,
|
||||||
|
) -> anyhow::Result<(AdbHdr, Vec<u8>)> {
|
||||||
|
while buf.len() < 24 {
|
||||||
|
let n = r.read_buf(buf).await?;
|
||||||
|
if n == 0 {
|
||||||
|
return Err(anyhow!("eof"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut h = AdbHdr {
|
||||||
|
cmd: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
|
||||||
|
arg0: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
|
||||||
|
arg1: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
|
||||||
|
data_len: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
|
||||||
|
data_crc32: u32::from_le_bytes(buf[16..20].try_into().unwrap()),
|
||||||
|
magic: u32::from_le_bytes(buf[20..24].try_into().unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !hdr_ok(&h) {
|
||||||
|
return Err(anyhow!("bad magic"));
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.advance(24);
|
||||||
|
while buf.len() < h.data_len as usize {
|
||||||
|
let n = r.read_buf(buf).await?;
|
||||||
|
if n == 0 {
|
||||||
|
return Err(anyhow!("eof"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let payload = buf.split_to(h.data_len as usize).to_vec();
|
||||||
|
|
||||||
|
Ok((h, payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_frame_w<W: AsyncWriteExt + Unpin>(
|
||||||
|
w: &mut W,
|
||||||
|
mut h: AdbHdr,
|
||||||
|
payload: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(payload);
|
||||||
|
h.data_len = payload.len() as u32;
|
||||||
|
h.data_crc32 = hasher.finalize();
|
||||||
|
h.magic = h.cmd ^ 0xffffffff;
|
||||||
|
|
||||||
|
let mut b = BytesMut::with_capacity(24 + payload.len());
|
||||||
|
for v in [h.cmd, h.arg0, h.arg1, h.data_len, h.data_crc32, h.magic] {
|
||||||
|
b.put_u32_le(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.extend_from_slice(payload);
|
||||||
|
w.write_all(&b).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn service_from_open_payload(bytes: &[u8]) -> String {
|
||||||
|
let s = std::str::from_utf8(bytes).unwrap_or_default();
|
||||||
|
s.trim_end_matches('\0').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suspicious(service: &str) -> bool {
|
||||||
|
let s = service.to_ascii_lowercase();
|
||||||
|
s.starts_with("shell:su")
|
||||||
|
|| s.contains("setenforce")
|
||||||
|
|| s.starts_with("tcpip:")
|
||||||
|
|| s.contains("pm grant")
|
||||||
|
|| s.contains("appops")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reject_stream_to_host<W: AsyncWriteExt + Unpin>(
|
||||||
|
w: &mut W,
|
||||||
|
local_id: u32,
|
||||||
|
reason: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// OKAY L,0 (ack stream so host accepts WRTE)
|
||||||
|
write_frame_w(
|
||||||
|
w,
|
||||||
|
AdbHdr {
|
||||||
|
cmd: ADB_COMMAND::OKAY.value(),
|
||||||
|
arg0: local_id,
|
||||||
|
arg1: 0,
|
||||||
|
data_len: 0,
|
||||||
|
data_crc32: 0,
|
||||||
|
magic: 0,
|
||||||
|
},
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// WRTE L,0 with reason
|
||||||
|
write_frame_w(
|
||||||
|
w,
|
||||||
|
AdbHdr {
|
||||||
|
cmd: ADB_COMMAND::WRTE.value(),
|
||||||
|
arg0: local_id,
|
||||||
|
arg1: 0,
|
||||||
|
data_len: 0,
|
||||||
|
data_crc32: 0,
|
||||||
|
magic: 0,
|
||||||
|
},
|
||||||
|
reason.as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// CLSE L,0
|
||||||
|
write_frame_w(
|
||||||
|
w,
|
||||||
|
AdbHdr {
|
||||||
|
cmd: ADB_COMMAND::CLSE.value(),
|
||||||
|
arg0: local_id,
|
||||||
|
arg1: 0,
|
||||||
|
data_len: 0,
|
||||||
|
data_crc32: 0,
|
||||||
|
magic: 0,
|
||||||
|
},
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// probing check IN vs OUT
|
||||||
|
fn open_ffs_endpoints() -> anyhow::Result<(std::fs::File, std::fs::File)> {
|
||||||
|
let f1 = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(FFS_EP1)
|
||||||
|
.with_context(|| format!("open {FFS_EP1}"))?;
|
||||||
|
let f2 = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(FFS_EP2)
|
||||||
|
.with_context(|| format!("open {FFS_EP2}"))?;
|
||||||
|
|
||||||
|
let mut a = f1.try_clone()?;
|
||||||
|
let mut b = f2.try_clone()?;
|
||||||
|
let w1 = a.write(&[0u8; 0]).map(|_| true).unwrap_or(false);
|
||||||
|
let w2 = b.write(&[0u8; 0]).map(|_| true).unwrap_or(false);
|
||||||
|
|
||||||
|
let w1b = if !w1 {
|
||||||
|
a.write(&[0x41]).map(|n| n == 1).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
w1
|
||||||
|
};
|
||||||
|
let w2b = if !w2 {
|
||||||
|
b.write(&[0x41]).map(|n| n == 1).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
w2
|
||||||
|
};
|
||||||
|
|
||||||
|
if w1b && !w2b {
|
||||||
|
Ok((f1, f2))
|
||||||
|
} else if w2b && !w1b {
|
||||||
|
Ok((f2, f1))
|
||||||
|
} else {
|
||||||
|
Ok((f2, f1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run() -> anyhow::Result<()> {
|
||||||
|
let mut ep0 = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(FFS_EP0)
|
||||||
|
.context("open ep0")?;
|
||||||
|
write_ffs_descriptors(&mut ep0).context("write FFS descriptors")?;
|
||||||
|
|
||||||
|
let (ep_in_file, ep_out_file) = open_ffs_endpoints().context("open endpoints")?;
|
||||||
|
|
||||||
|
let mut ffs_in =
|
||||||
|
tokio::fs::File::from_std(unsafe { std::fs::File::from_raw_fd(ep_in_file.as_raw_fd()) });
|
||||||
|
let mut ffs_out =
|
||||||
|
tokio::fs::File::from_std(unsafe { std::fs::File::from_raw_fd(ep_out_file.as_raw_fd()) });
|
||||||
|
|
||||||
|
let mut adbd = loop {
|
||||||
|
match TcpStream::connect("127.0.0.1:5566").await {
|
||||||
|
Ok(s) => break s,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("wait adbd 5566: {e}");
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut hb = BytesMut::with_capacity(64 * 1024);
|
||||||
|
let mut db = BytesMut::with_capacity(64 * 1024);
|
||||||
|
|
||||||
|
println!("proxy ready");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
h2d = read_frame_r(&mut ffs_out, &mut hb) => {
|
||||||
|
let (h, payload) = match h2d { Ok(v)=>v, Err(e)=> { eprintln!("ffs_out read: {e}"); break; } };
|
||||||
|
if h.cmd == ADB_COMMAND::OPEN.value() {
|
||||||
|
let service = service_from_open_payload(&payload);
|
||||||
|
if suspicious(&service) {
|
||||||
|
let reason = format!("rejected by policy: {service}");
|
||||||
|
if let Err(e) = reject_stream_to_host(&mut ffs_in, h.arg0, &reason).await {
|
||||||
|
eprintln!("reject write: {e}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue; // do not forward to adbd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = write_frame_w(&mut adbd, h, &payload).await {
|
||||||
|
eprintln!("to adbd: {e}"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d2h = read_frame_r(&mut adbd, &mut db) => {
|
||||||
|
let (h, payload) = match d2h { Ok(v)=>v, Err(e)=> { eprintln!("adbd read: {e}"); break; } };
|
||||||
|
if let Err(e) = write_frame_w(&mut ffs_in, h, &payload).await {
|
||||||
|
eprintln!("to host: {e}"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
53
src/main.rs
53
src/main.rs
|
|
@ -1,10 +1,13 @@
|
||||||
use std::{path::PathBuf, sync::{Arc, Mutex}, time::Duration};
|
use std::{
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use notify::{RecommendedWatcher, Watcher};
|
use notify::{RecommendedWatcher, Watcher};
|
||||||
use rule::RuleSet;
|
use rule::RuleSet;
|
||||||
use tracing::{info, error};
|
use tracing::{error, info};
|
||||||
use tracing_subscriber::FmtSubscriber;
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
mod adb;
|
mod adb;
|
||||||
|
|
@ -12,9 +15,13 @@ mod backend;
|
||||||
mod broadcaster;
|
mod broadcaster;
|
||||||
mod config;
|
mod config;
|
||||||
mod rule;
|
mod rule;
|
||||||
|
mod traits;
|
||||||
|
|
||||||
fn pidof(name: &str) -> Option<i32> {
|
fn pidof(name: &str) -> Option<i32> {
|
||||||
let out = std::process::Command::new("pidof").arg(name).output().ok()?;
|
let out = std::process::Command::new("pidof")
|
||||||
|
.arg(name)
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||||
s.split_whitespace().next()?.parse::<i32>().ok()
|
s.split_whitespace().next()?.parse::<i32>().ok()
|
||||||
}
|
}
|
||||||
|
|
@ -33,40 +40,12 @@ fn watch_config(path: PathBuf, cfg: Arc<Mutex<Config>>) -> notify::Result<Recomm
|
||||||
w.watch(&saved_path, notify::RecursiveMode::NonRecursive)?;
|
w.watch(&saved_path, notify::RecursiveMode::NonRecursive)?;
|
||||||
Ok(w)
|
Ok(w)
|
||||||
}
|
}
|
||||||
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let sub = FmtSubscriber::builder().without_time().with_target(false).finish();
|
#[cfg(feature = "ffs_proxy")]
|
||||||
tracing::subscriber::set_global_default(sub).ok();
|
|
||||||
|
|
||||||
let mut args = std::env::args().skip(1);
|
|
||||||
let cfg_path = args.next().filter(|s| s=="--config").and_then(|_| args.next()).unwrap_or_else(|| "/data/adb/modules/adbguard/adbguard.json".into());
|
|
||||||
let cfg0 = Config::load(&cfg_path)?;
|
|
||||||
let cfg = Arc::new(Mutex::new(cfg0));
|
|
||||||
let _w = watch_config((&cfg_path).into(), cfg.clone()).ok();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let pid = loop {
|
|
||||||
if let Some(p) = pidof("adbd"){
|
|
||||||
break p;
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("attaching to adbd {}", pid);
|
|
||||||
|
|
||||||
let snapshot = {
|
|
||||||
cfg.lock().unwrap().clone()
|
|
||||||
};
|
|
||||||
let rules = RuleSet::from_cfg(&snapshot);
|
|
||||||
let action = snapshot.broadcast_action.clone();
|
|
||||||
|
|
||||||
#[cfg(feature = "ptrace")]
|
|
||||||
{
|
{
|
||||||
if let Err(e) = backend::ptrace::run(pid, rules, action, snapshot.debounce(), snapshot.max_payload_peek){
|
backend::proxy::run().await?;
|
||||||
error!("backend exited, {e:?}");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
|
||||||
|
|
||||||
}
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/traits.rs
Normal file
19
src/traits.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub trait EnumerateValue<T> {
|
||||||
|
/// get enum name
|
||||||
|
fn name(&self) -> String
|
||||||
|
where
|
||||||
|
Self: Debug,
|
||||||
|
{
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get value of enum
|
||||||
|
fn value(&self) -> T
|
||||||
|
where
|
||||||
|
Self: Clone;
|
||||||
|
|
||||||
|
/// get enum from expected type
|
||||||
|
fn get(val: T) -> Self;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue