diff --git a/.github/workflows/build-manual.yaml b/.github/workflows/build-manual.yaml new file mode 100644 index 0000000000..5d7ec6ab60 --- /dev/null +++ b/.github/workflows/build-manual.yaml @@ -0,0 +1,106 @@ +name: Build Manual + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to build from' + required: true + default: 'main' + type: branch + flavor: + description: 'Build flavor' + required: true + default: 'meta' + type: choice + options: + - meta + - alpha + release-tag: + description: 'Release tag' + required: true + default: 'manual' + +jobs: + BuildManual: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch }} + fetch-depth: 0 + + - name: Checkout submodules + run: git submodule update --init --recursive --force + + - name: Setup Java + uses: actions/setup-java@v5 + with: + distribution: "temurin" + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c + with: + go-download-base-url: 'https://github.com/MetaCubeX/go/releases/download/build' + go-version: '1.26' + cache: false + + - name: Apply Patches + run: | + cd $(go env GOROOT) + for p in $GITHUB_WORKSPACE/.github/patch/*.patch; do patch --verbose -p 1 < "$p"; done + + - uses: actions/cache@v5 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Update CA + run: | + sudo apt-get update && sudo apt-get install ca-certificates + sudo update-ca-certificates + cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt + + - name: Restore Keystore + env: + KEYSTORE: ${{ secrets.KEYSTORE }} + run: | + echo "$KEYSTORE" | base64 --decode > release.keystore + + - name: Signing properties + env: + SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} + SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} + run: | + echo "keystore.password=$SIGNING_STORE_PASSWORD" > signing.properties + echo "key.alias=$SIGNING_KEY_ALIAS" >> signing.properties + echo "key.password=$SIGNING_KEY_PASSWORD" >> signing.properties + + - name: Manual Build + if: success() + run: | + if [ "${{ inputs.flavor }}" = "meta" ]; then + ./gradlew --no-daemon app:assembleMetaRelease + else + ./gradlew --no-daemon app:assembleAlphaRelease + fi + + - name: Upload Release + uses: softprops/action-gh-release@v3 + if: ${{ success() }} + with: + tag_name: ${{ inputs.release-tag }} + files: app/build/outputs/apk/${{ inputs.flavor }}/release/*.apk + generate_release_notes: true diff --git a/core/src/main/cpp/main.c b/core/src/main/cpp/main.c index 61e431c8a1..f1e4d85c81 100644 --- a/core/src/main/cpp/main.c +++ b/core/src/main/cpp/main.c @@ -116,6 +116,8 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobje jstring gateway, jstring portal, jstring dns, + jboolean disable_icmp_forwarding, + jint icmp_timeout, jobject cb) { TRACE_METHOD(); @@ -125,7 +127,7 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobje scoped_string _dns = get_string(dns); jobject _interface = new_global(cb); - startTun(fd, _stack, _gateway, _portal, _dns, _interface); + startTun(fd, _stack, _gateway, _portal, _dns, (int) disable_icmp_forwarding, (int) icmp_timeout, _interface); } JNIEXPORT void JNICALL diff --git a/core/src/main/golang/native/tun.go b/core/src/main/golang/native/tun.go index 860a1fdc21..36f9863170 100644 --- a/core/src/main/golang/native/tun.go +++ b/core/src/main/golang/native/tun.go @@ -64,7 +64,7 @@ func (t *remoteTun) close() { } //export startTun -func startTun(fd C.int, stack, gateway, portal, dns C.c_string, callback unsafe.Pointer) C.int { +func startTun(fd C.int, stack, gateway, portal, dns C.c_string, disableICMPForwarding C.int, icmpTimeout C.int, callback unsafe.Pointer) C.int { rTunLock.Lock() defer rTunLock.Unlock() @@ -83,7 +83,7 @@ func startTun(fd C.int, stack, gateway, portal, dns C.c_string, callback unsafe. app.ApplyTunContext(remote.markSocket, remote.querySocketUid) - closer, err := tun.Start(f, s, g, p, d) + closer, err := tun.Start(f, s, g, p, d, disableICMPForwarding != 0, int64(icmpTimeout)) if err != nil { remote.close() diff --git a/core/src/main/golang/native/tun/tun.go b/core/src/main/golang/native/tun/tun.go index ee2c2a9124..aaa52f4d69 100644 --- a/core/src/main/golang/native/tun/tun.go +++ b/core/src/main/golang/native/tun/tun.go @@ -14,7 +14,7 @@ import ( "github.com/metacubex/mihomo/tunnel" ) -func Start(fd int, stack, gateway, portal, dns string) (io.Closer, error) { +func Start(fd int, stack, gateway, portal, dns string, disableICMPForwarding bool, icmpTimeout int64) (io.Closer, error) { log.Debugln("TUN: fd = %d, stack = %s, gateway = %s, portal = %s, dns = %s", fd, stack, gateway, portal, dns) tunStack, ok := C.StackTypeMapping[strings.ToLower(stack)] @@ -52,16 +52,18 @@ func Start(fd int, stack, gateway, portal, dns string) (io.Closer, error) { } options := LC.Tun{ - Enable: true, - Device: sing_tun.InterfaceName, - Stack: tunStack, - DNSHijack: dnsHijack, - AutoRoute: false, // had set route in TunService.kt - AutoDetectInterface: false, // implements by VpnService::protect - Inet4Address: prefix4, - Inet6Address: prefix6, - MTU: 9000, // private const val TUN_MTU = 9000 in TunService.kt - FileDescriptor: fd, + Enable: true, + Device: sing_tun.InterfaceName, + Stack: tunStack, + DNSHijack: dnsHijack, + AutoRoute: false, // had set route in TunService.kt + AutoDetectInterface: false, // implements by VpnService::protect + Inet4Address: prefix4, + Inet6Address: prefix6, + MTU: 9000, // private const val TUN_MTU = 9000 in TunService.kt + FileDescriptor: fd, + DisableICMPForwarding: disableICMPForwarding, + ICMPTimeout: icmpTimeout, } tunOptions, _ := json.Marshal(options) diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index edba62bed0..a0753d1917 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -70,10 +70,12 @@ object Clash { gateway: String, portal: String, dns: String, + disableIcmpForwarding: Boolean, + icmpTimeout: Int, markSocket: (Int) -> Boolean, querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int ) { - Bridge.nativeStartTun(fd, stack, gateway, portal, dns, object : TunInterface { + Bridge.nativeStartTun(fd, stack, gateway, portal, dns, disableIcmpForwarding, icmpTimeout, object : TunInterface { override fun markSocket(fd: Int) { markSocket(fd) } diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt index fa3b9247ef..1e8231849f 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt @@ -19,7 +19,16 @@ object Bridge { external fun nativeNotifyDnsChanged(dnsList: String) external fun nativeNotifyTimeZoneChanged(name: String, offset: Int) external fun nativeNotifyInstalledAppChanged(uidList: String) - external fun nativeStartTun(fd: Int, stack: String, gateway: String, portal: String, dns: String, cb: TunInterface) + external fun nativeStartTun( + fd: Int, + stack: String, + gateway: String, + portal: String, + dns: String, + disableIcmpForwarding: Boolean, + icmpTimeout: Int, + cb: TunInterface + ) external fun nativeStopTun() external fun nativeStartHttp(listenAt: String): String? external fun nativeStopHttp() diff --git a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt index 93e1ee3d07..432d31927e 100644 --- a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt +++ b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt @@ -70,6 +70,21 @@ class NetworkSettingsDesign( configure = vpnDependencies::add, ) + switch( + value = srvStore::disableIcmpForwarding, + title = R.string.disable_icmp_forwarding, + summary = R.string.disable_icmp_forwarding_summary, + configure = vpnDependencies::add, + ) + + editableText( + value = srvStore::icmpTimeout, + adapter = NullableTextAdapter.Int, + title = R.string.icmp_timeout, + empty = R.string.icmp_timeout_default, + configure = vpnDependencies::add, + ) + switch( value = srvStore::allowBypass, title = R.string.allow_bypass, diff --git a/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt b/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt index cf2dcb79fc..7e6ce7a708 100644 --- a/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt +++ b/design/src/main/java/com/github/kr328/clash/design/preference/Value.kt @@ -19,6 +19,16 @@ interface NullableTextAdapter { } } + val Int = object : NullableTextAdapter { + override fun from(value: Int): String? { + return if (value > 0) value.toString() else "" + } + + override fun to(text: String?): Int { + return text?.trim()?.toIntOrNull()?.coerceAtLeast(0) ?: 0 + } + } + val String = object : NullableTextAdapter { override fun from(value: String?): String? { return value diff --git a/design/src/main/res/values/strings.xml b/design/src/main/res/values/strings.xml index 4e43d3fd26..d6cc8d03d4 100644 --- a/design/src/main/res/values/strings.xml +++ b/design/src/main/res/values/strings.xml @@ -135,6 +135,10 @@ Bypass private network addresses DNS Hijacking Handle all dns packet + Disable ICMP Forwarding + Reply ICMP echo requests locally instead of forwarding them + ICMP Timeout (seconds) + Default Block Loopback Block loopback connections Allow Bypass diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt index 8550f8ab81..eb3a88036a 100644 --- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt @@ -222,6 +222,8 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De gateway = "$TUN_GATEWAY/$TUN_SUBNET_PREFIX" + if (store.allowIpv6) ",$TUN_GATEWAY6/$TUN_SUBNET_PREFIX6" else "", portal = TUN_PORTAL + if (store.allowIpv6) ",$TUN_PORTAL6" else "", dns = if (store.dnsHijacking) NET_ANY else (TUN_DNS + if (store.allowIpv6) ",$TUN_DNS6" else ""), + disableIcmpForwarding = store.disableIcmpForwarding, + icmpTimeout = store.icmpTimeout, ) } diff --git a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt index 84197d325f..ddf794143e 100644 --- a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt +++ b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt @@ -19,6 +19,8 @@ class TunModule(private val vpn: VpnService) : Module(vpn) { val gateway: String, val portal: String, val dns: String, + val disableIcmpForwarding: Boolean, + val icmpTimeout: Int, ) private val connectivity = service.getSystemService()!! @@ -61,6 +63,8 @@ class TunModule(private val vpn: VpnService) : Module(vpn) { gateway = device.gateway, portal = device.portal, dns = device.dns, + disableIcmpForwarding = device.disableIcmpForwarding, + icmpTimeout = device.icmpTimeout, markSocket = vpn::protect, querySocketUid = this::queryUid ) diff --git a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt index 17734d4ce6..42a3973ad4 100644 --- a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt +++ b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt @@ -61,6 +61,16 @@ class ServiceStore(context: Context) { defaultValue = "mips" ) + var disableIcmpForwarding by store.boolean( + key = "disable_icmp_forwarding", + defaultValue = true + ) + + var icmpTimeout by store.int( + key = "icmp_timeout", + defaultValue = 0 + ) + var dynamicNotification by store.boolean( key = "dynamic_notification", defaultValue = true