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  }