style: add clang-format, ktlint, editorconfig and format all code

- Add .editorconfig with ktlint config (disable wildcard-import rule,
  allow PascalCase for @Composable functions)
- Add kmod/.clang-format from upstream kernel tree
- Run clang-format on vpnhide_kmod.c (kernel coding style)
- Run ktlint --format on all Kotlin files (lsposed + test-app)
This commit is contained in:
okhsunrog 2026-04-12 23:26:36 +03:00
parent ca35183084
commit e2d41dea13
7 changed files with 1065 additions and 284 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
root = true
[*]
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.kt]
# Compose @Composable functions use PascalCase by convention
ktlint_function_naming_ignore_when_annotated_with = Composable
# Wildcard imports are idiomatic for Compose (androidx.compose.*)
ktlint_standard_no-wildcard-imports = disabled

684
kmod/.clang-format Normal file
View file

@ -0,0 +1,684 @@
# SPDX-License-Identifier: GPL-2.0
#
# clang-format configuration file. Intended for clang-format >= 11.
#
# For more information, see:
#
# Documentation/process/clang-format.rst
# https://clang.llvm.org/docs/ClangFormat.html
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
# Taken from:
# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \
# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
# | LC_ALL=C sort -u
ForEachMacros:
- '__ata_qc_for_each'
- '__bio_for_each_bvec'
- '__bio_for_each_segment'
- '__evlist__for_each_entry'
- '__evlist__for_each_entry_continue'
- '__evlist__for_each_entry_from'
- '__evlist__for_each_entry_reverse'
- '__evlist__for_each_entry_safe'
- '__for_each_mem_range'
- '__for_each_mem_range_rev'
- '__for_each_thread'
- '__hlist_for_each_rcu'
- '__map__for_each_symbol_by_name'
- '__perf_evlist__for_each_entry'
- '__perf_evlist__for_each_entry_reverse'
- '__perf_evlist__for_each_entry_safe'
- '__rq_for_each_bio'
- '__shost_for_each_device'
- 'apei_estatus_for_each_section'
- 'ata_for_each_dev'
- 'ata_for_each_link'
- 'ata_qc_for_each'
- 'ata_qc_for_each_raw'
- 'ata_qc_for_each_with_internal'
- 'ax25_for_each'
- 'ax25_uid_for_each'
- 'bio_for_each_bvec'
- 'bio_for_each_bvec_all'
- 'bio_for_each_folio_all'
- 'bio_for_each_integrity_vec'
- 'bio_for_each_segment'
- 'bio_for_each_segment_all'
- 'bio_list_for_each'
- 'bip_for_each_vec'
- 'bond_for_each_slave'
- 'bond_for_each_slave_rcu'
- 'bpf__perf_for_each_map'
- 'bpf__perf_for_each_map_named'
- 'bpf_for_each_spilled_reg'
- 'bpf_object__for_each_map'
- 'bpf_object__for_each_program'
- 'bpf_object__for_each_safe'
- 'bpf_perf_object__for_each'
- 'btree_for_each_safe128'
- 'btree_for_each_safe32'
- 'btree_for_each_safe64'
- 'btree_for_each_safel'
- 'card_for_each_dev'
- 'cgroup_taskset_for_each'
- 'cgroup_taskset_for_each_leader'
- 'cpufreq_for_each_efficient_entry_idx'
- 'cpufreq_for_each_entry'
- 'cpufreq_for_each_entry_idx'
- 'cpufreq_for_each_valid_entry'
- 'cpufreq_for_each_valid_entry_idx'
- 'css_for_each_child'
- 'css_for_each_descendant_post'
- 'css_for_each_descendant_pre'
- 'damon_for_each_region'
- 'damon_for_each_region_safe'
- 'damon_for_each_scheme'
- 'damon_for_each_scheme_safe'
- 'damon_for_each_target'
- 'damon_for_each_target_safe'
- 'data__for_each_file'
- 'data__for_each_file_new'
- 'data__for_each_file_start'
- 'device_for_each_child_node'
- 'displayid_iter_for_each'
- 'dma_fence_array_for_each'
- 'dma_fence_chain_for_each'
- 'dma_fence_unwrap_for_each'
- 'dma_resv_for_each_fence'
- 'dma_resv_for_each_fence_unlocked'
- 'do_for_each_ftrace_op'
- 'drm_atomic_crtc_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane_state'
- 'drm_atomic_for_each_plane_damage'
- 'drm_client_for_each_connector_iter'
- 'drm_client_for_each_modeset'
- 'drm_connector_for_each_possible_encoder'
- 'drm_for_each_bridge_in_chain'
- 'drm_for_each_connector_iter'
- 'drm_for_each_crtc'
- 'drm_for_each_crtc_reverse'
- 'drm_for_each_encoder'
- 'drm_for_each_encoder_mask'
- 'drm_for_each_fb'
- 'drm_for_each_legacy_plane'
- 'drm_for_each_plane'
- 'drm_for_each_plane_mask'
- 'drm_for_each_privobj'
- 'drm_mm_for_each_hole'
- 'drm_mm_for_each_node'
- 'drm_mm_for_each_node_in_range'
- 'drm_mm_for_each_node_safe'
- 'dsa_switch_for_each_available_port'
- 'dsa_switch_for_each_cpu_port'
- 'dsa_switch_for_each_port'
- 'dsa_switch_for_each_port_continue_reverse'
- 'dsa_switch_for_each_port_safe'
- 'dsa_switch_for_each_user_port'
- 'dsa_tree_for_each_user_port'
- 'dso__for_each_symbol'
- 'dsos__for_each_with_build_id'
- 'elf_hash_for_each_possible'
- 'elf_section__for_each_rel'
- 'elf_section__for_each_rela'
- 'elf_symtab__for_each_symbol'
- 'evlist__for_each_cpu'
- 'evlist__for_each_entry'
- 'evlist__for_each_entry_continue'
- 'evlist__for_each_entry_from'
- 'evlist__for_each_entry_reverse'
- 'evlist__for_each_entry_safe'
- 'flow_action_for_each'
- 'for_each_acpi_dev_match'
- 'for_each_active_dev_scope'
- 'for_each_active_drhd_unit'
- 'for_each_active_iommu'
- 'for_each_aggr_pgid'
- 'for_each_available_child_of_node'
- 'for_each_bench'
- 'for_each_bio'
- 'for_each_board_func_rsrc'
- 'for_each_btf_ext_rec'
- 'for_each_btf_ext_sec'
- 'for_each_bvec'
- 'for_each_card_auxs'
- 'for_each_card_auxs_safe'
- 'for_each_card_components'
- 'for_each_card_dapms'
- 'for_each_card_pre_auxs'
- 'for_each_card_prelinks'
- 'for_each_card_rtds'
- 'for_each_card_rtds_safe'
- 'for_each_card_widgets'
- 'for_each_card_widgets_safe'
- 'for_each_cgroup_storage_type'
- 'for_each_child_of_node'
- 'for_each_clear_bit'
- 'for_each_clear_bit_from'
- 'for_each_clear_bitrange'
- 'for_each_clear_bitrange_from'
- 'for_each_cmd'
- 'for_each_cmsghdr'
- 'for_each_collection'
- 'for_each_comp_order'
- 'for_each_compatible_node'
- 'for_each_component_dais'
- 'for_each_component_dais_safe'
- 'for_each_console'
- 'for_each_cpu'
- 'for_each_cpu_and'
- 'for_each_cpu_not'
- 'for_each_cpu_wrap'
- 'for_each_dapm_widgets'
- 'for_each_dedup_cand'
- 'for_each_dev_addr'
- 'for_each_dev_scope'
- 'for_each_dma_cap_mask'
- 'for_each_dpcm_be'
- 'for_each_dpcm_be_rollback'
- 'for_each_dpcm_be_safe'
- 'for_each_dpcm_fe'
- 'for_each_drhd_unit'
- 'for_each_dss_dev'
- 'for_each_efi_memory_desc'
- 'for_each_efi_memory_desc_in_map'
- 'for_each_element'
- 'for_each_element_extid'
- 'for_each_element_id'
- 'for_each_endpoint_of_node'
- 'for_each_event'
- 'for_each_event_tps'
- 'for_each_evictable_lru'
- 'for_each_fib6_node_rt_rcu'
- 'for_each_fib6_walker_rt'
- 'for_each_free_mem_pfn_range_in_zone'
- 'for_each_free_mem_pfn_range_in_zone_from'
- 'for_each_free_mem_range'
- 'for_each_free_mem_range_reverse'
- 'for_each_func_rsrc'
- 'for_each_group_evsel'
- 'for_each_group_member'
- 'for_each_hstate'
- 'for_each_if'
- 'for_each_inject_fn'
- 'for_each_insn'
- 'for_each_insn_prefix'
- 'for_each_intid'
- 'for_each_iommu'
- 'for_each_ip_tunnel_rcu'
- 'for_each_irq_nr'
- 'for_each_lang'
- 'for_each_link_codecs'
- 'for_each_link_cpus'
- 'for_each_link_platforms'
- 'for_each_lru'
- 'for_each_matching_node'
- 'for_each_matching_node_and_match'
- 'for_each_mem_pfn_range'
- 'for_each_mem_range'
- 'for_each_mem_range_rev'
- 'for_each_mem_region'
- 'for_each_member'
- 'for_each_memory'
- 'for_each_migratetype_order'
- 'for_each_missing_reg'
- 'for_each_net'
- 'for_each_net_continue_reverse'
- 'for_each_net_rcu'
- 'for_each_netdev'
- 'for_each_netdev_continue'
- 'for_each_netdev_continue_rcu'
- 'for_each_netdev_continue_reverse'
- 'for_each_netdev_feature'
- 'for_each_netdev_in_bond_rcu'
- 'for_each_netdev_rcu'
- 'for_each_netdev_reverse'
- 'for_each_netdev_safe'
- 'for_each_new_connector_in_state'
- 'for_each_new_crtc_in_state'
- 'for_each_new_mst_mgr_in_state'
- 'for_each_new_plane_in_state'
- 'for_each_new_plane_in_state_reverse'
- 'for_each_new_private_obj_in_state'
- 'for_each_new_reg'
- 'for_each_node'
- 'for_each_node_by_name'
- 'for_each_node_by_type'
- 'for_each_node_mask'
- 'for_each_node_state'
- 'for_each_node_with_cpus'
- 'for_each_node_with_property'
- 'for_each_nonreserved_multicast_dest_pgid'
- 'for_each_of_allnodes'
- 'for_each_of_allnodes_from'
- 'for_each_of_cpu_node'
- 'for_each_of_pci_range'
- 'for_each_old_connector_in_state'
- 'for_each_old_crtc_in_state'
- 'for_each_old_mst_mgr_in_state'
- 'for_each_old_plane_in_state'
- 'for_each_old_private_obj_in_state'
- 'for_each_oldnew_connector_in_state'
- 'for_each_oldnew_crtc_in_state'
- 'for_each_oldnew_mst_mgr_in_state'
- 'for_each_oldnew_plane_in_state'
- 'for_each_oldnew_plane_in_state_reverse'
- 'for_each_oldnew_private_obj_in_state'
- 'for_each_online_cpu'
- 'for_each_online_node'
- 'for_each_online_pgdat'
- 'for_each_path'
- 'for_each_pci_bridge'
- 'for_each_pci_dev'
- 'for_each_pcm_streams'
- 'for_each_physmem_range'
- 'for_each_populated_zone'
- 'for_each_possible_cpu'
- 'for_each_present_cpu'
- 'for_each_prime_number'
- 'for_each_prime_number_from'
- 'for_each_probe_cache_entry'
- 'for_each_process'
- 'for_each_process_thread'
- 'for_each_prop_codec_conf'
- 'for_each_prop_dai_codec'
- 'for_each_prop_dai_cpu'
- 'for_each_prop_dlc_codecs'
- 'for_each_prop_dlc_cpus'
- 'for_each_prop_dlc_platforms'
- 'for_each_property_of_node'
- 'for_each_reg'
- 'for_each_reg_filtered'
- 'for_each_registered_fb'
- 'for_each_requested_gpio'
- 'for_each_requested_gpio_in_range'
- 'for_each_reserved_mem_range'
- 'for_each_reserved_mem_region'
- 'for_each_rtd_codec_dais'
- 'for_each_rtd_components'
- 'for_each_rtd_cpu_dais'
- 'for_each_rtd_dais'
- 'for_each_script'
- 'for_each_sec'
- 'for_each_set_bit'
- 'for_each_set_bit_from'
- 'for_each_set_bitrange'
- 'for_each_set_bitrange_from'
- 'for_each_set_clump8'
- 'for_each_sg'
- 'for_each_sg_dma_page'
- 'for_each_sg_page'
- 'for_each_sgtable_dma_page'
- 'for_each_sgtable_dma_sg'
- 'for_each_sgtable_page'
- 'for_each_sgtable_sg'
- 'for_each_shell_test'
- 'for_each_sibling_event'
- 'for_each_subelement'
- 'for_each_subelement_extid'
- 'for_each_subelement_id'
- 'for_each_sublist'
- 'for_each_subsystem'
- 'for_each_supported_activate_fn'
- 'for_each_supported_inject_fn'
- 'for_each_test'
- 'for_each_thread'
- 'for_each_token'
- 'for_each_unicast_dest_pgid'
- 'for_each_vsi'
- 'for_each_wakeup_source'
- 'for_each_zone'
- 'for_each_zone_zonelist'
- 'for_each_zone_zonelist_nodemask'
- 'func_for_each_insn'
- 'fwnode_for_each_available_child_node'
- 'fwnode_for_each_child_node'
- 'fwnode_graph_for_each_endpoint'
- 'gadget_for_each_ep'
- 'genradix_for_each'
- 'genradix_for_each_from'
- 'hash_for_each'
- 'hash_for_each_possible'
- 'hash_for_each_possible_rcu'
- 'hash_for_each_possible_rcu_notrace'
- 'hash_for_each_possible_safe'
- 'hash_for_each_rcu'
- 'hash_for_each_safe'
- 'hashmap__for_each_entry'
- 'hashmap__for_each_entry_safe'
- 'hashmap__for_each_key_entry'
- 'hashmap__for_each_key_entry_safe'
- 'hctx_for_each_ctx'
- 'hists__for_each_format'
- 'hists__for_each_sort_list'
- 'hlist_bl_for_each_entry'
- 'hlist_bl_for_each_entry_rcu'
- 'hlist_bl_for_each_entry_safe'
- 'hlist_for_each'
- 'hlist_for_each_entry'
- 'hlist_for_each_entry_continue'
- 'hlist_for_each_entry_continue_rcu'
- 'hlist_for_each_entry_continue_rcu_bh'
- 'hlist_for_each_entry_from'
- 'hlist_for_each_entry_from_rcu'
- 'hlist_for_each_entry_rcu'
- 'hlist_for_each_entry_rcu_bh'
- 'hlist_for_each_entry_rcu_notrace'
- 'hlist_for_each_entry_safe'
- 'hlist_for_each_entry_srcu'
- 'hlist_for_each_safe'
- 'hlist_nulls_for_each_entry'
- 'hlist_nulls_for_each_entry_from'
- 'hlist_nulls_for_each_entry_rcu'
- 'hlist_nulls_for_each_entry_safe'
- 'i3c_bus_for_each_i2cdev'
- 'i3c_bus_for_each_i3cdev'
- 'idr_for_each_entry'
- 'idr_for_each_entry_continue'
- 'idr_for_each_entry_continue_ul'
- 'idr_for_each_entry_ul'
- 'in_dev_for_each_ifa_rcu'
- 'in_dev_for_each_ifa_rtnl'
- 'inet_bind_bucket_for_each'
- 'inet_lhash2_for_each_icsk'
- 'inet_lhash2_for_each_icsk_continue'
- 'inet_lhash2_for_each_icsk_rcu'
- 'intlist__for_each_entry'
- 'intlist__for_each_entry_safe'
- 'kcore_copy__for_each_phdr'
- 'key_for_each'
- 'key_for_each_safe'
- 'klp_for_each_func'
- 'klp_for_each_func_safe'
- 'klp_for_each_func_static'
- 'klp_for_each_object'
- 'klp_for_each_object_safe'
- 'klp_for_each_object_static'
- 'kunit_suite_for_each_test_case'
- 'kvm_for_each_memslot'
- 'kvm_for_each_memslot_in_gfn_range'
- 'kvm_for_each_vcpu'
- 'libbpf_nla_for_each_attr'
- 'list_for_each'
- 'list_for_each_codec'
- 'list_for_each_codec_safe'
- 'list_for_each_continue'
- 'list_for_each_entry'
- 'list_for_each_entry_continue'
- 'list_for_each_entry_continue_rcu'
- 'list_for_each_entry_continue_reverse'
- 'list_for_each_entry_from'
- 'list_for_each_entry_from_rcu'
- 'list_for_each_entry_from_reverse'
- 'list_for_each_entry_lockless'
- 'list_for_each_entry_rcu'
- 'list_for_each_entry_reverse'
- 'list_for_each_entry_safe'
- 'list_for_each_entry_safe_continue'
- 'list_for_each_entry_safe_from'
- 'list_for_each_entry_safe_reverse'
- 'list_for_each_entry_srcu'
- 'list_for_each_from'
- 'list_for_each_prev'
- 'list_for_each_prev_safe'
- 'list_for_each_safe'
- 'llist_for_each'
- 'llist_for_each_entry'
- 'llist_for_each_entry_safe'
- 'llist_for_each_safe'
- 'map__for_each_symbol'
- 'map__for_each_symbol_by_name'
- 'map_for_each_event'
- 'map_for_each_metric'
- 'maps__for_each_entry'
- 'maps__for_each_entry_safe'
- 'mci_for_each_dimm'
- 'media_device_for_each_entity'
- 'media_device_for_each_intf'
- 'media_device_for_each_link'
- 'media_device_for_each_pad'
- 'msi_for_each_desc'
- 'nanddev_io_for_each_page'
- 'netdev_for_each_lower_dev'
- 'netdev_for_each_lower_private'
- 'netdev_for_each_lower_private_rcu'
- 'netdev_for_each_mc_addr'
- 'netdev_for_each_uc_addr'
- 'netdev_for_each_upper_dev_rcu'
- 'netdev_hw_addr_list_for_each'
- 'nft_rule_for_each_expr'
- 'nla_for_each_attr'
- 'nla_for_each_nested'
- 'nlmsg_for_each_attr'
- 'nlmsg_for_each_msg'
- 'nr_neigh_for_each'
- 'nr_neigh_for_each_safe'
- 'nr_node_for_each'
- 'nr_node_for_each_safe'
- 'of_for_each_phandle'
- 'of_property_for_each_string'
- 'of_property_for_each_u32'
- 'pci_bus_for_each_resource'
- 'pci_doe_for_each_off'
- 'pcl_for_each_chunk'
- 'pcl_for_each_segment'
- 'pcm_for_each_format'
- 'perf_config_items__for_each_entry'
- 'perf_config_sections__for_each_entry'
- 'perf_config_set__for_each_entry'
- 'perf_cpu_map__for_each_cpu'
- 'perf_evlist__for_each_entry'
- 'perf_evlist__for_each_entry_reverse'
- 'perf_evlist__for_each_entry_safe'
- 'perf_evlist__for_each_evsel'
- 'perf_evlist__for_each_mmap'
- 'perf_hpp_list__for_each_format'
- 'perf_hpp_list__for_each_format_safe'
- 'perf_hpp_list__for_each_sort_list'
- 'perf_hpp_list__for_each_sort_list_safe'
- 'perf_pmu__for_each_hybrid_pmu'
- 'ping_portaddr_for_each_entry'
- 'ping_portaddr_for_each_entry_rcu'
- 'plist_for_each'
- 'plist_for_each_continue'
- 'plist_for_each_entry'
- 'plist_for_each_entry_continue'
- 'plist_for_each_entry_safe'
- 'plist_for_each_safe'
- 'pnp_for_each_card'
- 'pnp_for_each_dev'
- 'protocol_for_each_card'
- 'protocol_for_each_dev'
- 'queue_for_each_hw_ctx'
- 'radix_tree_for_each_slot'
- 'radix_tree_for_each_tagged'
- 'rb_for_each'
- 'rbtree_postorder_for_each_entry_safe'
- 'rdma_for_each_block'
- 'rdma_for_each_port'
- 'rdma_umem_for_each_dma_block'
- 'resort_rb__for_each_entry'
- 'resource_list_for_each_entry'
- 'resource_list_for_each_entry_safe'
- 'rhl_for_each_entry_rcu'
- 'rhl_for_each_rcu'
- 'rht_for_each'
- 'rht_for_each_entry'
- 'rht_for_each_entry_from'
- 'rht_for_each_entry_rcu'
- 'rht_for_each_entry_rcu_from'
- 'rht_for_each_entry_safe'
- 'rht_for_each_from'
- 'rht_for_each_rcu'
- 'rht_for_each_rcu_from'
- 'rq_for_each_bvec'
- 'rq_for_each_segment'
- 'rq_list_for_each'
- 'rq_list_for_each_safe'
- 'scsi_for_each_prot_sg'
- 'scsi_for_each_sg'
- 'sctp_for_each_hentry'
- 'sctp_skb_for_each'
- 'sec_for_each_insn'
- 'sec_for_each_insn_continue'
- 'sec_for_each_insn_from'
- 'shdma_for_each_chan'
- 'shost_for_each_device'
- 'sk_for_each'
- 'sk_for_each_bound'
- 'sk_for_each_entry_offset_rcu'
- 'sk_for_each_from'
- 'sk_for_each_rcu'
- 'sk_for_each_safe'
- 'sk_nulls_for_each'
- 'sk_nulls_for_each_from'
- 'sk_nulls_for_each_rcu'
- 'snd_array_for_each'
- 'snd_pcm_group_for_each_entry'
- 'snd_soc_dapm_widget_for_each_path'
- 'snd_soc_dapm_widget_for_each_path_safe'
- 'snd_soc_dapm_widget_for_each_sink_path'
- 'snd_soc_dapm_widget_for_each_source_path'
- 'strlist__for_each_entry'
- 'strlist__for_each_entry_safe'
- 'sym_for_each_insn'
- 'sym_for_each_insn_continue_reverse'
- 'symbols__for_each_entry'
- 'tb_property_for_each'
- 'tcf_act_for_each_action'
- 'tcf_exts_for_each_action'
- 'udp_portaddr_for_each_entry'
- 'udp_portaddr_for_each_entry_rcu'
- 'usb_hub_for_each_child'
- 'v4l2_device_for_each_subdev'
- 'v4l2_m2m_for_each_dst_buf'
- 'v4l2_m2m_for_each_dst_buf_safe'
- 'v4l2_m2m_for_each_src_buf'
- 'v4l2_m2m_for_each_src_buf_safe'
- 'virtio_device_for_each_vq'
- 'while_for_each_ftrace_op'
- 'xa_for_each'
- 'xa_for_each_marked'
- 'xa_for_each_range'
- 'xa_for_each_start'
- 'xas_for_each'
- 'xas_for_each_conflict'
- 'xas_for_each_marked'
- 'xbc_array_for_each_value'
- 'xbc_for_each_key_value'
- 'xbc_node_for_each_array_value'
- 'xbc_node_for_each_child'
- 'xbc_node_for_each_key_value'
- 'xbc_node_for_each_subkey'
- 'zorro_for_each_dev'
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentGotoLabels: false
IndentPPDirectives: None
IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
# Taken from git's rules
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatementsExceptForEachMacros
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: Always
...

View file

@ -43,7 +43,7 @@
/* VPN interface name matching */
/* ------------------------------------------------------------------ */
static const char * const vpn_prefixes[] = {
static const char *const vpn_prefixes[] = {
"tun", "ppp", "tap", "wg", "ipsec", "xfrm", "utun", "l2tp", "gre",
};
@ -55,8 +55,8 @@ static bool is_vpn_ifname(const char *name)
return false;
for (i = 0; i < ARRAY_SIZE(vpn_prefixes); i++) {
if (strncmp(name, vpn_prefixes[i],
strlen(vpn_prefixes[i])) == 0)
if (strncmp(name, vpn_prefixes[i], strlen(vpn_prefixes[i])) ==
0)
return true;
}
if (strstr(name, "vpn") || strstr(name, "VPN"))
@ -158,10 +158,10 @@ static int targets_open(struct inode *inode, struct file *file)
}
static const struct proc_ops targets_proc_ops = {
.proc_open = targets_open,
.proc_read = seq_read,
.proc_write = targets_write,
.proc_lseek = seq_lseek,
.proc_open = targets_open,
.proc_read = seq_read,
.proc_write = targets_write,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
@ -183,8 +183,7 @@ struct dev_ioctl_data {
struct ifreq *kifr; /* kernel pointer, saved from x2 */
};
static int dev_ioctl_entry(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int dev_ioctl_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct dev_ioctl_data *data = (void *)ri->data;
@ -197,8 +196,7 @@ static int dev_ioctl_entry(struct kretprobe_instance *ri,
return 0;
}
static int dev_ioctl_ret(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int dev_ioctl_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct dev_ioctl_data *data = (void *)ri->data;
char name[IFNAMSIZ];
@ -227,11 +225,11 @@ static int dev_ioctl_ret(struct kretprobe_instance *ri,
}
static struct kretprobe dev_ioctl_krp = {
.handler = dev_ioctl_ret,
.entry_handler = dev_ioctl_entry,
.data_size = sizeof(struct dev_ioctl_data),
.maxactive = 20,
.kp.symbol_name = "dev_ioctl",
.handler = dev_ioctl_ret,
.entry_handler = dev_ioctl_entry,
.data_size = sizeof(struct dev_ioctl_data),
.maxactive = 20,
.kp.symbol_name = "dev_ioctl",
};
/* ================================================================== */
@ -249,8 +247,7 @@ struct dev_ifconf_data {
bool target;
};
static int dev_ifconf_entry(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int dev_ifconf_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct dev_ifconf_data *data = (void *)ri->data;
@ -259,8 +256,7 @@ static int dev_ifconf_entry(struct kretprobe_instance *ri,
return 0;
}
static int dev_ifconf_ret(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int dev_ifconf_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct dev_ifconf_data *data = (void *)ri->data;
struct ifconf ifc;
@ -304,11 +300,11 @@ static int dev_ifconf_ret(struct kretprobe_instance *ri,
}
static struct kretprobe dev_ifconf_krp = {
.handler = dev_ifconf_ret,
.entry_handler = dev_ifconf_entry,
.data_size = sizeof(struct dev_ifconf_data),
.maxactive = 20,
.kp.symbol_name = "dev_ifconf",
.handler = dev_ifconf_ret,
.entry_handler = dev_ifconf_entry,
.data_size = sizeof(struct dev_ifconf_data),
.maxactive = 20,
.kp.symbol_name = "dev_ifconf",
};
/* ================================================================== */
@ -325,8 +321,7 @@ struct rtnl_fill_data {
bool should_filter;
};
static int rtnl_fill_entry(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int rtnl_fill_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct rtnl_fill_data *data = (void *)ri->data;
struct net_device *dev;
@ -351,8 +346,7 @@ static int rtnl_fill_entry(struct kretprobe_instance *ri,
return 0;
}
static int rtnl_fill_ret(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int rtnl_fill_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct rtnl_fill_data *data = (void *)ri->data;
@ -363,11 +357,11 @@ static int rtnl_fill_ret(struct kretprobe_instance *ri,
}
static struct kretprobe rtnl_fill_krp = {
.handler = rtnl_fill_ret,
.entry_handler = rtnl_fill_entry,
.data_size = sizeof(struct rtnl_fill_data),
.maxactive = 20,
.kp.symbol_name = "rtnl_fill_ifinfo",
.handler = rtnl_fill_ret,
.entry_handler = rtnl_fill_entry,
.data_size = sizeof(struct rtnl_fill_data),
.maxactive = 20,
.kp.symbol_name = "rtnl_fill_ifinfo",
};
/* ================================================================== */
@ -393,8 +387,7 @@ struct inet6_fill_data {
bool should_filter;
};
static int inet6_fill_entry(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int inet6_fill_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct inet6_fill_data *data = (void *)ri->data;
struct inet6_ifaddr *ifa;
@ -422,8 +415,7 @@ static int inet6_fill_entry(struct kretprobe_instance *ri,
return 0;
}
static int inet6_fill_ret(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int inet6_fill_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct inet6_fill_data *data = (void *)ri->data;
@ -437,11 +429,11 @@ static int inet6_fill_ret(struct kretprobe_instance *ri,
}
static struct kretprobe inet6_fill_krp = {
.handler = inet6_fill_ret,
.entry_handler = inet6_fill_entry,
.data_size = sizeof(struct inet6_fill_data),
.maxactive = 20,
.kp.symbol_name = "inet6_fill_ifaddr",
.handler = inet6_fill_ret,
.entry_handler = inet6_fill_entry,
.data_size = sizeof(struct inet6_fill_data),
.maxactive = 20,
.kp.symbol_name = "inet6_fill_ifaddr",
};
/* ================================================================== */
@ -459,8 +451,7 @@ struct inet_fill_data {
bool should_filter;
};
static int inet_fill_entry(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int inet_fill_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct inet_fill_data *data = (void *)ri->data;
struct in_ifaddr *ifa;
@ -484,8 +475,7 @@ static int inet_fill_entry(struct kretprobe_instance *ri,
return 0;
}
static int inet_fill_ret(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int inet_fill_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct inet_fill_data *data = (void *)ri->data;
@ -498,11 +488,11 @@ static int inet_fill_ret(struct kretprobe_instance *ri,
}
static struct kretprobe inet_fill_krp = {
.handler = inet_fill_ret,
.entry_handler = inet_fill_entry,
.data_size = sizeof(struct inet_fill_data),
.maxactive = 20,
.kp.symbol_name = "inet_fill_ifaddr",
.handler = inet_fill_ret,
.entry_handler = inet_fill_entry,
.data_size = sizeof(struct inet_fill_data),
.maxactive = 20,
.kp.symbol_name = "inet_fill_ifaddr",
};
/* ================================================================== */
@ -522,8 +512,7 @@ struct fib_route_data {
bool target;
};
static int fib_route_entry(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int fib_route_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct fib_route_data *data = (void *)ri->data;
@ -549,8 +538,7 @@ static int fib_route_entry(struct kretprobe_instance *ri,
* synchronously under its own fd context no concurrent access to
* the same seq_file is possible between our entry and return handlers.
*/
static int fib_route_ret(struct kretprobe_instance *ri,
struct pt_regs *regs)
static int fib_route_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct fib_route_data *data = (void *)ri->data;
struct seq_file *seq = data->seq;
@ -583,7 +571,8 @@ static int fib_route_ret(struct kretprobe_instance *ri,
/* Extract the interface name (first field, tab-delimited) */
for (j = 0; j < IFNAMSIZ - 1 && j < (int)line_len &&
src[j] != '\t' && src[j] != '\n'; j++)
src[j] != '\t' && src[j] != '\n';
j++)
ifname[j] = src[j];
ifname[j] = '\0';
@ -605,11 +594,11 @@ static int fib_route_ret(struct kretprobe_instance *ri,
}
static struct kretprobe fib_route_krp = {
.handler = fib_route_ret,
.entry_handler = fib_route_entry,
.data_size = sizeof(struct fib_route_data),
.maxactive = 20,
.kp.symbol_name = "fib_route_seq_show",
.handler = fib_route_ret,
.entry_handler = fib_route_entry,
.data_size = sizeof(struct fib_route_data),
.maxactive = 20,
.kp.symbol_name = "fib_route_seq_show",
};
/* ================================================================== */
@ -625,12 +614,12 @@ struct kretprobe_reg {
};
static struct kretprobe_reg probes[] = {
{ &dev_ioctl_krp, "dev_ioctl", false },
{ &dev_ifconf_krp, "dev_ifconf", false },
{ &rtnl_fill_krp, "rtnl_fill_ifinfo", false },
{ &inet6_fill_krp, "inet6_fill_ifaddr", false },
{ &inet_fill_krp, "inet_fill_ifaddr", false },
{ &fib_route_krp, "fib_route_seq_show", false },
{ &dev_ioctl_krp, "dev_ioctl", false },
{ &dev_ifconf_krp, "dev_ifconf", false },
{ &rtnl_fill_krp, "rtnl_fill_ifinfo", false },
{ &inet6_fill_krp, "inet6_fill_ifaddr", false },
{ &inet_fill_krp, "inet_fill_ifaddr", false },
{ &fib_route_krp, "fib_route_seq_show", false },
};
static int __init vpnhide_init(void)
@ -656,13 +645,13 @@ static int __init vpnhide_init(void)
}
if (ok < ARRAY_SIZE(probes))
pr_warn(MODNAME ": only %d/%zu kretprobes registered — "
"some detection paths are not covered\n",
"some detection paths are not covered\n",
ok, ARRAY_SIZE(probes));
/* 0600: root-only read/write. UIDs are written here by service.sh
* and WebUI (both root). Apps must not see the target list. */
targets_entry = proc_create("vpnhide_targets", 0600, NULL,
&targets_proc_ops);
targets_entry =
proc_create("vpnhide_targets", 0600, NULL, &targets_proc_ops);
pr_info(MODNAME ": loaded — write UIDs to /proc/vpnhide_targets\n");
return 0;
@ -679,7 +668,7 @@ static void __exit vpnhide_exit(void)
if (probes[i].registered) {
unregister_kretprobe(probes[i].krp);
pr_info(MODNAME ": kretprobe(%s) unregistered "
"(missed %d)\n",
"(missed %d)\n",
probes[i].name, probes[i].krp->nmissed);
}
}

View file

@ -34,16 +34,16 @@ import java.util.concurrent.atomic.AtomicBoolean
* Only "System Framework" needs to be in LSPosed scope.
*/
class HookEntry : IXposedHookLoadPackage {
private val hookInstalled = AtomicBoolean(false)
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// Only hook system_server. handleLoadPackage fires multiple times
// in system_server (once per hosted package / APEX), so we use
// compareAndSet to install hooks exactly once.
val inSystemServer = hookInstalled.get() ||
lpparam.processName == "android" ||
android.os.Process.myUid() == 1000
val inSystemServer =
hookInstalled.get() ||
lpparam.processName == "android" ||
android.os.Process.myUid() == 1000
if (!inSystemServer) return
@ -53,7 +53,10 @@ class HookEntry : IXposedHookLoadPackage {
}
}
private inline fun tryHook(name: String, block: () -> Unit) {
private inline fun tryHook(
name: String,
block: () -> Unit,
) {
try {
block()
} catch (t: Throwable) {
@ -85,6 +88,7 @@ class HookEntry : IXposedHookLoadPackage {
// ==================================================================
@Volatile private var systemServerTargetUids: Set<Int>? = null
@Volatile private var targetUidsFileObserver: android.os.FileObserver? = null
private val uidLock = Any()
@ -135,7 +139,7 @@ class HookEntry : IXposedHookLoadPackage {
tryHook("NC.writeToParcel") { hookNCWriteToParcel() }
tryHook("NI.writeToParcel") { hookNIWriteToParcel() }
tryHook("LP.writeToParcel") { hookLPWriteToParcel() }
tryHook("FileObserver") { watchTargetUidsFile() }
tryHook("FileObserver") { watchTargetUidsFile() }
}
/**
@ -146,17 +150,21 @@ class HookEntry : IXposedHookLoadPackage {
private fun watchTargetUidsFile() {
val dir = "/data/system"
val filename = "vpnhide_uids.txt"
val observer = object : android.os.FileObserver(
File(dir),
CREATE or CLOSE_WRITE or MOVED_TO or MODIFY
) {
override fun onEvent(event: Int, path: String?) {
if (path == filename) {
XposedBridge.log("VpnHide: $filename changed (event=$event), invalidating UID cache")
systemServerTargetUids = null
val observer =
object : android.os.FileObserver(
File(dir),
CREATE or CLOSE_WRITE or MOVED_TO or MODIFY,
) {
override fun onEvent(
event: Int,
path: String?,
) {
if (path == filename) {
XposedBridge.log("VpnHide: $filename changed (event=$event), invalidating UID cache")
systemServerTargetUids = null
}
}
}
}
targetUidsFileObserver = observer
observer.startWatching()
XposedBridge.log("VpnHide: watching $dir for $filename changes (inotify)")
@ -170,8 +178,10 @@ class HookEntry : IXposedHookLoadPackage {
*/
private fun hookNCWriteToParcel() {
XposedHelpers.findAndHookMethod(
NetworkCapabilities::class.java, "writeToParcel",
android.os.Parcel::class.java, Integer.TYPE,
NetworkCapabilities::class.java,
"writeToParcel",
android.os.Parcel::class.java,
Integer.TYPE,
object : XC_MethodHook() {
private val savedTransport = ThreadLocal<Long>()
private val savedCaps = ThreadLocal<Long>()
@ -188,7 +198,12 @@ class HookEntry : IXposedHookLoadPackage {
// Read all original values before mutating anything
val caps = XposedHelpers.getLongField(nc, "mNetworkCapabilities")
val ti = try { XposedHelpers.getObjectField(nc, "mTransportInfo") } catch (_: Throwable) { null }
val ti =
try {
XposedHelpers.getObjectField(nc, "mTransportInfo")
} catch (_: Throwable) {
null
}
// Mutate — if any step fails, restore everything
try {
@ -226,9 +241,10 @@ class HookEntry : IXposedHookLoadPackage {
XposedHelpers.setLongField(nc, "mTransportTypes", origTransport)
if (origCaps != null) XposedHelpers.setLongField(nc, "mNetworkCapabilities", origCaps)
XposedHelpers.setObjectField(nc, "mTransportInfo", origTi)
} catch (_: Throwable) {}
} catch (_: Throwable) {
}
}
}
},
)
XposedBridge.log("VpnHide: hooked NetworkCapabilities.writeToParcel")
}
@ -241,8 +257,10 @@ class HookEntry : IXposedHookLoadPackage {
*/
private fun hookNIWriteToParcel() {
XposedHelpers.findAndHookMethod(
NetworkInfo::class.java, "writeToParcel",
android.os.Parcel::class.java, Integer.TYPE,
NetworkInfo::class.java,
"writeToParcel",
android.os.Parcel::class.java,
Integer.TYPE,
object : XC_MethodHook() {
private val savedType = ThreadLocal<Int>()
@ -265,9 +283,10 @@ class HookEntry : IXposedHookLoadPackage {
savedType.remove()
try {
XposedHelpers.setIntField(param.thisObject, "mNetworkType", origType)
} catch (_: Throwable) {}
} catch (_: Throwable) {
}
}
}
},
)
XposedBridge.log("VpnHide: hooked NetworkInfo.writeToParcel")
}
@ -280,8 +299,10 @@ class HookEntry : IXposedHookLoadPackage {
*/
private fun hookLPWriteToParcel() {
XposedHelpers.findAndHookMethod(
LinkProperties::class.java, "writeToParcel",
android.os.Parcel::class.java, Integer.TYPE,
LinkProperties::class.java,
"writeToParcel",
android.os.Parcel::class.java,
Integer.TYPE,
object : XC_MethodHook() {
private val savedIfname = ThreadLocal<String>()
@ -304,9 +325,10 @@ class HookEntry : IXposedHookLoadPackage {
savedIfname.remove()
try {
XposedHelpers.setObjectField(param.thisObject, "mIfaceName", origIfname)
} catch (_: Throwable) {}
} catch (_: Throwable) {
}
}
}
},
)
XposedBridge.log("VpnHide: hooked LinkProperties.writeToParcel")
}

View file

@ -54,8 +54,8 @@ class MainActivity : ComponentActivity() {
}
}
private fun suExec(cmd: String): Pair<Int, String> {
return try {
private fun suExec(cmd: String): Pair<Int, String> =
try {
val proc = Runtime.getRuntime().exec(arrayOf("su", "-c", cmd))
try {
// Drain stderr in the background to prevent the process from
@ -73,21 +73,20 @@ private fun suExec(cmd: String): Pair<Int, String> {
Log.e(TAG, "su exec failed: ${e.message}")
-1 to ""
}
}
private suspend fun suExecAsync(cmd: String): Pair<Int, String> =
withContext(Dispatchers.IO) { suExec(cmd) }
private suspend fun suExecAsync(cmd: String): Pair<Int, String> = withContext(Dispatchers.IO) { suExec(cmd) }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VpnHideApp() {
val darkTheme = isSystemInDarkTheme()
val colorScheme = if (android.os.Build.VERSION.SDK_INT >= 31) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
if (darkTheme) darkColorScheme() else lightColorScheme()
}
val colorScheme =
if (android.os.Build.VERSION.SDK_INT >= 31) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
if (darkTheme) darkColorScheme() else lightColorScheme()
}
MaterialTheme(colorScheme = colorScheme) {
val context = LocalContext.current
@ -111,44 +110,56 @@ fun VpnHideApp() {
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
val (_, targetsRaw) = suExec(
"cat $KMOD_TARGETS 2>/dev/null || cat $ZYGISK_TARGETS 2>/dev/null || true"
)
val selected = targetsRaw.lines()
.map { it.trim() }
.filter { it.isNotEmpty() && !it.startsWith("#") }
.toSet()
val (_, targetsRaw) =
suExec(
"cat $KMOD_TARGETS 2>/dev/null || cat $ZYGISK_TARGETS 2>/dev/null || true",
)
val selected =
targetsRaw
.lines()
.map { it.trim() }
.filter { it.isNotEmpty() && !it.startsWith("#") }
.toSet()
val installedApps = pm.getInstalledApplications(PackageManager.GET_META_DATA)
val entries = installedApps.map { info ->
val label = try {
pm.getApplicationLabel(info).toString()
} catch (_: Exception) { info.packageName }
val icon = try {
pm.getApplicationIcon(info)
} catch (_: Exception) { null }
val isSystem = (info.flags and ApplicationInfo.FLAG_SYSTEM) != 0
AppEntry(
packageName = info.packageName,
label = label,
icon = icon,
isSystem = isSystem,
selected = info.packageName in selected,
)
}.sortedWith(compareByDescending<AppEntry> { it.selected }.thenBy { it.label.lowercase() })
val entries =
installedApps
.map { info ->
val label =
try {
pm.getApplicationLabel(info).toString()
} catch (_: Exception) {
info.packageName
}
val icon =
try {
pm.getApplicationIcon(info)
} catch (_: Exception) {
null
}
val isSystem = (info.flags and ApplicationInfo.FLAG_SYSTEM) != 0
AppEntry(
packageName = info.packageName,
label = label,
icon = icon,
isSystem = isSystem,
selected = info.packageName in selected,
)
}.sortedWith(compareByDescending<AppEntry> { it.selected }.thenBy { it.label.lowercase() })
allApps = entries
loading = false
}
}
val filteredApps = remember(allApps, searchQuery, showSystem) {
val q = searchQuery.trim().lowercase()
allApps.filter { app ->
(showSystem || !app.isSystem || app.selected) &&
(q.isEmpty() || app.label.lowercase().contains(q) || app.packageName.lowercase().contains(q))
val filteredApps =
remember(allApps, searchQuery, showSystem) {
val q = searchQuery.trim().lowercase()
allApps.filter { app ->
(showSystem || !app.isSystem || app.selected) &&
(q.isEmpty() || app.label.lowercase().contains(q) || app.packageName.lowercase().contains(q))
}
}
}
val selectedCount = remember(allApps) { allApps.count { it.selected } }
@ -157,19 +168,21 @@ fun VpnHideApp() {
topBar = {
TopAppBar(
title = { Text("VPN Hide") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
),
)
},
bottomBar = {
Surface(tonalElevation = 3.dp) {
Row(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(horizontal = 16.dp, vertical = 12.dp),
modifier =
Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
@ -189,18 +202,20 @@ fun VpnHideApp() {
}
}
}
}
},
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
modifier =
Modifier
.fillMaxSize()
.padding(innerPadding),
) {
// Search + system toggle
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
OutlinedTextField(
@ -231,11 +246,12 @@ fun VpnHideApp() {
AppRow(
app = app,
onToggle = {
allApps = allApps.map {
if (it.packageName == app.packageName) it.copy(selected = !it.selected) else it
}
allApps =
allApps.map {
if (it.packageName == app.packageName) it.copy(selected = !it.selected) else it
}
dirty = true
}
},
)
}
}
@ -247,9 +263,10 @@ fun VpnHideApp() {
if (saving) {
LaunchedEffect(Unit) {
val selected = allApps.filter { it.selected }.map { it.packageName }.sorted()
val body = "# Managed by VPN Hide app\n" +
selected.joinToString("\n") +
if (selected.isNotEmpty()) "\n" else ""
val body =
"# Managed by VPN Hide app\n" +
selected.joinToString("\n") +
if (selected.isNotEmpty()) "\n" else ""
try {
val (exitCode, _) = suExecAsync(buildSaveCommand(body, selected))
@ -269,7 +286,10 @@ fun VpnHideApp() {
}
}
private fun buildSaveCommand(body: String, selectedPackages: List<String>): String {
private fun buildSaveCommand(
body: String,
selectedPackages: List<String>,
): String {
val b64 = android.util.Base64.encodeToString(body.toByteArray(), android.util.Base64.NO_WRAP)
val parts = mutableListOf<String>()
@ -287,21 +307,22 @@ private fun buildSaveCommand(body: String, selectedPackages: List<String>): Stri
// Uses the same approach as kmod/service.sh — real newlines in $UIDS via heredoc-style
// accumulation, not printf \n escapes.
if (selectedPackages.isNotEmpty()) {
val uidResolution = buildString {
append("ALL_PKGS=\"\$(pm list packages -U 2>/dev/null)\"")
append("; UIDS=\"\"")
for (pkg in selectedPackages) {
append("; U=\$(echo \"\$ALL_PKGS\" | grep '^package:$pkg ' | sed 's/.*uid://')")
append("; if [ -n \"\$U\" ]; then if [ -z \"\$UIDS\" ]; then UIDS=\"\$U\"; else UIDS=\"\$UIDS")
// Real newline in the shell string — not \n escape
append("\n")
append("\$U\"; fi; fi")
val uidResolution =
buildString {
append("ALL_PKGS=\"\$(pm list packages -U 2>/dev/null)\"")
append("; UIDS=\"\"")
for (pkg in selectedPackages) {
append("; U=\$(echo \"\$ALL_PKGS\" | grep '^package:$pkg ' | sed 's/.*uid://')")
append("; if [ -n \"\$U\" ]; then if [ -z \"\$UIDS\" ]; then UIDS=\"\$U\"; else UIDS=\"\$UIDS")
// Real newline in the shell string — not \n escape
append("\n")
append("\$U\"; fi; fi")
}
append("; if [ -n \"\$UIDS\" ]; then echo \"\$UIDS\" > $PROC_TARGETS 2>/dev/null; echo \"\$UIDS\" > $SS_UIDS_FILE")
append("; else echo > $PROC_TARGETS 2>/dev/null; echo > $SS_UIDS_FILE; fi")
append("; chmod 644 $SS_UIDS_FILE 2>/dev/null")
append("; chcon u:object_r:system_data_file:s0 $SS_UIDS_FILE 2>/dev/null")
}
append("; if [ -n \"\$UIDS\" ]; then echo \"\$UIDS\" > $PROC_TARGETS 2>/dev/null; echo \"\$UIDS\" > $SS_UIDS_FILE")
append("; else echo > $PROC_TARGETS 2>/dev/null; echo > $SS_UIDS_FILE; fi")
append("; chmod 644 $SS_UIDS_FILE 2>/dev/null")
append("; chcon u:object_r:system_data_file:s0 $SS_UIDS_FILE 2>/dev/null")
}
parts += uidResolution
} else {
// No targets — clear the UIDs files. echo -n writes a zero-length
@ -314,12 +335,16 @@ private fun buildSaveCommand(body: String, selectedPackages: List<String>): Stri
}
@Composable
private fun AppRow(app: AppEntry, onToggle: () -> Unit) {
private fun AppRow(
app: AppEntry,
onToggle: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onToggle)
.padding(horizontal = 16.dp, vertical = 10.dp),
modifier =
Modifier
.fillMaxWidth()
.clickable(onClick = onToggle)
.padding(horizontal = 16.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(

View file

@ -42,20 +42,21 @@ private val VPN_PREFIXES = listOf("tun", "wg", "ppp", "tap", "ipsec", "xfrm")
data class CheckResult(
val name: String,
val passed: Boolean?, // null = informational
val detail: String
val passed: Boolean?, // null = informational
val detail: String,
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VpnHideTestApp() {
val darkTheme = isSystemInDarkTheme()
val colorScheme = if (android.os.Build.VERSION.SDK_INT >= 31) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
if (darkTheme) darkColorScheme() else lightColorScheme()
}
val colorScheme =
if (android.os.Build.VERSION.SDK_INT >= 31) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
if (darkTheme) darkColorScheme() else lightColorScheme()
}
MaterialTheme(colorScheme = colorScheme) {
val context = LocalContext.current
@ -75,18 +76,20 @@ fun VpnHideTestApp() {
topBar = {
TopAppBar(
title = { Text("VPNHide Diagnostics") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
),
)
}
},
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 16.dp)
modifier =
Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 16.dp),
) {
Spacer(Modifier.height(8.dp))
@ -94,7 +97,7 @@ fun VpnHideTestApp() {
text = summary,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier.padding(bottom = 8.dp),
)
Button(
@ -106,7 +109,7 @@ fun VpnHideTestApp() {
val passed = scored.count { it.passed == true }
summary = "$passed/${scored.size} passed"
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
) {
Text("Run All Checks")
}
@ -114,10 +117,11 @@ fun VpnHideTestApp() {
Spacer(Modifier.height(8.dp))
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(6.dp)
modifier =
Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
for (r in results) {
CheckCard(r)
@ -131,48 +135,58 @@ fun VpnHideTestApp() {
@Composable
fun CheckCard(r: CheckResult) {
val containerColor = when (r.passed) {
true -> Color(0xFFE8F5E9)
false -> Color(0xFFFFEBEE)
null -> MaterialTheme.colorScheme.surfaceVariant
}
val darkTheme = isSystemInDarkTheme()
val actualColor = if (darkTheme) {
val containerColor =
when (r.passed) {
true -> Color(0xFF1B5E20).copy(alpha = 0.3f)
false -> Color(0xFFB71C1C).copy(alpha = 0.3f)
true -> Color(0xFFE8F5E9)
false -> Color(0xFFFFEBEE)
null -> MaterialTheme.colorScheme.surfaceVariant
}
} else containerColor
val darkTheme = isSystemInDarkTheme()
val actualColor =
if (darkTheme) {
when (r.passed) {
true -> Color(0xFF1B5E20).copy(alpha = 0.3f)
false -> Color(0xFFB71C1C).copy(alpha = 0.3f)
null -> MaterialTheme.colorScheme.surfaceVariant
}
} else {
containerColor
}
val badgeColor = when (r.passed) {
true -> Color(0xFF2E7D32)
false -> Color(0xFFC62828)
null -> MaterialTheme.colorScheme.onSurfaceVariant
}
val badgeColor =
when (r.passed) {
true -> Color(0xFF2E7D32)
false -> Color(0xFFC62828)
null -> MaterialTheme.colorScheme.onSurfaceVariant
}
Card(
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(containerColor = actualColor),
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
) {
Column(modifier = Modifier.padding(12.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = r.name,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
)
Text(
text = when (r.passed) { true -> "PASS"; false -> "FAIL"; null -> "INFO" },
text =
when (r.passed) {
true -> "PASS"
false -> "FAIL"
null -> "INFO"
},
fontWeight = FontWeight.Bold,
fontSize = 13.sp,
color = badgeColor
color = badgeColor,
)
}
Spacer(Modifier.height(4.dp))
@ -180,7 +194,7 @@ fun CheckCard(r: CheckResult) {
text = r.detail,
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
)
}
}
@ -230,8 +244,11 @@ private fun runAllChecks(cm: ConnectivityManager): List<CheckResult> {
return results
}
private fun nativeCheck(name: String, block: () -> String): CheckResult {
return try {
private fun nativeCheck(
name: String,
block: () -> String,
): CheckResult =
try {
val raw = block()
val passed = raw.startsWith("PASS")
Log.i(TAG, "[$name] ${if (passed) "PASS" else "FAIL"}: $raw")
@ -241,23 +258,24 @@ private fun nativeCheck(name: String, block: () -> String): CheckResult {
Log.e(TAG, "[$name] $detail", e)
CheckResult(name, false, detail)
}
}
private fun checkHasTransportVpn(cm: ConnectivityManager): CheckResult {
val name = "7. hasTransport(VPN)"
Log.i(TAG, "=== CHECK: $name ===")
val net = cm.activeNetwork
if (net == null) return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps = cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps =
cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val hasVpn = caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
val hasWifi = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
val hasCellular = caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
val detail = if (!hasVpn) {
"PASS: hasTransport(VPN)=false, WIFI=$hasWifi, CELLULAR=$hasCellular"
} else {
"FAIL: hasTransport(VPN)=true, WIFI=$hasWifi, CELLULAR=$hasCellular"
}
val detail =
if (!hasVpn) {
"PASS: hasTransport(VPN)=false, WIFI=$hasWifi, CELLULAR=$hasCellular"
} else {
"FAIL: hasTransport(VPN)=true, WIFI=$hasWifi, CELLULAR=$hasCellular"
}
Log.i(TAG, "[$name] $detail")
Log.i(TAG, "[$name] caps.toString(): $caps")
return CheckResult(name, !hasVpn, detail)
@ -266,10 +284,12 @@ private fun checkHasTransportVpn(cm: ConnectivityManager): CheckResult {
private fun checkHasCapabilityNotVpn(cm: ConnectivityManager): CheckResult {
val name = "8. hasCapability(NOT_VPN)"
Log.i(TAG, "=== CHECK: $name ===")
val net = cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps = cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val net =
cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps =
cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val notVpn = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
val detail = if (notVpn) "PASS: NOT_VPN capability present" else "FAIL: NOT_VPN capability MISSING"
Log.i(TAG, "[$name] $detail")
@ -279,10 +299,12 @@ private fun checkHasCapabilityNotVpn(cm: ConnectivityManager): CheckResult {
private fun checkTransportInfo(cm: ConnectivityManager): CheckResult {
val name = "9. getTransportInfo()"
Log.i(TAG, "=== CHECK: $name ===")
val net = cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps = cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val net =
cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps =
cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val info = caps.transportInfo
val className = info?.javaClass?.name ?: "null"
Log.i(TAG, "[$name] transportInfo class=$className, value=$info")
@ -296,8 +318,9 @@ private fun checkNetworkInterfaceEnum(): CheckResult {
val name = "10. NetworkInterface enum"
Log.i(TAG, "=== CHECK: $name ===")
return try {
val ifaces = NetworkInterface.getNetworkInterfaces()
?: return CheckResult(name, true, "PASS: returned null").also { Log.i(TAG, "[$name] ${it.detail}") }
val ifaces =
NetworkInterface.getNetworkInterfaces()
?: return CheckResult(name, true, "PASS: returned null").also { Log.i(TAG, "[$name] ${it.detail}") }
val allNames = mutableListOf<String>()
val vpnNames = mutableListOf<String>()
for (iface in ifaces) {
@ -305,11 +328,12 @@ private fun checkNetworkInterfaceEnum(): CheckResult {
Log.i(TAG, "[$name] ${iface.name} up=${iface.isUp} loopback=${iface.isLoopback} p2p=${iface.isPointToPoint}")
if (VPN_PREFIXES.any { iface.name.startsWith(it) }) vpnNames.add(iface.name)
}
val detail = if (vpnNames.isEmpty()) {
"PASS: ${allNames.size} ifaces [${allNames.joinToString()}], no VPN"
} else {
"FAIL: VPN [${vpnNames.joinToString()}] in [${allNames.joinToString()}]"
}
val detail =
if (vpnNames.isEmpty()) {
"PASS: ${allNames.size} ifaces [${allNames.joinToString()}], no VPN"
} else {
"FAIL: VPN [${vpnNames.joinToString()}] in [${allNames.joinToString()}]"
}
Log.i(TAG, "[$name] $detail")
CheckResult(name, vpnNames.isEmpty(), detail)
} catch (e: Exception) {
@ -341,11 +365,12 @@ private fun checkAllNetworksVpn(cm: ConnectivityManager): CheckResult {
Log.i(TAG, "[$name] Network $net: [${transports.joinToString()}] VPN=$hasVpn")
if (hasVpn) vpnNetworks.add(net.toString())
}
val detail = if (vpnNetworks.isEmpty()) {
"PASS: ${networks.size} networks, none have TRANSPORT_VPN"
} else {
"FAIL: ${vpnNetworks.size} network(s) with TRANSPORT_VPN: [${vpnNetworks.joinToString()}]"
}
val detail =
if (vpnNetworks.isEmpty()) {
"PASS: ${networks.size} networks, none have TRANSPORT_VPN"
} else {
"FAIL: ${vpnNetworks.size} network(s) with TRANSPORT_VPN: [${vpnNetworks.joinToString()}]"
}
Log.i(TAG, "[$name] $detail")
return CheckResult(name, vpnNetworks.isEmpty(), detail)
}
@ -353,10 +378,12 @@ private fun checkAllNetworksVpn(cm: ConnectivityManager): CheckResult {
private fun checkActiveNetworkVpn(cm: ConnectivityManager): CheckResult {
val name = "11. ActiveNetwork transports"
Log.i(TAG, "=== CHECK: $name ===")
val net = cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps = cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val net =
cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val caps =
cm.getNetworkCapabilities(net)
?: return CheckResult(name, true, "PASS: no capabilities").also { Log.i(TAG, "[$name] ${it.detail}") }
val transports = mutableListOf<String>()
mapOf(
NetworkCapabilities.TRANSPORT_CELLULAR to "CELLULAR",
@ -367,11 +394,12 @@ private fun checkActiveNetworkVpn(cm: ConnectivityManager): CheckResult {
NetworkCapabilities.TRANSPORT_WIFI_AWARE to "WIFI_AWARE",
).forEach { (id, label) -> if (caps.hasTransport(id)) transports.add(label) }
val hasVpn = caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
val detail = if (!hasVpn) {
"PASS: transports=[${transports.joinToString()}], no VPN"
} else {
"FAIL: transports include VPN: [${transports.joinToString()}]"
}
val detail =
if (!hasVpn) {
"PASS: transports=[${transports.joinToString()}], no VPN"
} else {
"FAIL: transports include VPN: [${transports.joinToString()}]"
}
Log.i(TAG, "[$name] $detail")
return CheckResult(name, !hasVpn, detail)
}
@ -379,10 +407,12 @@ private fun checkActiveNetworkVpn(cm: ConnectivityManager): CheckResult {
private fun checkLinkPropertiesIfname(cm: ConnectivityManager): CheckResult {
val name = "12. LinkProperties ifname"
Log.i(TAG, "=== CHECK: $name ===")
val net = cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val lp = cm.getLinkProperties(net)
?: return CheckResult(name, true, "PASS: no link properties").also { Log.i(TAG, "[$name] ${it.detail}") }
val net =
cm.activeNetwork
?: return CheckResult(name, true, "PASS: no active network").also { Log.i(TAG, "[$name] ${it.detail}") }
val lp =
cm.getLinkProperties(net)
?: return CheckResult(name, true, "PASS: no link properties").also { Log.i(TAG, "[$name] ${it.detail}") }
val ifname = lp.interfaceName ?: "(null)"
val routes = lp.routes.map { "${it.destination} via ${it.gateway} dev ${it.`interface`}" }
val dns = lp.dnsServers.map { it.hostAddress ?: "?" }
@ -391,11 +421,12 @@ private fun checkLinkPropertiesIfname(cm: ConnectivityManager): CheckResult {
Log.i(TAG, "[$name] dns=${dns.joinToString(", ")}")
Log.i(TAG, "[$name] httpProxy=${lp.httpProxy}")
val isVpn = VPN_PREFIXES.any { ifname.startsWith(it) }
val detail = if (!isVpn) {
"PASS: ifname=$ifname, ${routes.size} routes, dns=[${dns.joinToString()}]"
} else {
"FAIL: ifname=$ifname is a VPN interface"
}
val detail =
if (!isVpn) {
"PASS: ifname=$ifname, ${routes.size} routes, dns=[${dns.joinToString()}]"
} else {
"FAIL: ifname=$ifname is a VPN interface"
}
Log.i(TAG, "[$name] $detail")
return CheckResult(name, !isVpn, detail)
}
@ -409,11 +440,12 @@ private fun checkProxyHost(): CheckResult {
val socksPort = System.getProperty("socksProxyPort")
Log.i(TAG, "[$name] http=$httpHost:$httpPort, socks=$socksHost:$socksPort")
val hasProxy = !httpHost.isNullOrEmpty() || !socksHost.isNullOrEmpty()
val detail = if (!hasProxy) {
"PASS: no proxy (http=$httpHost, socks=$socksHost)"
} else {
"FAIL: proxy found — http=$httpHost:$httpPort, socks=$socksHost:$socksPort"
}
val detail =
if (!hasProxy) {
"PASS: no proxy (http=$httpHost, socks=$socksHost)"
} else {
"FAIL: proxy found — http=$httpHost:$httpPort, socks=$socksHost:$socksPort"
}
Log.i(TAG, "[$name] $detail")
return CheckResult(name, !hasProxy, detail)
}
@ -432,11 +464,12 @@ private fun checkProcNetRouteJava(): CheckResult {
if (VPN_PREFIXES.any { line!!.startsWith(it) }) vpnLines.add(line!!.take(60))
}
}
val detail = if (vpnLines.isEmpty()) {
"PASS: ${allLines.size} lines, no VPN entries"
} else {
"FAIL: ${vpnLines.size} VPN lines:\n${vpnLines.joinToString("\n") { " $it" }}"
}
val detail =
if (vpnLines.isEmpty()) {
"PASS: ${allLines.size} lines, no VPN entries"
} else {
"FAIL: ${vpnLines.size} VPN lines:\n${vpnLines.joinToString("\n") { " $it" }}"
}
Log.i(TAG, "[$name] $detail")
CheckResult(name, vpnLines.isEmpty(), detail)
} catch (e: Exception) {

View file

@ -6,18 +6,32 @@ object NativeChecks {
}
external fun checkIoctlSiocgifflags(): String
external fun checkIoctlSiocgifconf(): String
external fun checkGetifaddrs(): String
external fun checkProcNetRoute(): String
external fun checkProcNetIfInet6(): String
external fun checkNetlinkGetlink(): String
external fun checkNetlinkGetroute(): String
external fun checkProcNetIpv6Route(): String
external fun checkProcNetTcp(): String
external fun checkProcNetTcp6(): String
external fun checkProcNetUdp(): String
external fun checkProcNetUdp6(): String
external fun checkProcNetDev(): String
external fun checkProcNetFibTrie(): String
external fun checkSysClassNet(): String
}