build_info.go 6.2 KB

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