github.com/sagernet/sing-box@v1.9.0-rc.20/experimental/libbox/build_info.go (about) 1 //go:build android 2 3 package libbox 4 5 import ( 6 "archive/zip" 7 "bytes" 8 "debug/buildinfo" 9 "io" 10 "runtime/debug" 11 "strings" 12 13 "github.com/sagernet/sing/common" 14 ) 15 16 const ( 17 androidVPNCoreTypeOpenVPN = "OpenVPN" 18 androidVPNCoreTypeShadowsocks = "Shadowsocks" 19 androidVPNCoreTypeClash = "Clash" 20 androidVPNCoreTypeV2Ray = "V2Ray" 21 androidVPNCoreTypeWireGuard = "WireGuard" 22 androidVPNCoreTypeSingBox = "sing-box" 23 androidVPNCoreTypeUnknown = "Unknown" 24 ) 25 26 type AndroidVPNType struct { 27 CoreType string 28 CorePath string 29 GoVersion string 30 } 31 32 func ReadAndroidVPNType(publicSourceDirList StringIterator) (*AndroidVPNType, error) { 33 apkPathList := iteratorToArray[string](publicSourceDirList) 34 var lastError error 35 for _, apkPath := range apkPathList { 36 androidVPNType, err := readAndroidVPNType(apkPath) 37 if androidVPNType == nil { 38 if err != nil { 39 lastError = err 40 } 41 continue 42 } 43 return androidVPNType, nil 44 } 45 return nil, lastError 46 } 47 48 func readAndroidVPNType(publicSourceDir string) (*AndroidVPNType, error) { 49 reader, err := zip.OpenReader(publicSourceDir) 50 if err != nil { 51 return nil, err 52 } 53 defer reader.Close() 54 var lastError error 55 for _, file := range reader.File { 56 if !strings.HasPrefix(file.Name, "lib/") { 57 continue 58 } 59 vpnType, err := readAndroidVPNTypeEntry(file) 60 if err != nil { 61 lastError = err 62 continue 63 } 64 return vpnType, nil 65 } 66 for _, file := range reader.File { 67 if !strings.HasPrefix(file.Name, "lib/") { 68 continue 69 } 70 if strings.Contains(file.Name, androidVPNCoreTypeOpenVPN) || strings.Contains(file.Name, "ovpn") { 71 return &AndroidVPNType{CoreType: androidVPNCoreTypeOpenVPN}, nil 72 } 73 if strings.Contains(file.Name, androidVPNCoreTypeShadowsocks) { 74 return &AndroidVPNType{CoreType: androidVPNCoreTypeShadowsocks}, nil 75 } 76 } 77 return nil, lastError 78 } 79 80 func readAndroidVPNTypeEntry(zipFile *zip.File) (*AndroidVPNType, error) { 81 readCloser, err := zipFile.Open() 82 if err != nil { 83 return nil, err 84 } 85 libContent := make([]byte, zipFile.UncompressedSize64) 86 _, err = io.ReadFull(readCloser, libContent) 87 readCloser.Close() 88 if err != nil { 89 return nil, err 90 } 91 buildInfo, err := buildinfo.Read(bytes.NewReader(libContent)) 92 if err != nil { 93 return nil, err 94 } 95 var vpnType AndroidVPNType 96 vpnType.GoVersion = buildInfo.GoVersion 97 if !strings.HasPrefix(vpnType.GoVersion, "go") { 98 vpnType.GoVersion = "obfuscated" 99 } else { 100 vpnType.GoVersion = vpnType.GoVersion[2:] 101 } 102 vpnType.CoreType = androidVPNCoreTypeUnknown 103 if len(buildInfo.Deps) == 0 { 104 vpnType.CoreType = "obfuscated" 105 return &vpnType, nil 106 } 107 108 dependencies := make(map[string]bool) 109 dependencies[buildInfo.Path] = true 110 for _, module := range buildInfo.Deps { 111 dependencies[module.Path] = true 112 if module.Replace != nil { 113 dependencies[module.Replace.Path] = true 114 } 115 } 116 for dependency := range dependencies { 117 pkgType, loaded := determinePkgType(dependency) 118 if loaded { 119 vpnType.CoreType = pkgType 120 } 121 } 122 if vpnType.CoreType == androidVPNCoreTypeUnknown { 123 for dependency := range dependencies { 124 pkgType, loaded := determinePkgTypeSecondary(dependency) 125 if loaded { 126 vpnType.CoreType = pkgType 127 return &vpnType, nil 128 } 129 } 130 } 131 if vpnType.CoreType != androidVPNCoreTypeUnknown { 132 vpnType.CorePath, _ = determineCorePath(buildInfo, vpnType.CoreType) 133 return &vpnType, nil 134 } 135 if dependencies["github.com/golang/protobuf"] && dependencies["github.com/v2fly/ss-bloomring"] { 136 vpnType.CoreType = androidVPNCoreTypeV2Ray 137 return &vpnType, nil 138 } 139 return &vpnType, nil 140 } 141 142 func determinePkgType(pkgName string) (string, bool) { 143 pkgNameLower := strings.ToLower(pkgName) 144 if strings.Contains(pkgNameLower, "clash") { 145 return androidVPNCoreTypeClash, true 146 } 147 if strings.Contains(pkgNameLower, "v2ray") || strings.Contains(pkgNameLower, "xray") { 148 return androidVPNCoreTypeV2Ray, true 149 } 150 151 if strings.Contains(pkgNameLower, "sing-box") { 152 return androidVPNCoreTypeSingBox, true 153 } 154 return "", false 155 } 156 157 func determinePkgTypeSecondary(pkgName string) (string, bool) { 158 pkgNameLower := strings.ToLower(pkgName) 159 if strings.Contains(pkgNameLower, "wireguard") { 160 return androidVPNCoreTypeWireGuard, true 161 } 162 return "", false 163 } 164 165 func determineCorePath(pkgInfo *buildinfo.BuildInfo, pkgType string) (string, bool) { 166 switch pkgType { 167 case androidVPNCoreTypeClash: 168 return determineCorePathForPkgs(pkgInfo, []string{"github.com/Dreamacro/clash"}, []string{"clash"}) 169 case androidVPNCoreTypeV2Ray: 170 if v2rayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{ 171 "github.com/v2fly/v2ray-core", 172 "github.com/v2fly/v2ray-core/v4", 173 "github.com/v2fly/v2ray-core/v5", 174 }, []string{ 175 "v2ray", 176 }); loaded { 177 return v2rayVersion, true 178 } 179 if xrayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{ 180 "github.com/xtls/xray-core", 181 }, []string{ 182 "xray", 183 }); loaded { 184 return xrayVersion, true 185 } 186 return "", false 187 case androidVPNCoreTypeSingBox: 188 return determineCorePathForPkgs(pkgInfo, []string{"github.com/sagernet/sing-box"}, []string{"sing-box"}) 189 case androidVPNCoreTypeWireGuard: 190 return determineCorePathForPkgs(pkgInfo, []string{"golang.zx2c4.com/wireguard"}, []string{"wireguard"}) 191 default: 192 return "", false 193 } 194 } 195 196 func determineCorePathForPkgs(pkgInfo *buildinfo.BuildInfo, pkgs []string, names []string) (string, bool) { 197 for _, pkg := range pkgs { 198 if pkgInfo.Path == pkg { 199 return pkg, true 200 } 201 strictDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool { 202 return module.Path == pkg 203 }) 204 if strictDependency != nil { 205 if isValidVersion(strictDependency.Version) { 206 return strictDependency.Path + " " + strictDependency.Version, true 207 } else { 208 return strictDependency.Path, true 209 } 210 } 211 } 212 for _, name := range names { 213 if strings.Contains(pkgInfo.Path, name) { 214 return pkgInfo.Path, true 215 } 216 looseDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool { 217 return strings.Contains(module.Path, name) || (module.Replace != nil && strings.Contains(module.Replace.Path, name)) 218 }) 219 if looseDependency != nil { 220 return looseDependency.Path, true 221 } 222 } 223 return "", false 224 } 225 226 func isValidVersion(version string) bool { 227 if version == "(devel)" { 228 return false 229 } 230 if strings.Contains(version, "v0.0.0") { 231 return false 232 } 233 return true 234 }