Skip to content

NodePassProject/EverywhereCore-Android

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EverywhereCore-Android

Go core (mihomo + sing-box + xray-core, glued by gomobile) packaged as an Android .aar. Consumed by the Everywhere Android app's VpnService via Gradle. Sibling of EverywhereCore (iOS/macOS xcframework) and EverywhereCore-Windows (c-shared DLL) — same wrapper design, same upstream pins.

Each core runs its own TUN inbound directly on the file descriptor returned by VpnService.Builder.establish() — there is no userland tun→socks shim in between.

The three core dependencies are not vendored. go/go.mod pins them by Go-module-compatible semver; the GitHub Actions workflow at .github/workflows/upstream-watch.yml polls the Go proxy daily and auto-cuts a new release whenever any upstream moves.

Layout

go/                        gomobile entry package; *.go + go.mod
Scripts/build.sh           gomobile bind → EverywhereCore.aar
.github/workflows/
  upstream-watch.yml       daily upstream poll + auto-release

Building locally

git clone https://github.com/NodePassProject/EverywhereCore-Android
cd EverywhereCore-Android
Scripts/build.sh           # gomobile bind, deps fetched from Go proxy

Requires an Android SDK with an NDK (ANDROID_HOME or ANDROID_NDK_HOME). Produces ./EverywhereCore.aar with arm64-v8a, armeabi-v7a, x86_64, and x86 slices, built against -androidapi 26 (override with ANDROID_API=<n>).

Consuming from an Android project

Drop the .aar (from a local build or a GitHub Release asset) into app/libs/ and add:

dependencies {
    implementation(files("libs/EverywhereCore.aar"))
}

The bindings land in the com.argsment.everywhere.evcore package as a static-method facade named Evcore.

Calling from a VpnService

class EverywhereVpnService : VpnService() {
    private inner class ServiceProtector : Protector {
        override fun protect(fd: Int): Boolean = this@EverywhereVpnService.protect(fd)
    }

    private var tunFd: ParcelFileDescriptor? = null

    fun startTunnel(coreType: String, config: String) {
        val builder = Builder()
            .setMtu(MTU)
            .addAddress("172.19.0.1", 30)
            .addRoute("0.0.0.0", 0)
            .addDnsServer("172.19.0.2")
        val pfd = builder.establish() ?: error("VPN permission revoked")
        tunFd = pfd

        Evcore.setSocketProtector(ServiceProtector())   // BEFORE startCore
        Evcore.setResourcesPath(noBackupFilesDir.absolutePath)
        Evcore.startCore(coreType, config, pfd.fd.toLong(), MTU.toLong())
    }

    fun stopTunnel() {
        Evcore.stopAll()
        tunFd?.close()      // unblocks any reader still inside the cores
        tunFd = null
    }
}

coreType is "xray", "singbox", or "mihomo". The config string must declare a TUN inbound for the active core (the app-side ConfigNormalizer takes care of that): a tun inbound for Xray, a type: tun inbound for sing-box, an enabled tun: block for mihomo. The fd itself is injected by this framework — never write it into the config text.

setSocketProtector is mandatory. Android routes every socket the VPN process creates back into its own TUN unless VpnService.protect(fd) is called on it — without the protector, core outbound traffic loops through the tunnel and nothing connects. (iOS has no equivalent because an NE's sockets bypass the tunnel by design.)

Feed network changes from a ConnectivityManager.NetworkCallback:

override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
    val link = connectivityManager.getLinkProperties(network) ?: return
    val index = NetworkInterface.getByName(link.interfaceName)?.index ?: -1
    Evcore.updateDefaultInterface(
        link.interfaceName ?: "", index,
        !caps.hasCapability(NET_CAPABILITY_NOT_METERED), false)
}
override fun onLost(network: Network) {
    Evcore.updateDefaultInterface("", -1, false, false)
}

How releases happen

.github/workflows/upstream-watch.yml runs daily at 08:00 UTC and on manual workflow_dispatch. Each run:

  1. Queries proxy.golang.org/<module>/@latest for mihomo, sing-box, and xray-core (stable tags only — no pre-releases).
  2. Compares each against the version currently pinned in go/go.mod.
  3. If at least one is newer (or if dispatched with force_release: true):
    • go get each to its latest, go mod tidy
    • Scripts/build.sh to build the aar
    • Compute SHA256, commit + tag vYYYY.MM.DD
    • Append .1, .2, … to the tag if multiple runs land same day
    • Push tag + main; gh release create with the aar attached
  4. Otherwise: no-op (logged as a notice).

To bootstrap the first release after pushing this repo, run the workflow from the Actions tab with force_release: true.

Pinning a specific upstream version manually

Edit go/go.mod, push to main. The next cron run will detect the manual pin is current (or stale) and act accordingly. If you want a release for a manual bump immediately, dispatch the workflow with force_release: true.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors