diff --git a/Api/SunnyNet.go b/Api/SunnyNet.go index 4aec46e..caabd85 100644 --- a/Api/SunnyNet.go +++ b/Api/SunnyNet.go @@ -4,17 +4,19 @@ import "C" import ( "bytes" "fmt" + "io/ioutil" + "net/url" + "runtime" + "sort" + "strconv" + "strings" + "time" + "github.com/qtgolang/SunnyNet/SunnyNet" "github.com/qtgolang/SunnyNet/src/Call" "github.com/qtgolang/SunnyNet/src/SunnyProxy" "github.com/qtgolang/SunnyNet/src/http" "github.com/qtgolang/SunnyNet/src/public" - "io/ioutil" - "net/url" - "sort" - "strconv" - "strings" - "time" ) // GetSunnyVersion 获取SunnyNet版本 @@ -171,6 +173,9 @@ func GetRequestClientIp(MessageId int) string { } k.Lock.Lock() defer k.Lock.Unlock() + if k.Global.Drive().Name() == "tun" && runtime.GOOS == "android" { + return k.Conn.LocalAddr().String() + } return k.Conn.RemoteAddr().String() } diff --git a/BuildLibrary/BuildAndroid.bat b/BuildLibrary/BuildAndroid.bat new file mode 100644 index 0000000..a9ed348 --- /dev/null +++ b/BuildLibrary/BuildAndroid.bat @@ -0,0 +1,32 @@ + + + +@echo off +set NDK=E:\init\windows-ndk-x86_64 +set CGO_ENABLED=1 + + +set tmpPath=G:\AndroidProject\SunnyNet\app\src\main\jniLibs\ + +set GOOS=android +set GOARCH=arm64 +set CC=%NDK%\bin\aarch64-linux-android21-clang +echo [Full]_Build_Android_arm64-v8a.so +go build -trimpath -buildmode=c-shared -ldflags "-s -w" -o "%tmpPath%arm64-v8a/libSunnyNet.so" +set GOOS=android +set GOARCH=arm +set CC=%NDK%\bin\armv7a-linux-androideabi21-clang +echo [Full]_Build_Android_armeabi-v7a.so +go build -trimpath -buildmode=c-shared -ldflags "-s -w" -o "%tmpPath%armeabi-v7a/libSunnyNet.so" + +set GOOS=android +set GOARCH=386 +set CC=%NDK%\bin\x86_64-linux-android21-clang +echo [Full]_Build_Android_x86.so +go build -trimpath -buildmode=c-shared -ldflags "-s -w" -o "%tmpPath%x86/libSunnyNet.so" + +set GOOS=android +set GOARCH=amd64 +set CC=%NDK%\bin\x86_64-linux-android21-clang +echo [Full]_Build_Android_x86_64.so +go build -trimpath -buildmode=c-shared -ldflags "-s -w" -o "%tmpPath%x86_64/libSunnyNet.so" \ No newline at end of file diff --git a/BuildLibrary/BuildAndroidV8a.bat b/BuildLibrary/BuildAndroidV8a.bat new file mode 100644 index 0000000..8215fed --- /dev/null +++ b/BuildLibrary/BuildAndroidV8a.bat @@ -0,0 +1,15 @@ + + + +@echo off +set NDK=E:\init\windows-ndk-x86_64 +set CGO_ENABLED=1 + + +set tmpPath=G:\AndroidProject\SunnyNet\app\src\main\jniLibs\ + +set GOOS=android +set GOARCH=arm64 +set CC=%NDK%\bin\aarch64-linux-android21-clang +echo [Full]_Build_Android_arm64-v8a.so +go build -trimpath -buildmode=c-shared -ldflags "-s -w" -o "%tmpPath%arm64-v8a/libSunnyNet.so" \ No newline at end of file diff --git a/JavaApi.go b/JavaApi.go index e2b742c..f9c9e9f 100644 --- a/JavaApi.go +++ b/JavaApi.go @@ -168,16 +168,18 @@ func Java_com_SunnyNet_api_SunnyNetSetCallback(envObj uintptr, clazz uintptr, Su } defer GlobalVM.DetachCurrentThread() + _PackageName := _env.NewString(Conn.PackageName()) _Method := _env.NewString(Conn.Method()) _url := _env.NewString(Conn.URL()) _er := _env.NewString(Conn.Error()) HTTPEventClass := aliasToClass("HTTPEvent") - EventConstructor := _env.GetMethodID(HTTPEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s)%s", sig.Long, sig.Long, sig.Long, sig.Long, sig.String, sig.String, sig.String, sig.Long, sig.Void)) - EventObj := _env.NewObjectA(HTTPEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(Conn.Theology()), Jvalue(Conn.MessageId()), Jvalue(Conn.Type()), Jvalue(_Method), Jvalue(_url), Jvalue(_er), Jvalue(Conn.PID())) + EventConstructor := _env.GetMethodID(HTTPEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s%s)%s", sig.Long, sig.Long, sig.Long, sig.Long, sig.String, sig.String, sig.String, sig.String, sig.Long, sig.Void)) + EventObj := _env.NewObjectA(HTTPEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(Conn.Theology()), Jvalue(Conn.MessageId()), Jvalue(Conn.Type()), Jvalue(_Method), Jvalue(_url), Jvalue(_er), Jvalue(_PackageName), Jvalue(Conn.PID())) _env.CallVoidMethodA(obj, onHTTPCallbackMethodId, Jvalue(EventObj)) _env.DeleteLocalRef(EventObj) _env.DeleteLocalRef(_Method) + _env.DeleteLocalRef(_PackageName) _env.DeleteLocalRef(_url) _env.DeleteLocalRef(_er) return @@ -193,15 +195,17 @@ func Java_com_SunnyNet_api_SunnyNetSetCallback(envObj uintptr, clazz uintptr, Su defer GlobalVM.DetachCurrentThread() _LocalAddr := _env.NewString(Conn.LocalAddress()) _RemoteAddr := _env.NewString(Conn.RemoteAddress()) + _PackageName := _env.NewString(Conn.PackageName()) _data := _env.NewByteArray(Conn.Body()) TCPEventClass := aliasToClass("TCPEvent") - EventConstructor := _env.GetMethodID(TCPEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s)%s", sig.Long, sig.String, sig.String, sig.Long, sig.Long, sig.Long, sig.Long, sig.ByteArray, sig.Void)) - EventObj := _env.NewObjectA(TCPEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(_LocalAddr), Jvalue(_RemoteAddr), Jvalue(Conn.Theology()), Jvalue(Conn.MessageId()), Jvalue(Conn.Type()), Jvalue(Conn.PID()), Jvalue(_data)) + EventConstructor := _env.GetMethodID(TCPEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s%s)%s", sig.Long, sig.String, sig.String, sig.String, sig.Long, sig.Long, sig.Long, sig.Long, sig.ByteArray, sig.Void)) + EventObj := _env.NewObjectA(TCPEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(_LocalAddr), Jvalue(_RemoteAddr), Jvalue(_PackageName), Jvalue(Conn.Theology()), Jvalue(Conn.MessageId()), Jvalue(Conn.Type()), Jvalue(Conn.PID()), Jvalue(_data)) _env.CallVoidMethodA(obj, onTCPMethodId, Jvalue(EventObj)) _env.DeleteLocalRef(EventObj) _env.DeleteLocalRef(_LocalAddr) _env.DeleteLocalRef(_RemoteAddr) + _env.DeleteLocalRef(_PackageName) _env.DeleteLocalRef(_data) } @@ -214,15 +218,17 @@ func Java_com_SunnyNet_api_SunnyNetSetCallback(envObj uintptr, clazz uintptr, Su } defer GlobalVM.DetachCurrentThread() + _PackageName := _env.NewString(Conn.PackageName()) _Method := _env.NewString(Conn.Method()) _url := _env.NewString(Conn.URL()) WebSocketEventClass := aliasToClass("WebSocketEvent") - EventConstructor := _env.GetMethodID(WebSocketEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s)%s", sig.Long, sig.Long, sig.Long, sig.Long, sig.String, sig.String, sig.Long, sig.Long, sig.Void)) - EventObj := _env.NewObjectA(WebSocketEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(Conn.Theology()), Jvalue(Conn.MessageId()), Jvalue(Conn.Type()), Jvalue(_Method), Jvalue(_url), Jvalue(Conn.PID()), Jvalue(Conn.MessageType())) + EventConstructor := _env.GetMethodID(WebSocketEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s%s)%s", sig.Long, sig.Long, sig.Long, sig.Long, sig.String, sig.String, sig.String, sig.Long, sig.Long, sig.Void)) + EventObj := _env.NewObjectA(WebSocketEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(Conn.Theology()), Jvalue(Conn.MessageId()), Jvalue(Conn.Type()), Jvalue(_Method), Jvalue(_url), Jvalue(_PackageName), Jvalue(Conn.PID()), Jvalue(Conn.MessageType())) _env.CallVoidMethodA(obj, onWebSocketMethodId, Jvalue(EventObj)) _env.DeleteLocalRef(_Method) _env.DeleteLocalRef(_url) + _env.DeleteLocalRef(_PackageName) _env.DeleteLocalRef(EventObj) return } @@ -237,17 +243,17 @@ func Java_com_SunnyNet_api_SunnyNetSetCallback(envObj uintptr, clazz uintptr, Su defer GlobalVM.DetachCurrentThread() MessageId := Conn.MessageId() SunnyNetUDP.ResetMessage(MessageId, Conn.Body()) - + _PackageName := _env.NewString(Conn.PackageName()) _LocalAddr := _env.NewString(Conn.LocalAddress()) _RemoteAddr := _env.NewString(Conn.RemoteAddress()) UDPEventClass := aliasToClass("UDPEvent") - EventConstructor := _env.GetMethodID(UDPEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s)%s", sig.Long, sig.String, sig.String, sig.Long, sig.Long, sig.Long, sig.Long, sig.Void)) - EventObj := _env.NewObjectA(UDPEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(_LocalAddr), Jvalue(_RemoteAddr), Jvalue(Conn.Theology()), Jvalue(MessageId), Jvalue(Conn.Type()), Jvalue(Conn.PID())) + EventConstructor := _env.GetMethodID(UDPEventClass, "", fmt.Sprintf("(%s%s%s%s%s%s%s%s)%s", sig.Long, sig.String, sig.String, sig.String, sig.Long, sig.Long, sig.Long, sig.Long, sig.Void)) + EventObj := _env.NewObjectA(UDPEventClass, EventConstructor, Jvalue(SunnyContext), Jvalue(_LocalAddr), Jvalue(_RemoteAddr), Jvalue(_PackageName), Jvalue(Conn.Theology()), Jvalue(MessageId), Jvalue(Conn.Type()), Jvalue(Conn.PID())) _env.CallVoidMethodA(obj, onUDPMethodId, Jvalue(EventObj)) _env.DeleteLocalRef(EventObj) _env.DeleteLocalRef(_LocalAddr) _env.DeleteLocalRef(_RemoteAddr) - + _env.DeleteLocalRef(_PackageName) Conn.SetBody(SunnyNetUDP.GetMessage(MessageId)) SunnyNetUDP.DelMessage(MessageId) return @@ -2047,9 +2053,18 @@ func Java_com_SunnyNet_api_HTTPSetOutRouterIP(envObj uintptr, clazz uintptr, Mes return Api.HTTPSetOutRouterIP(int(MessageId), env.GetString(value)) } +/* +Java_com_SunnyNet_api_OnTunSetFd 将 VPN TUN fd 交给 Go 侧读取;ConnectivityManager 用于 Android 10+ 查询连接归属包名。 +对应 Java: public static native boolean OnTunSetFd(long fd, Object ConnectivityManager); +*/ //export Java_com_SunnyNet_api_OnTunSetFd -func Java_com_SunnyNet_api_OnTunSetFd(JavaVM uintptr, reserved uintptr, fd int64) { - tun.SetFd(int(fd)) +func Java_com_SunnyNet_api_OnTunSetFd(envObj uintptr, clazz uintptr, fd int64, connectivityManager uintptr) bool { + env := Env(envObj) + InitTunConnectivityManager(env, connectivityManager) + if fd <= 0 { + return false + } + return tun.SetFd(int(fd)) } type _GlobalRef struct { diff --git a/JavaApi/android_tun.go b/JavaApi/android_tun.go new file mode 100644 index 0000000..188b90c --- /dev/null +++ b/JavaApi/android_tun.go @@ -0,0 +1,322 @@ +//go:build android +// +build android + +package JavaJni + +import ( + "runtime" + "strings" + "sync" +) + +const ( + AndroidSDKQ = 29 + + IPProtoTCP = 6 + IPProtoUDP = 17 + + invalidUID = -1 +) + +// TunPackageResult TUN 五元组查询到的包名及是否在 PackageManager 注册。 +type TunPackageResult struct { + Name string + Registered bool +} + +var ( + // AndroidSDKInt 在 InitTunConnectivityManager 时写入当前系统 SDK。 + AndroidSDKInt int + // TunPackageLookupEnabled 为 true 表示已保存 ConnectivityManager 且 SDK>=29。 + TunPackageLookupEnabled bool + + tunCM Jobject + tunCMMu sync.RWMutex +) + +// SdkInt 读取 android.os.Build.VERSION.SDK_INT。 +func SdkInt(env Env) int { + verClass := env.FindClass("android/os/Build$VERSION") + if verClass == 0 { + return 0 + } + defer env.DeleteLocalRef(verClass) + field := env.GetStaticFieldID(verClass, "SDK_INT", "I") + if field == 0 { + return 0 + } + return env.GetStaticIntField(verClass, field) +} + +// InitTunConnectivityManager 在 OnTunSetFd 中调用:仅 Android 10+ 保存 CM 全局引用并启用包名查询。 +func InitTunConnectivityManager(env Env, cm Jobject) bool { + sdk := SdkInt(env) + AndroidSDKInt = sdk + if sdk < AndroidSDKQ || cm == 0 { + clearTunConnectivityManager(env) + return false + } + clearTunConnectivityManager(env) + tunCMMu.Lock() + tunCM = env.NewGlobalRef(cm) + tunCMMu.Unlock() + TunPackageLookupEnabled = true + return true +} + +func clearTunConnectivityManager(env Env) { + tunCMMu.Lock() + old := tunCM + tunCM = 0 + tunCMMu.Unlock() + if old != 0 { + env.DeleteGlobalRef(old) + } + TunPackageLookupEnabled = false +} + +// CurrentApplication 返回 ActivityThread.currentApplication()。 +func CurrentApplication(env Env) Jobject { + atClass := env.FindClass("android/app/ActivityThread") + if atClass == 0 { + return 0 + } + defer env.DeleteLocalRef(atClass) + + method := env.GetStaticMethodID(atClass, "currentApplication", "()Landroid/app/Application;") + if method == 0 { + return 0 + } + app := env.CallStaticObjectMethodA(atClass, method) + if app == 0 || env.ExceptionCheck() { + env.ExceptionClear() + return 0 + } + return app +} + +// NewInetSocketAddress 构造 java.net.InetSocketAddress(InetAddress, port)。 +func NewInetSocketAddress(env Env, host string, port int) Jobject { + if host == "" || port <= 0 || port > 65535 { + return 0 + } + inetClass := env.FindClass("java/net/InetAddress") + if inetClass == 0 { + return 0 + } + defer env.DeleteLocalRef(inetClass) + + getByName := env.GetStaticMethodID(inetClass, "getByName", "(Ljava/lang/String;)Ljava/net/InetAddress;") + if getByName == 0 { + return 0 + } + hostJ := env.NewString(host) + if hostJ == 0 { + return 0 + } + defer env.DeleteLocalRef(hostJ) + + inetAddr := env.CallStaticObjectMethodA(inetClass, getByName, Jvalue(hostJ)) + if inetAddr == 0 || env.ExceptionCheck() { + env.ExceptionClear() + return 0 + } + defer env.DeleteLocalRef(inetAddr) + + isaClass := env.FindClass("java/net/InetSocketAddress") + if isaClass == 0 { + return 0 + } + defer env.DeleteLocalRef(isaClass) + + init := env.GetMethodID(isaClass, "", "(Ljava/net/InetAddress;I)V") + if init == 0 { + return 0 + } + sock := env.NewObjectA(isaClass, init, Jvalue(inetAddr), Jvalue(port)) + if sock == 0 || env.ExceptionCheck() { + env.ExceptionClear() + return 0 + } + return sock +} + +// GetConnectionOwnerUid 调用 ConnectivityManager.getConnectionOwnerUid。 +func GetConnectionOwnerUid(env Env, cm Jobject, protocol int, local, remote Jobject) int { + cmClass := env.FindClass("android/net/ConnectivityManager") + if cmClass == 0 { + return invalidUID + } + defer env.DeleteLocalRef(cmClass) + + method := env.GetMethodID(cmClass, "getConnectionOwnerUid", + "(ILjava/net/InetSocketAddress;Ljava/net/InetSocketAddress;)I") + if method == 0 { + return invalidUID + } + + uid := env.CallIntMethodA(cm, method, Jvalue(protocol), Jvalue(local), Jvalue(remote)) + if env.ExceptionCheck() { + env.ExceptionClear() + return invalidUID + } + return uid +} + +// normalizePackageName 去掉 getNameForUid 返回的 ":appId" 后缀,得到纯包名。 +func normalizePackageName(name string) string { + name = strings.TrimSpace(name) + if name == "" { + return "" + } + if i := strings.LastIndex(name, ":"); i > 0 { + suffix := name[i+1:] + if suffix != "" && isDecimalString(suffix) { + return name[:i] + } + } + return name +} + +func isDecimalString(s string) bool { + for _, c := range s { + if c < '0' || c > '9' { + return false + } + } + return len(s) > 0 +} + +// isPackageRegistered 判断包名是否已安装(PackageManager.getPackageInfo 能查到)。 +func isPackageRegistered(env Env, pm Jobject, pmClass Jclass, pkg string) bool { + if pkg == "" { + return false + } + getPI := env.GetMethodID(pmClass, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;") + if getPI == 0 { + return false + } + pkgJ := env.NewString(pkg) + if pkgJ == 0 { + return false + } + defer env.DeleteLocalRef(pkgJ) + pi := env.CallObjectMethodA(pm, getPI, Jvalue(pkgJ), Jvalue(0)) + if pi == 0 || env.ExceptionCheck() { + env.ExceptionClear() + return false + } + env.DeleteLocalRef(pi) + return true +} + +// packageNameFromUid 优先 getPackagesForUid,否则 getNameForUid 并规范化;第二返回值表示是否在 PM 注册。 +func packageNameFromUid(env Env, pm Jobject, pmClass Jclass, uid int) (string, bool) { + getPkgs := env.GetMethodID(pmClass, "getPackagesForUid", "(I)[Ljava/lang/String;") + if getPkgs != 0 { + arr := env.CallObjectMethodA(pm, getPkgs, Jvalue(uid)) + if arr != 0 && !env.ExceptionCheck() { + defer env.DeleteLocalRef(arr) + n := env.GetArrayLength(Jarray(arr)) + for i := 0; i < n; i++ { + item := env.GetObjectArrayElement(JobjectArray(arr), i) + if item == 0 { + continue + } + pkg := strings.TrimSpace(env.GetString(Jstring(item))) + env.DeleteLocalRef(item) + if pkg != "" { + return pkg, true + } + } + } else { + env.ExceptionClear() + } + } + + getName := env.GetMethodID(pmClass, "getNameForUid", "(I)Ljava/lang/String;") + if getName == 0 { + return "", false + } + nameObj := env.CallObjectMethodA(pm, getName, Jvalue(uid)) + if nameObj == 0 || env.ExceptionCheck() { + env.ExceptionClear() + return "", false + } + defer env.DeleteLocalRef(nameObj) + pkg := normalizePackageName(env.GetString(Jstring(nameObj))) + if pkg == "" { + return "", false + } + return pkg, isPackageRegistered(env, pm, pmClass, pkg) +} + +// PackageNameForUid 解析 UID 对应纯包名及是否已注册。 +func PackageNameForUid(env Env, uid int) TunPackageResult { + ctx := CurrentApplication(env) + if ctx == 0 { + return TunPackageResult{} + } + defer env.DeleteLocalRef(ctx) + + getPM := env.GetMethodID(env.GetObjectClass(ctx), "getPackageManager", "()Landroid/content/pm/PackageManager;") + if getPM == 0 { + return TunPackageResult{} + } + pm := env.CallObjectMethodA(ctx, getPM) + if pm == 0 || env.ExceptionCheck() { + env.ExceptionClear() + return TunPackageResult{} + } + defer env.DeleteLocalRef(pm) + + pmClass := env.GetObjectClass(pm) + defer env.DeleteLocalRef(pmClass) + + name, reg := packageNameFromUid(env, pm, pmClass, uid) + return TunPackageResult{Name: name, Registered: reg} +} + +// TunGetRequestPackageName 根据五元组查询来源包名及是否在 PackageManager 注册。 +func TunGetRequestPackageName(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) TunPackageResult { + if !TunPackageLookupEnabled || GlobalVM == 0 { + return TunPackageResult{} + } + if protocol != IPProtoTCP && protocol != IPProtoUDP { + return TunPackageResult{} + } + + tunCMMu.RLock() + cm := tunCM + tunCMMu.RUnlock() + if cm == 0 { + return TunPackageResult{} + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + env, ret := GlobalVM.AttachCurrentThread() + if ret != JNI_OK { + return TunPackageResult{} + } + defer GlobalVM.DetachCurrentThread() + + local := NewInetSocketAddress(env, srcIp, srcPort) + if local == 0 { + return TunPackageResult{} + } + defer env.DeleteLocalRef(local) + + remote := NewInetSocketAddress(env, dstIp, dstPort) + if remote == 0 { + return TunPackageResult{} + } + defer env.DeleteLocalRef(remote) + + uid := GetConnectionOwnerUid(env, cm, protocol, local, remote) + if uid < 0 { + return TunPackageResult{} + } + return PackageNameForUid(env, uid) +} diff --git a/JavaApi/noAndroid_tun.go b/JavaApi/noAndroid_tun.go new file mode 100644 index 0000000..b08557e --- /dev/null +++ b/JavaApi/noAndroid_tun.go @@ -0,0 +1,43 @@ +//go:build !android +// +build !android + +package JavaJni + +const ( + AndroidSDKQ = 29 + + IPProtoTCP = 6 + IPProtoUDP = 17 +) + +var ( + AndroidSDKInt int + TunPackageLookupEnabled bool +) + +func SdkInt(env Env) int { return 0 } + +func InitTunConnectivityManager(env Env, cm Jobject) bool { + AndroidSDKInt = 0 + TunPackageLookupEnabled = false + return false +} + +func CurrentApplication(env Env) Jobject { return 0 } + +func NewInetSocketAddress(env Env, host string, port int) Jobject { return 0 } + +func GetConnectionOwnerUid(env Env, cm Jobject, protocol int, local, remote Jobject) int { + return -1 +} + +func PackageNameForUid(env Env, uid int) TunPackageResult { return TunPackageResult{} } + +type TunPackageResult struct { + Name string + Registered bool +} + +func TunGetRequestPackageName(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) TunPackageResult { + return TunPackageResult{} +} diff --git a/SunnyNet/Callback.go b/SunnyNet/Callback.go index 41c10f7..0836e27 100644 --- a/SunnyNet/Callback.go +++ b/SunnyNet/Callback.go @@ -88,7 +88,8 @@ func (s *proxyRequest) CallbackTCPRequest(callType int, _msg *public.TcpMsg, Rem localAddr: LocalAddr, remoteAddr: hostname, pid: pid, - sunnyContext: s.Global.SunnyContext, + packageName: s.packageName, + sunnyContext: s.Global.SunnyContext, _Display: true, _OutRouterIPFunc: s.SetOutRouterIP, _note: s._note, @@ -196,7 +197,8 @@ func (s *proxyRequest) CallbackBeforeRequest() { _localAddress: s.Conn.LocalAddr().String(), _OutRouterIPFunc: s.SetOutRouterIP, updateRawTarget: s.UpdateRawTarget, - _note: s._note, + _note: s._note, + packageName: s.packageName, } s.Global.scriptHTTPCall(m) s._note = m._note @@ -266,7 +268,8 @@ func (s *proxyRequest) CallbackBeforeResponse() { _localAddress: s.Conn.LocalAddr().String(), _OutRouterIPFunc: s.SetOutRouterIP, updateRawTarget: s.UpdateRawTarget, - _note: s._note, + _note: s._note, + packageName: s.packageName, } s.Global.scriptHTTPCall(m) s._note = m._note @@ -372,7 +375,8 @@ func (s *proxyRequest) CallbackError(err string) { _localAddress: s.Conn.LocalAddr().String(), _OutRouterIPFunc: s.SetOutRouterIP, updateRawTarget: s.UpdateRawTarget, - _note: s._note, + _note: s._note, + packageName: s.packageName, } s.Global.scriptHTTPCall(m) s._note = m._note @@ -414,7 +418,8 @@ func (s *proxyRequest) CallbackWssRequest(State int, Method, Url string, msg *pu _ClientIP: s.Conn.RemoteAddr().String(), _localAddress: s.Conn.LocalAddr().String(), _Display: true, - _note: s._note, + _note: s._note, + packageName: s.packageName, } s.Global.scriptWebsocketCall(m) messageIdLock.Lock() diff --git a/SunnyNet/ConnHTTP.go b/SunnyNet/ConnHTTP.go index 1537ec9..b6d2bce 100644 --- a/SunnyNet/ConnHTTP.go +++ b/SunnyNet/ConnHTTP.go @@ -2,16 +2,17 @@ package SunnyNet import ( "bytes" + "io" + "net/url" + "strconv" + "strings" + "github.com/qtgolang/SunnyNet/src/CrossCompiled" "github.com/qtgolang/SunnyNet/src/Interface" "github.com/qtgolang/SunnyNet/src/SunnyProxy" "github.com/qtgolang/SunnyNet/src/crypto/tls" "github.com/qtgolang/SunnyNet/src/http" "github.com/qtgolang/SunnyNet/src/public" - "io" - "net/url" - "strconv" - "strings" ) type ConnHTTP Interface.ConnHTTPCall @@ -37,8 +38,15 @@ type httpConn struct { _note string _OutRouterIPFunc func(string) bool updateRawTarget func(int uint32) + packageName string //安卓模式下 pid 始终为0,但可以获取对应包名 } +func (h *httpConn) PackageName() string { + return h.packageName +} +func (h *httpConn) SetPackageName(packageName string) { + h.packageName = packageName +} func (h *httpConn) SetNote(s string) { h._note = s } diff --git a/SunnyNet/ConnTCP.go b/SunnyNet/ConnTCP.go index 5cfa03a..2e5ef30 100644 --- a/SunnyNet/ConnTCP.go +++ b/SunnyNet/ConnTCP.go @@ -21,8 +21,15 @@ type tcpConn struct { _Display bool _OutRouterIPFunc func(string) bool _note string + packageName string //安卓模式下 pid 始终为0,但可以获取对应包名 } +func (t *tcpConn) PackageName() string { + return t.packageName +} +func (t *tcpConn) SetPackageName(packageName string) { + t.packageName = packageName +} func (t *tcpConn) SetNote(s string) { t._note = s } diff --git a/SunnyNet/ConnUDP.go b/SunnyNet/ConnUDP.go index 1a29807..71395a4 100644 --- a/SunnyNet/ConnUDP.go +++ b/SunnyNet/ConnUDP.go @@ -19,6 +19,11 @@ type udpConn struct { data []byte _Display bool _note string + packageName string //安卓模式下 pid 始终为0,但可以获取对应包名 +} + +func (u *udpConn) PackageName() string { + return u.packageName } func (u *udpConn) SetNote(s string) { diff --git a/SunnyNet/ConnWebsocket.go b/SunnyNet/ConnWebsocket.go index ddf283f..351b078 100644 --- a/SunnyNet/ConnWebsocket.go +++ b/SunnyNet/ConnWebsocket.go @@ -22,12 +22,19 @@ type wsConn struct { _Display bool _localAddress string _note string + packageName string //安卓模式下 pid 始终为0,但可以获取对应包名 } func (w *wsConn) SetNote(s string) { w._note = s } +func (w *wsConn) PackageName() string { + return w.packageName +} +func (w *wsConn) SetPackageName(packageName string) { + w.packageName = packageName +} func (w *wsConn) GetNote() string { return w._note } diff --git a/SunnyNet/SunnyNet.go b/SunnyNet/SunnyNet.go index 3adde97..e5d1c36 100644 --- a/SunnyNet/SunnyNet.go +++ b/SunnyNet/SunnyNet.go @@ -193,6 +193,7 @@ type proxyRequest struct { Target *TargetInfo //目标连接信息 ProxyHost string //请求之上的代理 Pid string //s5连接过来的pid + packageName string //安卓 TUN 来源包名 Global *Sunny //继承全局中间件信息 Request *http.Request //要发送的请求体 Response response //HTTP响应体 @@ -2271,6 +2272,11 @@ func (s *Sunny) OpenDrive(DevMode int) bool { return divert.Run() } +// Drive 加载的驱动、全局共享 +func (s *Sunny) Drive() ProcessDrv.Dev { + return divert +} + // ProcessALLName 是否允许所有进程通过 所有 SunnyNet 通用, // StopNetwork 是否对所有进程执行一次断网操作 // 请注意GoLang调试时候,StopNetwork请不要设置true @@ -2442,6 +2448,7 @@ func (s *proxyRequest) clone() *proxyRequest { defaultScheme: s.defaultScheme, SendTimeout: s.SendTimeout, rawTarget: s.rawTarget, + packageName: s.packageName, } if s.outRouterIP != nil { req.outRouterIP = &net.TCPAddr{IP: s.outRouterIP.IP} @@ -2557,6 +2564,7 @@ func (s *Sunny) handleClientConn(conn net.Conn) { req.setSocket5User("驱动程序") //如果是 通过 NFapi 驱动进来的数据 对连接信息进行赋值 req.Pid = info.GetPid() + req.packageName = info.GetPackageName() req.Target.Parse(info.GetRemoteAddress(), info.GetRemotePort(), info.IsV6()) if s.connHijack != nil { if s.connHijack(&_hijack{req}) { diff --git a/SunnyNet/udp.go b/SunnyNet/udp.go index bd801b4..b5b4610 100644 --- a/SunnyNet/udp.go +++ b/SunnyNet/udp.go @@ -3,10 +3,11 @@ package SunnyNet import ( "bytes" "fmt" + "sync" + "github.com/qtgolang/SunnyNet/src/Call" "github.com/qtgolang/SunnyNet/src/ProcessDrv/SunnyNetUDP" "github.com/qtgolang/SunnyNet/src/public" - "sync" "net" "sync/atomic" @@ -170,7 +171,7 @@ func (s *Sunny) listenUdpGo() { go s.goUdp(_info, Item.Tid, addr.String(), _info.RemoteAddress, conn, keyHash) } if Item.remote != nil { - bs = s.udpSendReceive(public.SunnyNetUDPTypeSend, Item.Tid, 0, addr.String(), _info.RemoteAddress, _info.Data) + bs = s.udpSendReceive(public.SunnyNetUDPTypeSend, Item.Tid, 0, addr.String(), _info.RemoteAddress, _info.Data, "") if len(bs) > 0 { _, _ = Item.remote.Write(bs) } @@ -191,7 +192,7 @@ func (s *Sunny) goUdp(info *udpInfo, tid int64, Local, Remote string, conn *net. break } // 调用 udpNFSendReceive 方法发送并接收数据,并将返回的数据添加来源信息 - bs := s.udpSendReceive(public.SunnyNetUDPTypeReceive, tid, 0, Local, Remote, buff[:nt]) + bs := s.udpSendReceive(public.SunnyNetUDPTypeReceive, tid, 0, Local, Remote, buff[:nt], "") if len(bs) < 1 { continue } @@ -202,18 +203,24 @@ func (s *Sunny) goUdp(info *udpInfo, tid int64, Local, Remote string, conn *net. _, _ = s.udpSocket.WriteToUDP(data, info.LocalAddress) } - s.udpSendReceive(public.SunnyNetUDPTypeClosed, tid, 0, Local, Remote, nil) + s.udpSendReceive(public.SunnyNetUDPTypeClosed, tid, 0, Local, Remote, nil, "") SunnyNetUDP.DelUDPItem(tid) mu.Lock() delete(list, keyHash) mu.Unlock() } -func (s *Sunny) udpSendReceive(Type int, Theoni int64, pid uint32, LocalAddress, RemoteAddress string, data []byte) []byte { +func (s *Sunny) udpSendReceive(Type int, Theoni int64, pid uint32, LocalAddress, RemoteAddress string, data []byte, packageName string) []byte { if s.disableUDP { return nil } - n := &udpConn{theology: Theoni, messageId: NewMessageId(), _type: Type, sunnyContext: s.SunnyContext, pid: int(pid), localAddress: LocalAddress, remoteAddress: RemoteAddress, data: data, _Display: true} + pkg := packageName + if item := SunnyNetUDP.GetUDPItem(Theoni); item != nil { + if p, ok := item.(interface{ GetPackageName() string }); ok && pkg == "" { + pkg = p.GetPackageName() + } + } + n := &udpConn{theology: Theoni, messageId: NewMessageId(), _type: Type, sunnyContext: s.SunnyContext, pid: int(pid), packageName: pkg, localAddress: LocalAddress, remoteAddress: RemoteAddress, data: data, _Display: true} s.scriptUDPCall(n) if !n._Display { return n.Body() diff --git a/src/Interface/interface.go b/src/Interface/interface.go index 53eda36..3d3c3a5 100644 --- a/src/Interface/interface.go +++ b/src/Interface/interface.go @@ -1,8 +1,9 @@ package Interface import ( - "github.com/qtgolang/SunnyNet/src/http" "io" + + "github.com/qtgolang/SunnyNet/src/http" ) /* =============================== 脚本 中 使用的接口 ================================================ */ @@ -478,6 +479,15 @@ type general interface { */ PID() int + /* + PackageName + + 返回当前会话由发起的安卓进程的包名 + + (仅安卓下有效) + */ + PackageName() string + /* Theology diff --git a/src/ProcessDrv/ProcessCheck/main.go b/src/ProcessDrv/ProcessCheck/main.go index 9f499da..2b89698 100644 --- a/src/ProcessDrv/ProcessCheck/main.go +++ b/src/ProcessDrv/ProcessCheck/main.go @@ -11,6 +11,7 @@ type DrvInfo interface { GetRemoteAddress() string GetRemotePort() uint16 GetPid() string + GetPackageName() string IsV6() bool ID() uint64 Close() error diff --git a/src/ProcessDrv/Proxifier/main.go b/src/ProcessDrv/Proxifier/main.go index 2182217..3a1a316 100644 --- a/src/ProcessDrv/Proxifier/main.go +++ b/src/ProcessDrv/Proxifier/main.go @@ -15,14 +15,15 @@ import ( "context" "encoding/binary" "fmt" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/ProcessCheck" "net" "os" "path/filepath" "sync" "time" "unsafe" + + "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/ProcessCheck" ) var HandleClientConn func(net.Conn) @@ -194,6 +195,9 @@ func (p *proxyProcessInfo) GetRemotePort() uint16 { func (p *proxyProcessInfo) GetPid() string { return p.Pid } +func (p *proxyProcessInfo) GetPackageName() string { + return "" +} func (p *proxyProcessInfo) IsV6() bool { return p.V6 } diff --git a/src/ProcessDrv/nfapi/EventHandler.go b/src/ProcessDrv/nfapi/EventHandler.go index 1978110..8d65b33 100644 --- a/src/ProcessDrv/nfapi/EventHandler.go +++ b/src/ProcessDrv/nfapi/EventHandler.go @@ -6,18 +6,20 @@ package NFapi import "C" import ( "fmt" - . "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/ProcessCheck" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/SunnyNetUDP" - net2 "github.com/qtgolang/SunnyNet/src/iphlpapi/net" - "github.com/qtgolang/SunnyNet/src/public" - "github.com/shirou/gopsutil/process" "net" "regexp" "strconv" "strings" "sync/atomic" "syscall" + + . "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/ProcessCheck" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/SunnyNetUDP" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/Tun" + net2 "github.com/qtgolang/SunnyNet/src/iphlpapi/net" + "github.com/qtgolang/SunnyNet/src/public" + "github.com/shirou/gopsutil/process" ) func getTcpInfoPID(tcpInfo string) string { @@ -231,7 +233,7 @@ func udpClosed(id uint64, pConnInfo *NF_UDP_CONN_INFO) { return } if UdpSendReceiveFunc != nil { - UdpSendReceiveFunc(public.SunnyNetUDPTypeClosed, obj.Theoni, pConnInfo.ProcessId.Get(), pConnInfo.LocalAddress.String(), obj.Send.RemoteAddress.String(), nil) + UdpSendReceiveFunc(public.SunnyNetUDPTypeClosed, obj.Theoni, pConnInfo.ProcessId.Get(), pConnInfo.LocalAddress.String(), obj.Send.RemoteAddress.String(), nil, "") } mu.Lock() delete(list, id) @@ -262,7 +264,7 @@ func udpReceive(id uint64, RemoteAddress *SockaddrInx, buf []byte, options *NF_U obj.Receive = &NfOPT{Id: id, RemoteAddress: RemoteAddress.Clone(), options: options.Clone()} } mu.Unlock() - bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeReceive, obj.Theoni, pid, LocalAddress.String(), RemoteAddress.String(), buf) + bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeReceive, obj.Theoni, pid, LocalAddress.String(), RemoteAddress.String(), buf, "") if len(bs) > 0 { _, _ = Api.NfUdpPostReceive(id, RemoteAddress, bs, options) } @@ -294,7 +296,7 @@ func udpSend(id uint64, RemoteAddress *SockaddrInx, buf []byte, options *NF_UDP_ } mu.Unlock() //这里因为是接收 所以 RemoteAddress 是本地地址 而 LocalAddress 是远程地址 - bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeReceive, obj.Theoni, pid, RemoteAddress.String(), LocalAddress.String(), buf) + bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeReceive, obj.Theoni, pid, RemoteAddress.String(), LocalAddress.String(), buf, "") if len(bs) > 0 { _, _ = Api.NfUdpPostSend(id, RemoteAddress, bs, options) } @@ -311,13 +313,13 @@ func udpSend(id uint64, RemoteAddress *SockaddrInx, buf []byte, options *NF_UDP_ mu.Lock() list[id] = obj mu.Unlock() - bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeSend, obj.Theoni, pid, LocalAddress.String(), RemoteAddress.String(), buf) + bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeSend, obj.Theoni, pid, LocalAddress.String(), RemoteAddress.String(), buf, "") if len(bs) > 0 { _, _ = Api.NfUdpPostSend(id, RemoteAddress, bs, options) } } else { // 如果连接已建立,则发送数据 - bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeSend, obj.Theoni, pid, LocalAddress.String(), RemoteAddress.String(), buf) + bs := UdpSendReceiveFunc(public.SunnyNetUDPTypeSend, obj.Theoni, pid, LocalAddress.String(), RemoteAddress.String(), buf, "") if len(bs) > 0 { _, _ = Api.NfUdpPostSend(id, RemoteAddress, bs, options) } @@ -332,4 +334,4 @@ func udpCanSend(id uint64) { return } -var UdpSendReceiveFunc func(Type int, Theoni int64, pid uint32, LocalAddress, RemoteAddress string, data []byte) []byte +var UdpSendReceiveFunc Tun.UdpFunc diff --git a/src/ProcessDrv/nfapi/noWinbytes.go b/src/ProcessDrv/nfapi/noWinbytes.go index b13d63f..6065393 100644 --- a/src/ProcessDrv/nfapi/noWinbytes.go +++ b/src/ProcessDrv/nfapi/noWinbytes.go @@ -6,11 +6,12 @@ package NFapi import ( "encoding/binary" "fmt" - . "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/nfapi/basetype" "net" "reflect" "unsafe" + + . "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/nfapi/basetype" ) const ( @@ -99,6 +100,9 @@ func (p *ProcessInfo) GetRemotePort() uint16 { func (p *ProcessInfo) GetPid() string { return p.Pid } +func (p *ProcessInfo) GetPackageName() string { + return "" +} func (p *ProcessInfo) IsV6() bool { return p.V6 } diff --git a/src/ProcessDrv/nfapi/winbytes.go b/src/ProcessDrv/nfapi/winbytes.go index 58e23f4..d51b9ce 100644 --- a/src/ProcessDrv/nfapi/winbytes.go +++ b/src/ProcessDrv/nfapi/winbytes.go @@ -6,11 +6,12 @@ package NFapi import ( "encoding/binary" "fmt" - . "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/nfapi/basetype" "net" "reflect" "unsafe" + + . "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/nfapi/basetype" ) const ( @@ -91,6 +92,10 @@ func (p *ProcessInfo) GetRemotePort() uint16 { func (p *ProcessInfo) GetPid() string { return p.Pid } +func (p *ProcessInfo) GetPackageName() string { + return "" +} + func (p *ProcessInfo) IsV6() bool { return p.V6 } diff --git a/src/ProcessDrv/tun/Tun/all.go b/src/ProcessDrv/tun/Tun/all.go index 0fb8b81..2c1149e 100644 --- a/src/ProcessDrv/tun/Tun/all.go +++ b/src/ProcessDrv/tun/Tun/all.go @@ -9,7 +9,9 @@ type Interface interface { SetOutRouterIP(RouterIP string) bool Port() int } -type UdpFunc func(Type int, Theoni int64, pid uint32, LocalAddress string, RemoteAddress string, data []byte) []byte + +// UdpFunc PackageName 为安卓模式下的包名,其他系统下无值 +type UdpFunc func(Type int, Theoni int64, pid uint32, LocalAddress string, RemoteAddress string, data []byte, PackageName string) []byte type TcpFunc func(conn n2.Conn) var _myPid = int32(os.Getpid()) diff --git a/src/ProcessDrv/tun/Tun/conn.go b/src/ProcessDrv/tun/Tun/conn.go index 44dcffe..a63e988 100644 --- a/src/ProcessDrv/tun/Tun/conn.go +++ b/src/ProcessDrv/tun/Tun/conn.go @@ -14,6 +14,7 @@ import ( "time" "github.com/qtgolang/SunnyNet/src/ProcessDrv/ProcessCheck" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/getPackageName" "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/tunPublic" ) @@ -24,6 +25,9 @@ type DevConn struct { serverIP net.IP serverPort uint16 + packageName string + packageRegistered bool + // TCP 序列号跟踪 clientNext uint32 // 客户端下一个期望的 seq serverISN uint32 // 我们伪造的 server 初始序列号 @@ -62,6 +66,10 @@ func (d *DevConn) GetPid() string { return strconv.Itoa(int(d.pid)) } +func (d *DevConn) GetPackageName() string { + return d.packageName +} + func (d *DevConn) IsV6() bool { return !d.v4 } @@ -70,8 +78,8 @@ func (d *DevConn) ID() uint64 { return uint64(d.clientPort) } -// 构造函数 -func NewDevConn(h io.ReadWriteCloser, clientIP net.IP, clientPort uint16, serverIP net.IP, serverPort uint16, ipv4 bool, clientSynSeq uint32) *DevConn { +// 构造函数;protocol 为 IP 协议号(TCP=6,UDP=17),仅 Android 用于查询来源包名。 +func NewDevConn(h io.ReadWriteCloser, clientIP net.IP, clientPort uint16, serverIP net.IP, serverPort uint16, ipv4 bool, protocol int, clientSynSeq uint32) *DevConn { // 创建 DevConn 基本信息 d := &DevConn{ clientIP: clientIP, // 客户端 IP @@ -82,6 +90,14 @@ func NewDevConn(h io.ReadWriteCloser, clientIP net.IP, clientPort uint16, server v4: ipv4, // 是否 IPv4 tun: h, // TUN 句柄 } + // 包名查询走 getPackageName 五元组缓存,减少 JNI 调用 + pkgRes := getPackageName.GetRequestPackageName( + protocol, + clientIP.String(), int(clientPort), + serverIP.String(), int(serverPort), + ) + d.packageName = pkgRes.Name + d.packageRegistered = pkgRes.Registered d.ts = time.Now() // 客户端期望 seq:客户端 ISN + 1 // 第一个数据包的 tcp.Seq 必须等于这个值才会被接收 diff --git a/src/ProcessDrv/tun/Tun/tcp.go b/src/ProcessDrv/tun/Tun/tcp.go index 2120862..635bf70 100644 --- a/src/ProcessDrv/tun/Tun/tcp.go +++ b/src/ProcessDrv/tun/Tun/tcp.go @@ -19,7 +19,7 @@ import ( func (n *NewTun) handleTCPCommand(tcp *layers.TCP, srcIP, dstIP net.IP, clientPort, serverPort uint16, v4 bool) { if tcp.SYN && !tcp.ACK { pid, name := getPidByPort("tcp", uint16(tcp.SrcPort)) - s := NewDevConn(n.tun, srcIP, clientPort, dstIP, serverPort, v4, tcp.Seq) + s := NewDevConn(n.tun, srcIP, clientPort, dstIP, serverPort, v4, 6, tcp.Seq) s.pid = uint32(pid) sessionsMu.Lock() sessions[clientPort] = s @@ -35,7 +35,7 @@ func (n *NewTun) handleTCPCommand(tcp *layers.TCP, srcIP, dstIP net.IP, clientPo sessionsMu.Lock() call := n.handleTCPCallback sessionsMu.Unlock() - if n.pidFromCheck(pid, name) { + if (s.packageName != "" && !s.packageRegistered) || n.pidFromCheck(pid, name) { go func() { var loader *net.TCPAddr if defaultGatewayIP != "" { @@ -79,7 +79,7 @@ func (n *NewTun) handleTCPCommand(tcp *layers.TCP, srcIP, dstIP net.IP, clientPo sess, ok := sessions[clientPort] sessionsMu.Unlock() if !ok { - h2 := NewDevConn(n.tun, srcIP, clientPort, dstIP, serverPort, v4, tcp.Seq) + h2 := NewDevConn(n.tun, srcIP, clientPort, dstIP, serverPort, v4, 6, tcp.Seq) _ = SendRstToClient(h2) return } diff --git a/src/ProcessDrv/tun/Tun/udp.go b/src/ProcessDrv/tun/Tun/udp.go index 03ffe02..622ef45 100644 --- a/src/ProcessDrv/tun/Tun/udp.go +++ b/src/ProcessDrv/tun/Tun/udp.go @@ -14,6 +14,7 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/qtgolang/SunnyNet/src/ProcessDrv/SunnyNetUDP" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/getPackageName" "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/tunPublic" "github.com/qtgolang/SunnyNet/src/public" ) @@ -30,8 +31,13 @@ type connEntry struct { v4 bool pid int32 pidFromCheck bool - callback func(Type int, Theoni int64, pid uint32, LocalAddress, RemoteAddress string, data []byte) []byte + callback func(Type int, Theoni int64, pid uint32, LocalAddress, RemoteAddress string, data []byte, packageName string) []byte mu *sync.Mutex + packageName string +} + +func (c *connEntry) GetPackageName() string { + return c.packageName } func (c *connEntry) ToClient(payload []byte) bool { @@ -116,9 +122,16 @@ func (n *NewTun) handleUDP(srcIP, dstIP net.IP, udp *layers.UDP, v4 bool) { if obj == nil { var mu sync.Mutex obj = &connEntry{ClientIP: clientIP, ClientPort: clientPort, ServerIP: serverIP, ServerPort: serverPort, Theology: atomic.AddInt64(&public.Theology, 1), v4: v4, callback: n.handleUDPCallback, mu: &mu, fd: n.tun} + pkgRes := getPackageName.GetRequestPackageName( + getPackageName.ProtocolUDP, + clientIP.String(), int(clientPort), + serverIP.String(), int(serverPort), + ) + obj.packageName = pkgRes.Name + pid, name := getPidByPort("udp", clientPort) obj.pid = pid - obj.pidFromCheck = n.pidFromCheck(obj.pid, name) + obj.pidFromCheck = (pkgRes.Name != "" && !pkgRes.Registered) || n.pidFromCheck(obj.pid, name) connTable[clientPort] = obj connMu.Unlock() mu.Lock() @@ -159,7 +172,7 @@ func (n *NewTun) handleUDP(srcIP, dstIP net.IP, udp *layers.UDP, v4 bool) { if tunPublic.IsLocalIp(obj.ServerIP) { RemoteAddress = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(obj.ServerPort))) } - bs := obj.callback(public.SunnyNetUDPTypeSend, obj.Theology, uint32(obj.pid), LocalAddress, RemoteAddress, Payload) + bs := obj.callback(public.SunnyNetUDPTypeSend, obj.Theology, uint32(obj.pid), LocalAddress, RemoteAddress, Payload, obj.packageName) if len(bs) < 1 { return } @@ -202,7 +215,7 @@ func (c *connEntry) loop() { return } - bs := c.callback(public.SunnyNetUDPTypeReceive, c.Theology, uint32(c.pid), LocalAddress, RemoteAddress, buff[:nt]) + bs := c.callback(public.SunnyNetUDPTypeReceive, c.Theology, uint32(c.pid), LocalAddress, RemoteAddress, buff[:nt], c.packageName) if len(bs) < 1 { continue } @@ -218,7 +231,7 @@ func (c *connEntry) loop() { return } if c.callback != nil { - c.callback(public.SunnyNetUDPTypeClosed, c.Theology, uint32(c.pid), LocalAddress, RemoteAddress, nil) + c.callback(public.SunnyNetUDPTypeClosed, c.Theology, uint32(c.pid), LocalAddress, RemoteAddress, nil, c.packageName) } delete(connTable, c.ClientPort) } diff --git a/src/ProcessDrv/tun/WinDivert/conn.go b/src/ProcessDrv/tun/WinDivert/conn.go index 4c260ef..18c0c09 100644 --- a/src/ProcessDrv/tun/WinDivert/conn.go +++ b/src/ProcessDrv/tun/WinDivert/conn.go @@ -67,6 +67,10 @@ func (d *DevConn) GetPid() string { return strconv.Itoa(int(d.pid)) } +func (d *DevConn) GetPackageName() string { + return "" +} + func (d *DevConn) IsV6() bool { return !d.v4 } diff --git a/src/ProcessDrv/tun/WinDivert/main.go b/src/ProcessDrv/tun/WinDivert/main.go index ebca08a..8595fcf 100644 --- a/src/ProcessDrv/tun/WinDivert/main.go +++ b/src/ProcessDrv/tun/WinDivert/main.go @@ -126,7 +126,7 @@ func (d *Divert) Run() bool { func (d *Divert) SetHandle( callbackTCP func(conn net.Conn), checkProcess func(int32, string) bool, - udpSendReceiveFunc func(Type int, Theoni int64, pid uint32, LocalAddress, RemoteAddress string, data []byte) []byte) { + udpSendReceiveFunc Tun.UdpFunc) { sessionsMu.Lock() defer sessionsMu.Unlock() d.handleTCP = callbackTCP diff --git a/src/ProcessDrv/tun/WinDivert/udp.go b/src/ProcessDrv/tun/WinDivert/udp.go index e025a5f..d0b33f0 100644 --- a/src/ProcessDrv/tun/WinDivert/udp.go +++ b/src/ProcessDrv/tun/WinDivert/udp.go @@ -6,14 +6,15 @@ package WinDivert import ( "bytes" "fmt" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/qtgolang/SunnyNet/src/ProcessDrv/SunnyNetUDP" - "github.com/qtgolang/SunnyNet/src/public" "net" "strconv" "sync" "sync/atomic" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/SunnyNetUDP" + "github.com/qtgolang/SunnyNet/src/public" ) type expiryUDP struct { @@ -153,9 +154,9 @@ func (d *Divert) handleCommandUDP(h *Handle, data []byte, addr *Address, udp *la RemoteAddress := net.JoinHostPort(serverIP.String(), strconv.Itoa(int(serverPort))) var bs []byte if addr.Outbound() { - bs = call(public.SunnyNetUDPTypeSend, obj.Theology, uint32(obj.pid), LocalAddress, RemoteAddress, payload) + bs = call(public.SunnyNetUDPTypeSend, obj.Theology, uint32(obj.pid), LocalAddress, RemoteAddress, payload, "") } else { - bs = call(public.SunnyNetUDPTypeReceive, obj.Theology, uint32(obj.pid), RemoteAddress, LocalAddress, payload) + bs = call(public.SunnyNetUDPTypeReceive, obj.Theology, uint32(obj.pid), RemoteAddress, LocalAddress, payload, "") } if len(bs) > 0 { if bytes.Equal(bs, payload) { @@ -265,7 +266,7 @@ func (d *Divert) runFlow() bool { call := d.handleUDP sessionsMu.Unlock() if call != nil { - call(public.SunnyNetUDPTypeClosed, obj.Theology, uint32(obj.pid), LocalAddress, RemoteAddress, nil) + call(public.SunnyNetUDPTypeClosed, obj.Theology, uint32(obj.pid), LocalAddress, RemoteAddress, nil, "") } SunnyNetUDP.DelUDPItem(obj.Theology) } diff --git a/src/ProcessDrv/tun/getPackageName/android.go b/src/ProcessDrv/tun/getPackageName/android.go new file mode 100644 index 0000000..d5b15d4 --- /dev/null +++ b/src/ProcessDrv/tun/getPackageName/android.go @@ -0,0 +1,23 @@ +//go:build android +// +build android + +package getPackageName + +import JavaJni "github.com/qtgolang/SunnyNet/JavaApi" + +const ( + ProtocolTCP = 6 // IPPROTO_TCP + ProtocolUDP = 17 // IPPROTO_UDP +) + +// SetConnectivityManager 兼容旧接口;实际 CM 由 JavaJni.InitTunConnectivityManager 在 OnTunSetFd 中注入。 +// 调用时清空包名缓存,避免 VPN 重建后沿用旧五元组结果。 +func SetConnectivityManager(uintptr) { + ClearPackageNameCache() +} + +// queryPackageName 执行一次 JNI 查询(无缓存);由 GetRequestPackageName 统一加缓存。 +func queryPackageName(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) Result { + r := JavaJni.TunGetRequestPackageName(protocol, srcIp, srcPort, dstIp, dstPort) + return Result{Name: r.Name, Registered: r.Registered} +} diff --git a/src/ProcessDrv/tun/getPackageName/cache.go b/src/ProcessDrv/tun/getPackageName/cache.go new file mode 100644 index 0000000..6cd80d0 --- /dev/null +++ b/src/ProcessDrv/tun/getPackageName/cache.go @@ -0,0 +1,116 @@ +// Package getPackageName 在 Android TUN 模式下根据五元组查询流量来源包名。 +// 查询结果带内存缓存,避免 SYN 重传、UDP 多包等场景反复调用 JNI。 +package getPackageName + +import ( + "fmt" + "sync" + "time" +) + +const ( + // pkgCacheTTLPositive 命中包名时的缓存时长(连接存活期内五元组通常不变)。 + pkgCacheTTLPositive = 2 * time.Minute + // pkgCacheTTLNegative 未查到包名时的缓存时长(较短,便于 socket 登记后尽快重试)。 + pkgCacheTTLNegative = 5 * time.Second + // maxCacheEntries 缓存条数上限,防止长时间运行内存无限增长。 + maxCacheEntries = 2048 +) + +// cacheItem 单条缓存记录。 +type cacheItem struct { + result Result // 查询结果(包名 + 是否已在 PackageManager 注册) + expire time.Time // 过期时间 +} + +var ( + pkgCacheMu sync.RWMutex + pkgCache = make(map[string]cacheItem) // key 见 cacheKey +) + +// cacheKey 生成五元组缓存键:protocol|srcIp|srcPort|dstIp|dstPort。 +// src 为 TUN 上看到的客户端(应用侧),dst 为目标服务器。 +func cacheKey(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) string { + return fmt.Sprintf("%d|%s|%d|%s|%d", protocol, srcIp, srcPort, dstIp, dstPort) +} + +// getFromCache 读取缓存;第二个返回值为 true 表示命中且未过期。 +func getFromCache(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) (Result, bool) { + key := cacheKey(protocol, srcIp, srcPort, dstIp, dstPort) + now := time.Now() + + pkgCacheMu.RLock() + item, ok := pkgCache[key] + pkgCacheMu.RUnlock() + if !ok { + return Result{}, false + } + if now.After(item.expire) { + // 惰性删除过期项,避免后台清扫协程 + pkgCacheMu.Lock() + if cur, still := pkgCache[key]; still && now.After(cur.expire) { + delete(pkgCache, key) + } + pkgCacheMu.Unlock() + return Result{}, false + } + return item.result, true +} + +// putCache 写入缓存;有包名与无包名使用不同 TTL。 +func putCache(protocol int, srcIp string, srcPort int, dstIp string, dstPort int, r Result) { + ttl := pkgCacheTTLNegative + if r.Name != "" { + ttl = pkgCacheTTLPositive + } + key := cacheKey(protocol, srcIp, srcPort, dstIp, dstPort) + item := cacheItem{result: r, expire: time.Now().Add(ttl)} + + pkgCacheMu.Lock() + pkgCache[key] = item + if len(pkgCache) > maxCacheEntries { + evictExpiredLocked(time.Now()) + // 仍超限则整表重建(极端流量下的兜底) + if len(pkgCache) > maxCacheEntries { + pkgCache = make(map[string]cacheItem) + } + } + pkgCacheMu.Unlock() +} + +// evictExpiredLocked 删除所有已过期项;调用方需已持有 pkgCacheMu 写锁。 +func evictExpiredLocked(now time.Time) { + for k, v := range pkgCache { + if now.After(v.expire) { + delete(pkgCache, k) + } + } +} + +// ClearPackageNameCache 清空五元组包名查询缓存。 +// 建议在 VPN 重启、OnTunSetFd 或切换用户后调用,避免沿用过期归属信息。 +func ClearPackageNameCache() { + pkgCacheMu.Lock() + pkgCache = make(map[string]cacheItem) + pkgCacheMu.Unlock() +} + +// GetRequestPackageName 按五元组查询来源包名。 +// +// 参数: +// - protocol:IP 协议号,TCP=6,UDP=17 +// - srcIp/srcPort:TUN 包中的源地址(VPN 内应用侧,如 10.0.0.2:port) +// - dstIp/dstPort:TUN 包中的目的地址 +// +// 先查缓存,未命中则调用 queryPackageName(Android 下走 ConnectivityManager JNI)。 +func GetRequestPackageName(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) Result { + if r, ok := getFromCache(protocol, srcIp, srcPort, dstIp, dstPort); ok { + return r + } + r := queryPackageName(protocol, srcIp, srcPort, dstIp, dstPort) + if r.Name == "" { + return r + } + putCache(protocol, srcIp, srcPort, dstIp, dstPort, r) + return r +} diff --git a/src/ProcessDrv/tun/getPackageName/noAndroid.go b/src/ProcessDrv/tun/getPackageName/noAndroid.go new file mode 100644 index 0000000..4c6deeb --- /dev/null +++ b/src/ProcessDrv/tun/getPackageName/noAndroid.go @@ -0,0 +1,17 @@ +//go:build !android +// +build !android + +package getPackageName + +const ( + ProtocolTCP = 6 + ProtocolUDP = 17 +) + +// SetConnectivityManager 非 Android 平台无 TUN 包名查询能力。 +func SetConnectivityManager(uintptr) {} + +// queryPackageName 非 Android 恒为空结果。 +func queryPackageName(protocol int, srcIp string, srcPort int, dstIp string, dstPort int) Result { + return Result{} +} diff --git a/src/ProcessDrv/tun/getPackageName/result.go b/src/ProcessDrv/tun/getPackageName/result.go new file mode 100644 index 0000000..b59bdb3 --- /dev/null +++ b/src/ProcessDrv/tun/getPackageName/result.go @@ -0,0 +1,7 @@ +package getPackageName + +// Result 为 TUN 五元组包名查询结果(与 JavaApi.TunPackageResult 对应)。 +type Result struct { + Name string // 来源应用包名(已去掉 getNameForUid 的 ":appId" 后缀) + Registered bool // Name 是否为已安装包(pm list packages 可查);false 时可能为共享 UID 合成名 +} diff --git a/src/ProcessDrv/tun/oth.go b/src/ProcessDrv/tun/oth.go index 27866e9..b9b8054 100644 --- a/src/ProcessDrv/tun/oth.go +++ b/src/ProcessDrv/tun/oth.go @@ -32,5 +32,5 @@ func UnInstall() bool { return true } func SetFd(fd int) bool { - return false + return true } diff --git a/src/ProcessDrv/tun/windows.go b/src/ProcessDrv/tun/windows.go index 9ae37d6..00a4c3d 100644 --- a/src/ProcessDrv/tun/windows.go +++ b/src/ProcessDrv/tun/windows.go @@ -6,15 +6,16 @@ package tun import ( "bufio" "bytes" + "io" + "os/exec" + "strings" + "syscall" + "github.com/qtgolang/SunnyNet/src/ProcessDrv/Info" "github.com/qtgolang/SunnyNet/src/ProcessDrv/ProcessCheck" "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/Tun" divert "github.com/qtgolang/SunnyNet/src/ProcessDrv/tun/WinDivert" "github.com/qtgolang/SunnyNet/src/Resource" - "io" - "os/exec" - "strings" - "syscall" ) var Divert = divert.NewWinDivert() diff --git a/src/crypto/internal/cpu/cpu_x86_test.go b/src/crypto/internal/cpu/cpu_x86_test.go deleted file mode 100644 index 61db93b..0000000 --- a/src/crypto/internal/cpu/cpu_x86_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build 386 amd64 - -package cpu_test - -import ( - . "internal/cpu" - "os" - "runtime" - "testing" -) - -func TestX86ifAVX2hasAVX(t *testing.T) { - if X86.HasAVX2 && !X86.HasAVX { - t.Fatalf("HasAVX expected true when HasAVX2 is true, got false") - } -} - -func TestDisableSSE2(t *testing.T) { - runDebugOptionsTest(t, "TestSSE2DebugOption", "cpu.sse2=off") -} - -func TestSSE2DebugOption(t *testing.T) { - MustHaveDebugOptionsSupport(t) - - if os.Getenv("GODEBUG") != "cpu.sse2=off" { - t.Skipf("skipping test: GODEBUG=cpu.sse2=off not set") - } - - want := runtime.GOARCH != "386" // SSE2 can only be disabled on 386. - if got := X86.HasSSE2; got != want { - t.Errorf("X86.HasSSE2 on %s expected %v, got %v", runtime.GOARCH, want, got) - } -} - -func TestDisableSSE3(t *testing.T) { - runDebugOptionsTest(t, "TestSSE3DebugOption", "cpu.sse3=off") -} - -func TestSSE3DebugOption(t *testing.T) { - MustHaveDebugOptionsSupport(t) - - if os.Getenv("GODEBUG") != "cpu.sse3=off" { - t.Skipf("skipping test: GODEBUG=cpu.sse3=off not set") - } - - want := false - if got := X86.HasSSE3; got != want { - t.Errorf("X86.HasSSE3 expected %v, got %v", want, got) - } -} diff --git a/src/public/constobj.go b/src/public/constobj.go index 7bf21c5..22a66de 100644 --- a/src/public/constobj.go +++ b/src/public/constobj.go @@ -17,7 +17,7 @@ import ( "github.com/qtgolang/SunnyNet/src/websocket" ) -const SunnyVersion = "2026-05-22" +const SunnyVersion = "2026-05-26" const Information = ` ------------------------------------------------------ 欢迎使用 SunnyNet 网络中间件 - V` + SunnyVersion + `