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 = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"crc32fast",
|
||||
"nix",
|
||||
"notify",
|
||||
"once_cell",
|
||||
|
|
@ -15,10 +16,26 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"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]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
|
|
@ -34,6 +51,21 @@ version = "1.0.99"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
|
@ -64,6 +96,15 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
|
@ -73,6 +114,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.0"
|
||||
|
|
@ -93,6 +140,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
|
|
@ -143,6 +201,15 @@ version = "2.7.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
|
|
@ -201,6 +268,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
|
@ -266,6 +342,12 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
|
@ -322,12 +404,28 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
|
|
@ -368,6 +466,35 @@ dependencies = [
|
|||
"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]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
|||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
bytes = "1.10.1"
|
||||
crc32fast = "1.5.0"
|
||||
nix = { version = "0.30.1", features = ["ptrace", "process", "uio", "signal"] }
|
||||
notify = "8.2.0"
|
||||
once_cell = "1.21.3"
|
||||
|
|
@ -13,9 +14,11 @@ regex = "1.11.2"
|
|||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.143"
|
||||
thiserror = "2.0.16"
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "time", "net", "io-util", "fs"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["fmt", "ansi"] }
|
||||
|
||||
[features]
|
||||
default = ["ptrace"]
|
||||
ptrace = []
|
||||
default = ["ffs_proxy"]
|
||||
ptrace = []
|
||||
ffs_proxy = []
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
|
||||
#[cfg(feature = "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(())
|
||||
}
|
||||
59
src/main.rs
59
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 notify::{RecommendedWatcher, Watcher};
|
||||
use rule::RuleSet;
|
||||
use tracing::{info, error};
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
mod adb;
|
||||
|
|
@ -12,9 +15,13 @@ mod backend;
|
|||
mod broadcaster;
|
||||
mod config;
|
||||
mod rule;
|
||||
mod traits;
|
||||
|
||||
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();
|
||||
s.split_whitespace().next()?.parse::<i32>().ok()
|
||||
}
|
||||
|
|
@ -24,7 +31,7 @@ fn watch_config(path: PathBuf, cfg: Arc<Mutex<Config>>) -> notify::Result<Recomm
|
|||
let mut w = notify::recommended_watcher(move |res: Result<notify::Event, _>| {
|
||||
if let Ok(event) = res {
|
||||
if event.kind.is_modify() {
|
||||
if let Ok(newc) = Config::load(path.clone().to_str().unwrap()){
|
||||
if let Ok(newc) = Config::load(path.clone().to_str().unwrap()) {
|
||||
*cfg.lock().unwrap() = newc;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,40 +40,12 @@ fn watch_config(path: PathBuf, cfg: Arc<Mutex<Config>>) -> notify::Result<Recomm
|
|||
w.watch(&saved_path, notify::RecursiveMode::NonRecursive)?;
|
||||
Ok(w)
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let sub = FmtSubscriber::builder().without_time().with_target(false).finish();
|
||||
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){
|
||||
error!("backend exited, {e:?}");
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "ffs_proxy")]
|
||||
{
|
||||
backend::proxy::run().await?;
|
||||
}
|
||||
|
||||
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