{"id":51893167,"url":"https://github.com/shadowsocks/shadowsocks-ohos","last_synced_at":"2026-07-29T10:00:30.987Z","repository":{"id":373048908,"uuid":"1309811328","full_name":"shadowsocks/shadowsocks-ohos","owner":"shadowsocks","description":"Shadowsocks for HarmonyOS NEXT","archived":false,"fork":false,"pushed_at":"2026-07-25T09:38:02.000Z","size":284,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-28T09:22:42.651Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shadowsocks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-07-23T10:22:32.000Z","updated_at":"2026-07-28T02:24:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shadowsocks/shadowsocks-ohos","commit_stats":null,"previous_names":["shadowsocks/shadowsocks-ohos"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/shadowsocks/shadowsocks-ohos","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shadowsocks%2Fshadowsocks-ohos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shadowsocks%2Fshadowsocks-ohos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shadowsocks%2Fshadowsocks-ohos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shadowsocks%2Fshadowsocks-ohos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shadowsocks","download_url":"https://codeload.github.com/shadowsocks/shadowsocks-ohos/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shadowsocks%2Fshadowsocks-ohos/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36027305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-29T02:00:04.910Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-26T07:00:25.608Z","updated_at":"2026-07-29T10:00:30.960Z","avatar_url":"https://github.com/shadowsocks.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shadowsocks for HarmonyOS NEXT\n\n[![Lint](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/lint.yml/badge.svg)](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/lint.yml)\n[![Core tests](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/test-core.yml/badge.svg)](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/test-core.yml)\n[![Cross-compile](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/test-cross.yml/badge.svg)](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/test-cross.yml)\n[![Tun e2e](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/test-tun.yml/badge.svg)](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/test-tun.yml)\n[![HarmonyOS build](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/harmonyos-build.yml/badge.svg)](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/harmonyos-build.yml)\n[![ArkTS unit tests](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/harmonyos-unit-tests.yml/badge.svg)](https://github.com/shadowsocks/shadowsocks-ohos/actions/workflows/harmonyos-unit-tests.yml)\n\nA native HarmonyOS NEXT (ArkTS/ArkUI, Stage model) client, sharing the Rust\ncore (`shadowsocks-rust`) with the Android app through a C ABI + NAPI bridge.\n\n\u003e HarmonyOS 2–4 devices run Android APKs and are covered by the main project's\n\u003e GMS-free `freedom` flavor. This subproject targets **HarmonyOS NEXT (5.x)**,\n\u003e which has no Android runtime.\n\n## Layout\n\n```\nharmony/\n├── AppScope/                  application-level config\n├── entry/                     main HAP module\n│   └── src/main\n│       ├── ets/\n│       │   ├── entryability/  UIAbility entry\n│       │   ├── pages/         ArkUI (profile form, ss:// import, connect)\n│       │   ├── model/         Profile (SIP002 parsing) + persistence\n│       │   └── vpnability/    VpnExtensionAbility driving the native core\n│       └── cpp/               NAPI shim (libsslocal.so) around the Rust core\n└── native/\n    ├── sslocal-ffi/           Rust crate: C ABI over shadowsocks-service\n    ├── build-ohos.sh          cross-compile for aarch64-unknown-linux-ohos\n    └── ohos-cc-wrapper.sh     zig-based C shim for SDK-less `cargo check`\n```\n\nThe Rust crate path-depends on `core/src/main/rust/shadowsocks-rust` — the\nsame submodule the Android app builds, so both platforms ship the same core.\n\n## Building\n\nPrerequisites:\n\n* DevEco Studio 5.x with the HarmonyOS NEXT SDK (API 12+)\n* Rust with `rustup target add aarch64-unknown-linux-ohos`\n* The OpenHarmony native SDK, exported as `OHOS_NDK_HOME=…/ohos-sdk/native`\n\nSteps:\n\n1. `native/build-ohos.sh` — builds `libsslocal_core.a` and installs it into\n   `entry/libs/arm64-v8a/` where the CMake NAPI build links it.\n2. `ohpm install` to resolve dependencies (needs the ohpm registry).\n3. Build the HAP with the command-line tools (or DevEco Studio):\n\n   ```sh\n   export DEVECO_SDK_HOME=\u003ccommand-line-tools\u003e/sdk\n   hvigorw assembleHap --mode module -p product=default -p buildMode=debug\n   ```\n\n   This compiles the ArkTS, builds `libsslocal.so` (NAPI shim + Rust core) via\n   CMake/Ninja, and emits `entry/build/default/outputs/default/entry-default-unsigned.hap`.\n\n### Debug signing (no Huawei account)\n\nFor a debug/emulator build, sign locally with the OpenHarmony sample signing\nmaterials that ship in the SDK — DevEco's auto-sign uses the same scheme:\n\n```sh\nnative/sign-hap-debug.sh entry/build/default/outputs/default/entry-default-unsigned.hap\n```\n\nThis signs a debug provisioning profile for the app's bundle and signs +\nverifies the HAP, producing `entry-default-signed.hap`. A Huawei developer\naccount is only needed for release signing / store distribution.\n\n### Running on the emulator\n\n`hdc -t \u003ctarget\u003e install entry-default-signed.hap` then launch the app. The\nHarmonyOS emulator needs a system image installed via DevEco / `Emulator\n-install`, which downloads from Huawei's servers.\n\n## Testing\n\n* **Host e2e (no HarmonyOS SDK needed)** — `./test-e2e-host.sh`, which runs\n  three independent steps; pass names to run a subset (`./test-e2e-host.sh\n  cross tun`):\n  1. `tests` — Rust test suite, including `tests/e2e.rs`: an in-process\n     shadowsocks server, an sslocal instance driven through the same C ABI the\n     NAPI bridge uses, and a SOCKS5 round-trip through the encrypted tunnel.\n  2. `cross` — cross-compile check that the whole core builds for\n     `aarch64-unknown-linux-ohos` (real SDK clang if present, else a zig cc\n     shim for the C bits).\n  3. `tun` — **tun packet-routing e2e** (Linux, `/dev/net/tun`, root or\n     passwordless sudo): sends a real TCP flow into a tun device and asserts it\n     round-trips through the tunnel. On non-Linux hosts run it in a privileged\n     container with `native/run-tun-e2e-docker.sh`.\n\n  Each step is also its own CI workflow, so a red badge names the surface that\n  broke: `test-core.yml`, `test-cross.yml`, `test-tun.yml`, plus `lint.yml` for\n  rustfmt/clippy/shellcheck. All four gate every pull request.\n* **ArkTS unit tests** — `entry/src/test` (hypium) covers `ss://` URL parsing\n  and both SOCKS and tun config serialization. Run from DevEco Studio, or\n  headless with `hvigorw test --mode module -p module=entry -p product=default`\n  — **on macOS or Windows**: the runner drives the SDK's previewer, which does\n  not work on Linux.\n* **On-device e2e (emulator)** — `ci/hos-emulator-e2e.sh` builds and debug-signs\n  both HAPs, boots the HarmonyOS emulator, installs them and runs\n  `entry/src/ohosTest` against a shadowsocks server on the host:\n\n  ```sh\n  HOS_TOOLS=~/workspace/command-line-tools HOS_IMAGES=~/Library/Huawei/Sdk \\\n      ci/hos-emulator-e2e.sh\n  ```\n\n  `SocksE2e.test.ets` is the real end-to-end case: it starts the core in SOCKS\n  mode through the NAPI bridge and fetches a marker page that is only reachable\n  from the far end of the tunnel (a companion spec asserts it is unreachable\n  without it). `SslocalNativeTest` covers the rest of the NAPI surface.\n  `VpnE2e.test.ets` is skipped here — the public emulator image never delivers\n  guest traffic to `vpn-tun`, so it is a real-device test (see\n  `docs/hos-emulator-vpn.md` §2a).\n* **CI** — one workflow per surface, so a failure names what broke:\n\n  | workflow | what it runs | where |\n  |---|---|---|\n  | `lint.yml` | rustfmt, clippy, shellcheck | hosted Linux |\n  | `test-core.yml` | Rust unit tests + host e2e tunnels | hosted Linux |\n  | `test-cross.yml` | `aarch64-unknown-linux-ohos` build check | hosted Linux |\n  | `test-tun.yml` | tun packet-routing e2e | hosted Linux |\n  | `harmonyos-build.yml` | HAP build and debug signing | hosted Linux |\n  | `harmonyos-unit-tests.yml` | ArkTS unit tests | hosted macOS |\n  | `harmonyos-e2e.yml` | on-device suites on the emulator | self-hosted macOS |\n\n  The first four gate every pull request. The HarmonyOS trio needs the DevEco\n  toolchain, which cannot be downloaded by a runner or redistributed, so it is\n  streamed from a private bucket populated by `ci/package-hos-toolchain.sh` and\n  authenticated with the repository secrets `R2_API_TOKEN` (a Cloudflare API\n  token) and `R2_ENDPOINT` — R2's S3 API takes that token as its ID plus the\n  SHA-256 of its value, which `ci/r2-env.sh` derives at runtime. The unpacked\n  toolchain is cached between runs, keyed on the archive's checksum from the\n  bundle manifest, so re-uploading a bundle invalidates it on its own. Since\n  secrets are not exposed to fork pull requests, those three run on pushes to\n  `main` and on demand.\n\n  Huawei ships the command-line tools for Linux x64 as well, so the HAP build\n  runs on a Linux runner — more free disk, no Xcode eviction, faster start. Two\n  things keep the Mac in the picture:\n\n  * The **ArkTS unit tests** cannot run on Linux. The task that emits the\n    results is what actually executes the specs, by driving the SDK's previewer\n    (a GUI component); on Linux it either throws or hangs, and a deliberately\n    failing spec produces no output at all. So `harmonyos-unit-tests.yml` uses\n    the macOS bundle.\n  * The **emulator** is macOS/Windows-only, and its binary and guest are both\n    arm64, so it needs HVF — which GitHub's Apple-silicon runners do not expose\n    (no nested virtualization) and whose Intel runners cannot use for an arm64\n    guest. `harmonyos-e2e.yml` therefore targets a self-hosted Apple-silicon\n    runner, and is skipped unless the repository variables `HOS_SELF_HOSTED`,\n    `HOS_TOOLS_PATH` and `HOS_IMAGES_PATH` are set, so pushes are never left\n    queued against an offline runner.\n\n  Common setup — the shared `shadowsocks-rust` checkout, the toolchain and the\n  cargo cache — lives in the composite action `.github/actions/rust-core`,\n  which is also where the core's pinned ref is defined.\n\n## Tun mode\n\n`SsVpnExtensionAbility` installs a default route and hands the tun fd from\n`VpnConnection.create()` to the core via `sslocal.startTunFd`. The core's tun\nstack (smoltcp) terminates each TCP/UDP flow off the tun and re-establishes it\nthrough the shadowsocks tunnel — the role tun2socks plays in the Android\nclient. The interface name is recovered from the fd (TUNGETIFF) so the tun\ncrate can read the interface address. Verified end-to-end by the tun\npacket-routing e2e above.\n\n## Status and known gaps\n\n* **Server-connection bypass**: handled via `VpnConnection.protectProcessNet()`\n  (API 22+), which keeps every socket the VPN-extension process creates — the\n  native core included — outside the tunnel, so the default route cannot loop\n  the server connection back into the tun. On API \u003c 22 runtimes the API does\n  not exist and there is no per-socket hook into the core yet (shadowsocks-rust\n  only wires its protect callback on Android); the ability logs a warning and\n  the server must be reachable through a more specific route. The host tun e2e\n  models the bypass by running the server in a separate network namespace.\n* Third-party VPN apps on HarmonyOS NEXT require Huawei's approval for the\n  VPN extension capability before store distribution.\n* SIP003 plugins run **in-process** (external plugin binaries cannot be\n  spawned on HarmonyOS): `obfs-local`/`simple-obfs` (http/tls) and\n  `v2ray-plugin` (websocket, optional TLS) are built into the core; any other\n  plugin name is rejected, and UDP does not pass through a plugin.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshadowsocks%2Fshadowsocks-ohos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshadowsocks%2Fshadowsocks-ohos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshadowsocks%2Fshadowsocks-ohos/lists"}