refactor(activator): model hooks as typed sets

This commit is contained in:
okhsunrog 2026-08-11 22:22:00 +03:00
parent eed9e57f2e
commit dfd246d011
5 changed files with 158 additions and 97 deletions

View file

@ -26,7 +26,7 @@ use vpnhide_apatch_abi::{
parse_kernel_version_hint as parse_apatch_kernel_version_hint,
};
use vpnhide_protocol::Target;
use vpnhide_protocol::hook_ids::{HOOK_NAMES, KERNEL_HOOK_MASK, ZYGISK_HOOK_MASK};
use vpnhide_protocol::hook_ids::{Hook, KERNEL_HOOK_MASK, ZYGISK_HOOK_MASK};
use vpnhide_protocol::{
KPM_ARGS_LEN, Kind, MAX_TARGET_UIDS, format_config, parse_config, peek_kind,
};
@ -392,7 +392,7 @@ pub fn boot_wait_requested_from_env() -> Result<bool> {
fn has_native_targets(cfg: &CanonicalConfig, family: NativeHookFamily) -> bool {
cfg.apps
.values()
.any(|app| app.native.hookmask(family).is_some())
.any(|app| app.native.hooks(family).is_some())
}
fn has_ports_targets(cfg: &CanonicalConfig) -> bool {

View file

@ -130,30 +130,66 @@ pub(crate) enum NativeHookFamily {
}
impl NativeHookFamily {
fn full_mask(self) -> u32 {
fn full_set(self) -> HookSet {
match self {
NativeHookFamily::Kernel => KERNEL_HOOK_MASK,
NativeHookFamily::Zygisk => ZYGISK_HOOK_MASK,
NativeHookFamily::Kernel => HookSet::from_bits(KERNEL_HOOK_MASK),
NativeHookFamily::Zygisk => HookSet::from_bits(ZYGISK_HOOK_MASK),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct HookSet(u32);
impl HookSet {
const fn from_bits(bits: u32) -> Self {
Self(bits)
}
fn from_names(names: &[String]) -> Self {
names
.iter()
.filter_map(|name| Hook::from_name(name))
.fold(Self::default(), |hooks, hook| hooks.with(hook))
}
const fn with(self, hook: Hook) -> Self {
Self(self.0 | hook.bit())
}
const fn restricted_to(self, owner: Self) -> Self {
Self(self.0 & owner.0)
}
fn merge(&mut self, other: Self) {
self.0 |= other.0;
}
const fn is_empty(self) -> bool {
self.0 == 0
}
const fn bits(self) -> u32 {
self.0
}
}
impl NativeSelection {
pub(crate) fn hookmask(&self, family: NativeHookFamily) -> Option<u32> {
pub(crate) fn hooks(&self, family: NativeHookFamily) -> Option<HookSet> {
match self {
NativeSelection::Enabled(false) => None,
NativeSelection::Enabled(true) => Some(family.full_mask()),
NativeSelection::Enabled(true) => Some(family.full_set()),
NativeSelection::Hooks(names) => {
if names.is_empty() {
return None;
}
let mask = match family {
let hooks = match family {
NativeHookFamily::Kernel => {
names.iter().fold(0u32, |acc, name| acc | hook_bit(name)) & KERNEL_HOOK_MASK
HookSet::from_names(names).restricted_to(family.full_set())
}
NativeHookFamily::Zygisk => ZYGISK_HOOK_MASK,
NativeHookFamily::Zygisk => family.full_set(),
};
(mask != 0).then_some(mask)
(!hooks.is_empty()).then_some(hooks)
}
NativeSelection::Detailed(detail) => {
if !detail.enabled {
@ -164,11 +200,10 @@ impl NativeSelection {
NativeHookFamily::Zygisk => &detail.zygisk,
};
let Some(names) = selected else {
return Some(family.full_mask());
return Some(family.full_set());
};
let mask =
names.iter().fold(0u32, |acc, name| acc | hook_bit(name)) & family.full_mask();
(mask != 0).then_some(mask)
let hooks = HookSet::from_names(names).restricted_to(family.full_set());
(!hooks.is_empty()).then_some(hooks)
}
}
}
@ -198,14 +233,6 @@ where
})
}
fn hook_bit(name: &str) -> u32 {
HOOK_NAMES
.iter()
.position(|known| *known == name)
.map(|id| 1u32 << id)
.unwrap_or(0)
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PackageUidMap {
packages: BTreeMap<String, Vec<u32>>,
@ -350,9 +377,9 @@ pub(crate) fn project_native_with_resolver_for_family(
resolver: &PackageUidMap,
family: NativeHookFamily,
) -> String {
let mut by_uid = BTreeMap::<u32, u32>::new();
let mut by_uid = BTreeMap::<u32, HookSet>::new();
for (pkg, app) in &cfg.apps {
let Some(mask) = app.native.hookmask(family) else {
let Some(hooks) = app.native.hooks(family) else {
continue;
};
// The APK is intentionally single-owner: only its main-profile copy
@ -368,8 +395,8 @@ pub(crate) fn project_native_with_resolver_for_family(
{
by_uid
.entry(uid)
.and_modify(|existing| *existing |= mask)
.or_insert(mask);
.and_modify(|existing| existing.merge(hooks))
.or_insert(hooks);
}
continue;
}
@ -389,8 +416,8 @@ pub(crate) fn project_native_with_resolver_for_family(
}
by_uid
.entry(*uid)
.and_modify(|existing| *existing |= mask)
.or_insert(mask);
.and_modify(|existing| existing.merge(hooks))
.or_insert(hooks);
}
}
// `by_uid` is a BTreeMap, so iteration is ascending by UID; truncating would
@ -403,7 +430,10 @@ pub(crate) fn project_native_with_resolver_for_family(
let targets = by_uid
.into_iter()
.take(MAX_NATIVE_TARGETS)
.map(|(uid, hookmask)| Target { uid, hookmask })
.map(|(uid, hooks)| Target {
uid,
hookmask: hooks.bits(),
})
.collect::<Vec<_>>();
format_config(cfg.debug, NO_DEFAULT_MASK, &targets)
}

View file

@ -62,6 +62,47 @@ pub enum Hook {
ZygiskSetsockopt = 26,
}
impl Hook {
/// This hook's bit in the control/stats wire mask.
pub const fn bit(self) -> u32 {
1u32 << self as u32
}
/// Resolve a canonical config hook name.
pub fn from_name(name: &str) -> Option<Self> {
match name {
"fib_route_seq_show" => Some(Self::FibRouteSeqShow),
"ipv6_route_seq_show" => Some(Self::Ipv6RouteSeqShow),
"rtnl_fill_ifinfo" => Some(Self::RtnlFillIfinfo),
"inet_fill_ifaddr" => Some(Self::InetFillIfaddr),
"inet6_fill_ifaddr" => Some(Self::Inet6FillIfaddr),
"dev_ioctl" => Some(Self::DevIoctl),
"sock_ioctl" => Some(Self::SockIoctl),
"fib_dump_info" => Some(Self::FibDumpInfo),
"rt6_fill_node" => Some(Self::Rt6FillNode),
"fib_nl_fill_rule" => Some(Self::FibNlFillRule),
"lsposed_link_properties" => Some(Self::LsposedLinkProperties),
"lsposed_network_capabilities" => Some(Self::LsposedNetworkCapabilities),
"lsposed_network_info" => Some(Self::LsposedNetworkInfo),
"lsposed_network" => Some(Self::LsposedNetwork),
"lsposed_connectivity_result" => Some(Self::LsposedConnectivityResult),
"lsposed_connectivity_callback" => Some(Self::LsposedConnectivityCallback),
"lsposed_connectivity_network" => Some(Self::LsposedConnectivityNetwork),
"lsposed_package_visibility" => Some(Self::LsposedPackageVisibility),
"zygisk_ioctl" => Some(Self::ZygiskIoctl),
"zygisk_getifaddrs" => Some(Self::ZygiskGetifaddrs),
"zygisk_openat" => Some(Self::ZygiskOpenat),
"zygisk_recvmsg" => Some(Self::ZygiskRecvmsg),
"zygisk_recv" => Some(Self::ZygiskRecv),
"zygisk_recvfrom" => Some(Self::ZygiskRecvfrom),
"zygisk_recvfrom_chk" => Some(Self::ZygiskRecvfromChk),
"socket_bind_interface" => Some(Self::SocketBindInterface),
"zygisk_setsockopt" => Some(Self::ZygiskSetsockopt),
_ => None,
}
}
}
pub const HOOK_COUNT: u32 = 27;
/// Hooks owned by each backend: apply `mask & own`.
@ -98,33 +139,3 @@ pub enum Backend {
/// LSPosed Java-hook backend (system_server)
Lsposed = 3,
}
pub const HOOK_NAMES: [&str; 27] = [
"fib_route_seq_show",
"ipv6_route_seq_show",
"rtnl_fill_ifinfo",
"inet_fill_ifaddr",
"inet6_fill_ifaddr",
"dev_ioctl",
"sock_ioctl",
"fib_dump_info",
"rt6_fill_node",
"fib_nl_fill_rule",
"lsposed_link_properties",
"lsposed_network_capabilities",
"lsposed_network_info",
"lsposed_network",
"lsposed_connectivity_result",
"lsposed_connectivity_callback",
"lsposed_connectivity_network",
"lsposed_package_visibility",
"zygisk_ioctl",
"zygisk_getifaddrs",
"zygisk_openat",
"zygisk_recvmsg",
"zygisk_recv",
"zygisk_recvfrom",
"zygisk_recvfrom_chk",
"socket_bind_interface",
"zygisk_setsockopt",
];

View file

@ -209,6 +209,22 @@ def emit_rust(hooks: list[Hook], errs: list[Err], backends: list[Backend]) -> st
L.append(f" {pascal(h.name)} = {h.id},")
L.append("}")
L.append("")
L.append("impl Hook {")
L.append(" /// This hook's bit in the control/stats wire mask.")
L.append(" pub const fn bit(self) -> u32 {")
L.append(" 1u32 << self as u32")
L.append(" }")
L.append("")
L.append(" /// Resolve a canonical config hook name.")
L.append(" pub fn from_name(name: &str) -> Option<Self> {")
L.append(" match name {")
for h in hooks:
L.append(f' "{h.name}" => Some(Self::{pascal(h.name)}),')
L.append(" _ => None,")
L.append(" }")
L.append(" }")
L.append("}")
L.append("")
L.append(f"pub const HOOK_COUNT: u32 = {len(hooks)};")
L.append("")
L.append("/// Hooks owned by each backend: apply `mask & own`.")
@ -235,13 +251,6 @@ def emit_rust(hooks: list[Hook], errs: list[Err], backends: list[Backend]) -> st
L.append(f" {pascal(b.name)} = {b.id},")
L.append("}")
L.append("")
# One name per line so the output is rustfmt-clean (CI runs `cargo fmt
# --check`); rustfmt wraps an over-width array into exactly this shape.
L.append(f"pub const HOOK_NAMES: [&str; {len(hooks)}] = [")
for h in hooks:
L.append(f' "{h.name}",')
L.append("];")
L.append("")
return "\n".join(L)

View file

@ -62,6 +62,47 @@ pub enum Hook {
ZygiskSetsockopt = 26,
}
impl Hook {
/// This hook's bit in the control/stats wire mask.
pub const fn bit(self) -> u32 {
1u32 << self as u32
}
/// Resolve a canonical config hook name.
pub fn from_name(name: &str) -> Option<Self> {
match name {
"fib_route_seq_show" => Some(Self::FibRouteSeqShow),
"ipv6_route_seq_show" => Some(Self::Ipv6RouteSeqShow),
"rtnl_fill_ifinfo" => Some(Self::RtnlFillIfinfo),
"inet_fill_ifaddr" => Some(Self::InetFillIfaddr),
"inet6_fill_ifaddr" => Some(Self::Inet6FillIfaddr),
"dev_ioctl" => Some(Self::DevIoctl),
"sock_ioctl" => Some(Self::SockIoctl),
"fib_dump_info" => Some(Self::FibDumpInfo),
"rt6_fill_node" => Some(Self::Rt6FillNode),
"fib_nl_fill_rule" => Some(Self::FibNlFillRule),
"lsposed_link_properties" => Some(Self::LsposedLinkProperties),
"lsposed_network_capabilities" => Some(Self::LsposedNetworkCapabilities),
"lsposed_network_info" => Some(Self::LsposedNetworkInfo),
"lsposed_network" => Some(Self::LsposedNetwork),
"lsposed_connectivity_result" => Some(Self::LsposedConnectivityResult),
"lsposed_connectivity_callback" => Some(Self::LsposedConnectivityCallback),
"lsposed_connectivity_network" => Some(Self::LsposedConnectivityNetwork),
"lsposed_package_visibility" => Some(Self::LsposedPackageVisibility),
"zygisk_ioctl" => Some(Self::ZygiskIoctl),
"zygisk_getifaddrs" => Some(Self::ZygiskGetifaddrs),
"zygisk_openat" => Some(Self::ZygiskOpenat),
"zygisk_recvmsg" => Some(Self::ZygiskRecvmsg),
"zygisk_recv" => Some(Self::ZygiskRecv),
"zygisk_recvfrom" => Some(Self::ZygiskRecvfrom),
"zygisk_recvfrom_chk" => Some(Self::ZygiskRecvfromChk),
"socket_bind_interface" => Some(Self::SocketBindInterface),
"zygisk_setsockopt" => Some(Self::ZygiskSetsockopt),
_ => None,
}
}
}
pub const HOOK_COUNT: u32 = 27;
/// Hooks owned by each backend: apply `mask & own`.
@ -98,33 +139,3 @@ pub enum Backend {
/// LSPosed Java-hook backend (system_server)
Lsposed = 3,
}
pub const HOOK_NAMES: [&str; 27] = [
"fib_route_seq_show",
"ipv6_route_seq_show",
"rtnl_fill_ifinfo",
"inet_fill_ifaddr",
"inet6_fill_ifaddr",
"dev_ioctl",
"sock_ioctl",
"fib_dump_info",
"rt6_fill_node",
"fib_nl_fill_rule",
"lsposed_link_properties",
"lsposed_network_capabilities",
"lsposed_network_info",
"lsposed_network",
"lsposed_connectivity_result",
"lsposed_connectivity_callback",
"lsposed_connectivity_network",
"lsposed_package_visibility",
"zygisk_ioctl",
"zygisk_getifaddrs",
"zygisk_openat",
"zygisk_recvmsg",
"zygisk_recv",
"zygisk_recvfrom",
"zygisk_recvfrom_chk",
"socket_bind_interface",
"zygisk_setsockopt",
];