This commit is contained in:
yiguo
2023-05-27 20:26:19 +08:00
parent fd4d5e6303
commit 3fb6faba89
15 changed files with 1578 additions and 1 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
*.xcframework/
*.jar
*.aar
go.mod
go.sum
main/

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 yiguous
Copyright (c) 2023 XTLS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

73
README.md Normal file
View File

@@ -0,0 +1,73 @@
# libxray
This is an Xray wrapper focusing on improving the experience of [Xray-core](https://github.com/XTLS/Xray-core) mobile development.
# Features
### build.sh
编译脚本。一键生成 xcframework 和 aar。
### clash.go
转换 Clash yamlClash.Meta yaml 为 Xray Json。
### controller.go
试验性的 Android Protect(fd)。
### file.go
文件写入。
### geo.go
读取 geosite.data生成类别名称文件包含 Attribute。
读取 geoip.data生成类别名称文件。
### memory.go
强制 GC。
### ping.go
测速。
### port.go
获取空闲端口。
### share.go
转换 v2rayN 订阅为 Xray Json。不支持 VMessQRCode。
转换 VMessAEAD/VLESS 分享为 Xray Json。
### uuid.go
转换自定义文本为 uuid。
### xray_json.go
Xray 配置的子集,为出口节点添加了 Name 字段,便于 App 内进行解析。
### xray.go
启动和停止 Xray 。
# Used By
[FoXray](https://apps.apple.com/app/foxray/id6448898396)
# Contributing
[yiguo](https://yiguo.dev) wrote the original source code. Now it belongs to the Xray Community.
## Credits
[Project X](https://github.com/xtls/xray-core)
[VMessPing](https://github.com/v2fly/vmessping)
[FreePort](https://github.com/phayes/freeport)

29
build.sh Normal file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
go mod init libxray
go mod tidy
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
go get -d golang.org/x/mobile/cmd/gomobile
build_apple() {
rm -fr *.xcframework
gomobile bind -target ios
mv Libxray.xcframework ios.xcframework
gomobile bind -target macos
mv Libxray.xcframework macos.xcframework
xcodebuild -create-xcframework -framework ios.xcframework/ios-arm64/Libxray.framework -framework ios.xcframework/ios-arm64_x86_64-simulator/Libxray.framework -framework macos.xcframework/macos-arm64_x86_64/Libxray.framework -output Libxray.xcframework
}
build_android() {
rm -fr *.jar
rm -fr *.aar
gomobile bind -target android -androidapi 28
}
echo "will build libxray for $1"
if [ "$1" != "apple" ]; then
build_android
else
build_apple
fi

416
clash.go Normal file
View File

@@ -0,0 +1,416 @@
package libxray
import (
"fmt"
"encoding/json"
"gopkg.in/yaml.v3"
)
type clashYaml struct {
Proxies []clashProxy `yaml:"proxies,omitempty"`
}
type clashProxy struct {
Name string `yaml:"name,omitempty"`
Type string `yaml:"type,omitempty"`
Server string `yaml:"server,omitempty"`
Port int `yaml:"port,omitempty"`
Uuid string `yaml:"uuid,omitempty"`
Cipher string `yaml:"cipher,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Tls bool `yaml:"tls,omitempty"`
SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"`
Servername string `yaml:"servername,omitempty"`
Sni string `yaml:"sni,omitempty"`
Alpn []string `yaml:"alpn,omitempty"`
Fingerprint string `yaml:"fingerprint,omitempty"`
ClientFingerprint string `yaml:"client-fingerprint,omitempty"`
Flow string `yaml:"flow,omitempty"`
RealityOpts *clashProxyRealityOpts `yaml:"reality-opts,omitempty"`
Network string `yaml:"network,omitempty"`
Plugin string `yaml:"plugin,omitempty"`
PluginOpts *clashProxyPluginOpts `yaml:"plugin-opts,omitempty"`
WsOpts *clashProxyWsOpts `yaml:"ws-opts,omitempty"`
H2Opts *clashProxyH2Opts `yaml:"h2-opts,omitempty"`
GrpcOpts *clashProxyGrpcOpts `yaml:"grpc-opts,omitempty"`
}
type clashProxyPluginOpts struct {
Mode string `yaml:"mode,omitempty"`
Tls bool `yaml:"tls,omitempty"`
Fingerprint string `yaml:"fingerprint,omitempty"`
SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"`
Host string `yaml:"host,omitempty"`
Path string `yaml:"path,omitempty"`
}
type clashProxyWsOpts struct {
Path string `yaml:"path,omitempty"`
Headers *clashProxyWsOptsHeaders `yaml:"headers,omitempty"`
MaxEarlyData int `yaml:"max-early-data,omitempty"`
EarlyDataHeaderName string `yaml:"early-data-header-name,omitempty"`
}
type clashProxyWsOptsHeaders struct {
Host string `yaml:"Host,omitempty"`
}
type clashProxyH2Opts struct {
Host []string `yaml:"host,omitempty"`
Path string `yaml:"path,omitempty"`
}
type clashProxyGrpcOpts struct {
GrpcServiceName string `yaml:"grpc-service-name,omitempty"`
}
type clashProxyRealityOpts struct {
PublicKey string `yaml:"public-key,omitempty"`
ShortId string `yaml:"short-id,omitempty"`
}
func tryConvertClashYaml(text string) (*xrayJson, error) {
clashBytes := []byte(text)
clash := clashYaml{}
err := yaml.Unmarshal(clashBytes, &clash)
if err != nil {
return nil, err
}
xray := clash.xrayConfig()
return &xray, nil
}
func (clash clashYaml) xrayConfig() xrayJson {
var xray xrayJson
var outbounds []xrayOutbound
for _, proxy := range clash.Proxies {
if outbound, err := proxy.outbound(); err == nil {
outbounds = append(outbounds, *outbound)
} else {
fmt.Println(err)
}
}
xray.Outbounds = outbounds
return xray
}
func (proxy clashProxy) outbound() (*xrayOutbound, error) {
switch proxy.Type {
case "ss":
outbound, err := proxy.shadowsocksOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "vmess":
outbound, err := proxy.vmessOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "vless":
outbound, err := proxy.vlessOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "socks5":
outbound, err := proxy.socksOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "trojan":
outbound, err := proxy.trojanOutbound()
if err != nil {
return nil, err
}
return outbound, nil
}
return nil, fmt.Errorf("unsupport proxy type: %s", proxy.Type)
}
func (proxy clashProxy) shadowsocksOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "shadowsocks"
outbound.Name = proxy.Name
var server xrayShadowsocksServer
server.Address = proxy.Server
server.Port = proxy.Port
server.Method = proxy.Cipher
server.Password = proxy.Password
var settings xrayShadowsocks
settings.Servers = []xrayShadowsocksServer{server}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
if len(proxy.Plugin) != 0 {
if proxy.Plugin != "v2ray-plugin" {
return nil, fmt.Errorf("unsupport ss plugin: obfs")
}
if proxy.PluginOpts == nil {
return nil, fmt.Errorf("unsupport ss plugin-opts: nil")
}
if proxy.PluginOpts.Mode != "websocket" {
return nil, fmt.Errorf("unsupport ss plugin-opts mode: %s", proxy.PluginOpts.Mode)
}
var streamSetting xrayStreamSettings
streamSetting.Network = "websocket"
var wsSettings xrayWsSettings
if len(proxy.PluginOpts.Path) > 0 {
wsSettings.Path = proxy.PluginOpts.Path
}
if len(proxy.PluginOpts.Host) > 0 {
var headers xrayWsSettingsHeaders
headers.Host = proxy.PluginOpts.Host
wsSettings.Headers = &headers
}
streamSetting.WsSettings = &wsSettings
if proxy.PluginOpts.Tls {
var tlsSettings xrayTlsSettings
tlsSettings.Fingerprint = proxy.PluginOpts.Fingerprint
tlsSettings.AllowInsecure = proxy.PluginOpts.SkipCertVerify
streamSetting.TlsSettings = &tlsSettings
}
outbound.StreamSettings = &streamSetting
}
return &outbound, nil
}
func (proxy clashProxy) vmessOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "vmess"
outbound.Name = proxy.Name
var user xrayVMessVnextUser
user.Id = proxy.Uuid
user.Security = proxy.Cipher
var vnext xrayVMessVnext
vnext.Address = proxy.Server
vnext.Port = proxy.Port
vnext.Users = []xrayVMessVnextUser{user}
var settings xrayVMess
settings.Vnext = []xrayVMessVnext{vnext}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
streamSettings, err := proxy.streamSettings(outbound)
if err != nil {
return nil, err
}
outbound.StreamSettings = streamSettings
return &outbound, nil
}
func (proxy clashProxy) vlessOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "vless"
outbound.Name = proxy.Name
var user xrayVLESSVnextUser
user.Id = proxy.Uuid
user.Flow = proxy.Flow
var vnext xrayVLESSVnext
vnext.Address = proxy.Server
vnext.Port = proxy.Port
vnext.Users = []xrayVLESSVnextUser{user}
var settings xrayVLESS
settings.Vnext = []xrayVLESSVnext{vnext}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
streamSettings, err := proxy.streamSettings(outbound)
if err != nil {
return nil, err
}
outbound.StreamSettings = streamSettings
return &outbound, nil
}
func (proxy clashProxy) socksOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "socks"
outbound.Name = proxy.Name
var user xraySocksServerUser
user.User = proxy.Username
user.Pass = proxy.Password
var server xraySocksServer
server.Address = proxy.Server
server.Port = proxy.Port
server.Users = []xraySocksServerUser{user}
var settings xraySocks
settings.Servers = []xraySocksServer{server}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
streamSettings, err := proxy.streamSettings(outbound)
if err != nil {
return nil, err
}
outbound.StreamSettings = streamSettings
return &outbound, nil
}
func (proxy clashProxy) trojanOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "trojan"
outbound.Name = proxy.Name
var server xrayTrojanServer
server.Address = proxy.Server
server.Port = proxy.Port
server.Password = proxy.Password
var settings xrayTrojan
settings.Servers = []xrayTrojanServer{server}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
streamSettings, err := proxy.streamSettings(outbound)
if err != nil {
return nil, err
}
outbound.StreamSettings = streamSettings
return &outbound, nil
}
func (proxy clashProxy) streamSettings(outbound xrayOutbound) (*xrayStreamSettings, error) {
var streamSettings xrayStreamSettings
if len(proxy.Network) == 0 {
streamSettings.Network = "tcp"
} else {
streamSettings.Network = proxy.Network
}
switch streamSettings.Network {
case "ws":
if proxy.WsOpts != nil {
var wsSettings xrayWsSettings
if proxy.WsOpts.Headers != nil {
var headers xrayWsSettingsHeaders
headers.Host = proxy.WsOpts.Headers.Host
wsSettings.Headers = &headers
}
wsSettings.Path = proxy.WsOpts.Path
if proxy.WsOpts.MaxEarlyData > 0 {
return nil, fmt.Errorf("unsupport ws-opts max-early-data: %d", proxy.WsOpts.MaxEarlyData)
}
streamSettings.WsSettings = &wsSettings
}
case "h2":
if proxy.H2Opts != nil {
var httpSettings xrayHttpSettings
httpSettings.Host = proxy.H2Opts.Host
httpSettings.Path = proxy.H2Opts.Path
streamSettings.HttpSettings = &httpSettings
}
case "grpc":
if proxy.GrpcOpts != nil {
var grpcSettings xrayGrpcSettings
grpcSettings.ServiceName = proxy.GrpcOpts.GrpcServiceName
streamSettings.GrpcSettings = &grpcSettings
}
}
proxy.parseSecurity(&streamSettings, outbound)
return &streamSettings, nil
}
func (proxy clashProxy) parseSecurity(streamSettings *xrayStreamSettings, outbound xrayOutbound) {
var tlsSettings xrayTlsSettings
var realitySettings xrayRealitySettings
if proxy.Tls {
streamSettings.Security = "tls"
if proxy.SkipCertVerify {
tlsSettings.AllowInsecure = true
}
}
if proxy.RealityOpts != nil {
streamSettings.Security = "reality"
realitySettings.PublicKey = proxy.RealityOpts.PublicKey
realitySettings.ShortId = proxy.RealityOpts.ShortId
}
if len(proxy.Servername) > 0 {
tlsSettings.ServerName = proxy.Servername
realitySettings.ServerName = proxy.Servername
}
if len(proxy.Sni) > 0 {
tlsSettings.ServerName = proxy.Sni
realitySettings.ServerName = proxy.Sni
}
if len(proxy.Alpn) > 0 {
tlsSettings.Alpn = proxy.Alpn
}
if len(proxy.Fingerprint) > 0 {
tlsSettings.Fingerprint = proxy.Fingerprint
realitySettings.Fingerprint = proxy.Fingerprint
}
if len(proxy.ClientFingerprint) > 0 {
tlsSettings.Fingerprint = proxy.ClientFingerprint
realitySettings.Fingerprint = proxy.ClientFingerprint
}
if outbound.Protocol == "trojan" && len(streamSettings.Security) == 0 {
streamSettings.Security = "tls"
}
switch streamSettings.Security {
case "tls":
streamSettings.TlsSettings = &tlsSettings
case "reality":
streamSettings.RealitySettings = &realitySettings
}
}

16
controller.go Normal file
View File

@@ -0,0 +1,16 @@
package libxray
import (
xinternet "github.com/xtls/xray-core/transport/internet"
)
type DialerController interface {
FdCallback(int) bool
}
func RegisterDialerController(controller DialerController) {
xinternet.RegisterDialerController(func(network, address string, fd uintptr) error {
controller.FdCallback(int(fd))
return nil
})
}

33
file.go Normal file
View File

@@ -0,0 +1,33 @@
package libxray
import (
"os"
)
func writeBytes(bytes []byte, path string) error {
fi, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0664)
if err != nil {
return err
}
defer fi.Close()
_, err = fi.Write(bytes)
if err != nil {
return err
}
return nil
}
func writeText(text string, path string) error {
fi, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0664)
if err != nil {
return err
}
defer fi.Close()
_, err = fi.WriteString(text)
if err != nil {
return err
}
return nil
}

95
geo.go Normal file
View File

@@ -0,0 +1,95 @@
package libxray
import (
"fmt"
"path"
"strconv"
"strings"
"time"
"github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/common/platform/filesystem"
"google.golang.org/protobuf/proto"
)
func LoadGeoData(dir string) string {
if err := loadGeoSite(dir); err != nil {
return err.Error()
}
if err := loadGeoIP(dir); err != nil {
return err.Error()
}
ts := time.Now().Unix()
tsText := strconv.FormatInt(ts, 10)
tsPath := path.Join(dir, "timestamp.txt")
if err := writeText(tsText, tsPath); err != nil {
return err.Error()
}
return ""
}
func loadGeoSite(dir string) error {
datPath := path.Join(dir, "geosite.dat")
txtPath := path.Join(dir, "geosite.txt")
geositeBytes, err := filesystem.ReadFile(datPath)
if err != nil {
return err
}
var geositeList router.GeoSiteList
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
return err
}
var countries []string
for _, site := range geositeList.Entry {
countries = append(countries, site.CountryCode)
for _, domain := range site.Domain {
for _, attribute := range domain.Attribute {
attr := fmt.Sprintf("%s@%s", site.CountryCode, attribute.Key)
if !containsString(countries, attr) {
countries = append(countries, attr)
}
}
}
}
text := strings.Join(countries, "\n")
if err := writeText(text, txtPath); err != nil {
return err
}
return nil
}
func loadGeoIP(dir string) error {
datPath := path.Join(dir, "geoip.dat")
txtPath := path.Join(dir, "geoip.txt")
geoipBytes, err := filesystem.ReadFile(datPath)
if err != nil {
return err
}
var geoipList router.GeoIPList
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
return err
}
var countries []string
for _, geoip := range geoipList.Entry {
countries = append(countries, geoip.CountryCode)
}
text := strings.Join(countries, "\n")
if err := writeText(text, txtPath); err != nil {
return err
}
return nil
}
func containsString(slice []string, element string) bool {
for _, e := range slice {
if e == element {
return true
}
}
return false
}

39
memory.go Normal file
View File

@@ -0,0 +1,39 @@
package libxray
import (
"runtime/debug"
"time"
"github.com/xtls/xray-core/common/platform"
)
// will be removed when 1.9.0 released
func forceFree(interval time.Duration) {
go func() {
for {
time.Sleep(interval)
debug.FreeOSMemory()
}
}()
}
func readForceFreeInterval() int {
const key = "XRAY_MEMORY_FORCEFREE"
const defaultValue = 0
interval := platform.EnvFlag{
Name: key,
AltName: platform.NormalizeEnvName(key),
}.GetValueAsInt(defaultValue)
return interval
}
func initForceFree(maxMemory int64) {
debug.SetGCPercent(10)
debug.SetMemoryLimit(maxMemory)
interval := readForceFreeInterval()
if interval > 0 {
duration := time.Duration(interval) * time.Second
forceFree(duration)
}
}

93
ping.go Normal file
View File

@@ -0,0 +1,93 @@
package libxray
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"time"
xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core"
)
const (
pingDelayTimeout int64 = 11000
pingDelayError int64 = 10000
)
func Ping(datDir string, config string, timeout int, url string, times int) string {
initEnv(datDir)
server, err := startXray(config)
if err != nil {
return fmt.Sprintf("%d:%s", pingDelayError, err)
}
if err := server.Start(); err != nil {
return fmt.Sprintf("%d:%s", pingDelayError, err)
}
defer server.Close()
return measureDelay(server, timeout, url, times)
}
func measureDelay(inst *core.Instance, timeout int, url string, times int) string {
httpTimeout := time.Second * time.Duration(timeout)
c, err := coreHTTPClient(inst, httpTimeout)
if err != nil {
return fmt.Sprintf("%d:%s", pingDelayError, err)
}
delaySum := int64(0)
count := int64(0)
isValid := false
lastErr := ""
for i := 0; i < times; i++ {
delay, err := coreHTTPRequest(c, url)
if delay != pingDelayTimeout {
delaySum += delay
count += 1
isValid = true
} else {
lastErr = err.Error()
}
}
if !isValid {
return fmt.Sprintf("%d:%s", pingDelayTimeout, lastErr)
}
return fmt.Sprintf("%d:%s", delaySum/count, lastErr)
}
func coreHTTPClient(inst *core.Instance, timeout time.Duration) (*http.Client, error) {
if inst == nil {
return nil, errors.New("core instance nil")
}
tr := &http.Transport{
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dest, err := xnet.ParseDestination(fmt.Sprintf("%s:%s", network, addr))
if err != nil {
return nil, err
}
return core.Dial(ctx, inst, dest)
},
}
c := &http.Client{
Transport: tr,
Timeout: timeout,
}
return c, nil
}
func coreHTTPRequest(c *http.Client, url string) (int64, error) {
start := time.Now()
req, _ := http.NewRequest("GET", url, nil)
_, err := c.Do(req)
if err != nil {
return pingDelayTimeout, err
}
return time.Since(start).Milliseconds(), nil
}

27
port.go Normal file
View File

@@ -0,0 +1,27 @@
package libxray
import (
"fmt"
"net"
"strings"
)
// https://github.com/phayes/freeport/blob/master/freeport.go
// GetFreePort asks the kernel for free open ports that are ready to use.
func GetFreePorts(count int) string {
var ports []int
for i := 0; i < count; i++ {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return ""
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return ""
}
defer l.Close()
ports = append(ports, l.Addr().(*net.TCPAddr).Port)
}
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(ports)), ":"), "[]")
}

487
share.go Normal file
View File

@@ -0,0 +1,487 @@
package libxray
import (
"fmt"
"strings"
"encoding/base64"
"encoding/json"
"net/url"
"strconv"
"github.com/xtls/xray-core/common/platform/filesystem"
)
func ParseShareText(textPath string, xrayPath string) string {
textBytes, err := filesystem.ReadFile(textPath)
if err != nil {
return err.Error()
}
text := string(textBytes)
text = strings.TrimSpace(text)
if strings.HasPrefix(text, "{") {
if err = filesystem.CopyFile(xrayPath, textPath); err != nil {
return err.Error()
}
return ""
}
text = checkWindowsReturn(text)
if strings.HasPrefix(text, "vless://") || strings.HasPrefix(text, "vmess://") || strings.HasPrefix(text, "socks://") || strings.HasPrefix(text, "ss://") || strings.HasPrefix(text, "trojan://") {
xray, err := parsePlainShareText(text)
if err != nil {
return err.Error()
}
err = writeXrayJson(xray, xrayPath)
if err != nil {
return err.Error()
}
} else {
xray, err := tryParse(text)
if err != nil {
return err.Error()
}
err = writeXrayJson(xray, xrayPath)
if err != nil {
return err.Error()
}
}
return ""
}
func checkWindowsReturn(text string) string {
if strings.Contains(text, "\r\n") {
text = strings.ReplaceAll(text, "\r\n", "\n")
}
return text
}
func parsePlainShareText(text string) (*xrayJson, error) {
proxies := strings.Split(text, "\n")
var xray xrayJson
var outbounds []xrayOutbound
for _, proxy := range proxies {
link, err := url.Parse(proxy)
if err == nil {
var shareLink xrayShareLink
shareLink.text = proxy
shareLink.link = link
if outbound, err := shareLink.outbound(); err == nil {
outbounds = append(outbounds, *outbound)
} else {
fmt.Println(err)
}
}
}
if len(outbounds) == 0 {
return nil, fmt.Errorf("no valid outbound found")
}
xray.Outbounds = outbounds
return &xray, nil
}
func tryParse(text string) (*xrayJson, error) {
base64Text, err := decodeBase64Text(text)
if err == nil {
cleanText := checkWindowsReturn(base64Text)
return parsePlainShareText(cleanText)
}
return tryConvertClashYaml(text)
}
func decodeBase64Text(text string) (string, error) {
content, err := base64.StdEncoding.DecodeString(text)
if err == nil {
return string(content), nil
}
if strings.Contains(text, "-") {
text = strings.ReplaceAll(text, "-", "+")
}
if strings.Contains(text, "_") {
text = strings.ReplaceAll(text, "_", "/")
}
missingPadding := len(text) % 4
if missingPadding != 0 {
padding := strings.Repeat("=", 4-missingPadding)
text += padding
}
content, err = base64.StdEncoding.DecodeString(text)
if err != nil {
return "", err
}
return string(content), nil
}
func writeXrayJson(xray *xrayJson, xrayPath string) error {
xrayBytes, err := json.Marshal(xray)
if err != nil {
return err
}
return writeBytes(xrayBytes, xrayPath)
}
type xrayShareLink struct {
text string
link *url.URL
}
func (proxy xrayShareLink) outbound() (*xrayOutbound, error) {
switch proxy.link.Scheme {
case "ss":
outbound, err := proxy.shadowsocksOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "vmess":
outbound, err := proxy.vmessOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "vless":
outbound, err := proxy.vlessOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "socks":
outbound, err := proxy.socksOutbound()
if err != nil {
return nil, err
}
return outbound, nil
case "trojan":
outbound, err := proxy.trojanOutbound()
if err != nil {
return nil, err
}
return outbound, nil
}
return nil, fmt.Errorf("unsupport link type: %s", proxy.link.Scheme)
}
func (proxy xrayShareLink) shadowsocksOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "shadowsocks"
outbound.Name = proxy.link.Fragment
var server xrayShadowsocksServer
server.Address = proxy.link.Hostname()
port, err := strconv.Atoi(proxy.link.Port())
if err != nil {
return nil, err
}
server.Port = port
user := proxy.link.User.String()
passwordText, err := decodeBase64Text(user)
if err != nil {
return nil, err
}
pwConfig := strings.Split(passwordText, ":")
if len(pwConfig) != 2 {
return nil, fmt.Errorf("unsupport link shadowsocks password: %s", passwordText)
}
server.Method = pwConfig[0]
server.Password = pwConfig[1]
var settings xrayShadowsocks
settings.Servers = []xrayShadowsocksServer{server}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
outbound.StreamSettings = proxy.streamSettings(proxy.link.Query())
return &outbound, nil
}
func (proxy xrayShareLink) vmessOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "vmess"
outbound.Name = proxy.link.Fragment
query := proxy.link.Query()
var user xrayVMessVnextUser
user.Id = proxy.link.User.String()
security := query.Get("encryption")
if len(security) > 0 {
user.Security = security
}
var vnext xrayVMessVnext
vnext.Address = proxy.link.Hostname()
port, err := strconv.Atoi(proxy.link.Port())
if err != nil {
return nil, err
}
vnext.Port = port
vnext.Users = []xrayVMessVnextUser{user}
var settings xrayVMess
settings.Vnext = []xrayVMessVnext{vnext}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
outbound.StreamSettings = proxy.streamSettings(query)
return &outbound, nil
}
func (proxy xrayShareLink) vlessOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "vless"
outbound.Name = proxy.link.Fragment
query := proxy.link.Query()
var user xrayVLESSVnextUser
user.Id = proxy.link.User.String()
flow := query.Get("flow")
if len(flow) > 0 {
user.Flow = flow
}
var vnext xrayVLESSVnext
vnext.Address = proxy.link.Hostname()
port, err := strconv.Atoi(proxy.link.Port())
if err != nil {
return nil, err
}
vnext.Port = port
vnext.Users = []xrayVLESSVnextUser{user}
var settings xrayVLESS
settings.Vnext = []xrayVLESSVnext{vnext}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
outbound.StreamSettings = proxy.streamSettings(query)
return &outbound, nil
}
func (proxy xrayShareLink) socksOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "socks"
outbound.Name = proxy.link.Fragment
userPassword := proxy.link.User.String()
passwordText, err := decodeBase64Text(userPassword)
if err != nil {
return nil, err
}
pwConfig := strings.Split(passwordText, ":")
if len(pwConfig) != 2 {
return nil, fmt.Errorf("unsupport link socks user password: %s", passwordText)
}
var user xraySocksServerUser
user.User = pwConfig[0]
user.Pass = pwConfig[1]
var server xraySocksServer
server.Address = proxy.link.Hostname()
port, err := strconv.Atoi(proxy.link.Port())
if err != nil {
return nil, err
}
server.Port = port
server.Users = []xraySocksServerUser{user}
var settings xraySocks
settings.Servers = []xraySocksServer{server}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
outbound.StreamSettings = proxy.streamSettings(proxy.link.Query())
return &outbound, nil
}
func (proxy xrayShareLink) trojanOutbound() (*xrayOutbound, error) {
var outbound xrayOutbound
outbound.Protocol = "trojan"
outbound.Name = proxy.link.Fragment
var server xrayTrojanServer
server.Address = proxy.link.Hostname()
port, err := strconv.Atoi(proxy.link.Port())
if err != nil {
return nil, err
}
server.Port = port
server.Password = proxy.link.User.String()
var settings xrayTrojan
settings.Servers = []xrayTrojanServer{server}
setttingsBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
outbound.Settings = (*json.RawMessage)(&setttingsBytes)
outbound.StreamSettings = proxy.streamSettings(proxy.link.Query())
return &outbound, nil
}
func (proxy xrayShareLink) streamSettings(query url.Values) *xrayStreamSettings {
var streamSettings xrayStreamSettings
if len(query) == 0 {
return &streamSettings
}
network := query.Get("type")
if len(network) == 0 {
streamSettings.Network = "tcp"
} else {
streamSettings.Network = network
}
switch streamSettings.Network {
case "tcp":
headerType := query.Get("headerType")
if headerType == "http" {
var request xrayTcpSettingsHeaderRequest
path := query.Get("path")
if len(path) > 0 {
request.Path = strings.Split(path, ",")
}
host := query.Get("host")
if len(host) > 0 {
var headers xrayTcpSettingsHeaderRequestHeaders
headers.Host = strings.Split(host, ",")
request.Headers = &headers
}
var header xrayTcpSettingsHeader
header.Type = headerType
header.Request = &request
var tcpSettings xrayTcpSettings
tcpSettings.Header = &header
streamSettings.TcpSettings = &tcpSettings
}
case "kcp":
var kcpSettings xrayKcpSettings
headerType := query.Get("headerType")
if len(headerType) > 0 {
var header xrayFakeHeader
header.Type = headerType
kcpSettings.Header = &header
}
seed := query.Get("seed")
kcpSettings.Seed = seed
streamSettings.KcpSettings = &kcpSettings
case "ws":
var wsSettings xrayWsSettings
path := query.Get("path")
wsSettings.Path = path
host := query.Get("host")
if len(host) > 0 {
var headers xrayWsSettingsHeaders
headers.Host = host
wsSettings.Headers = &headers
}
streamSettings.WsSettings = &wsSettings
case "grpc":
var grcpSettings xrayGrpcSettings
serviceName := query.Get("serviceName")
grcpSettings.ServiceName = serviceName
mode := query.Get("mode")
grcpSettings.MultiMode = mode == "multi"
streamSettings.GrpcSettings = &grcpSettings
case "quic":
var quicSettings xrayQuicSettings
headerType := query.Get("headerType")
if len(headerType) > 0 {
var header xrayFakeHeader
header.Type = headerType
quicSettings.Header = &header
}
quicSecurity := query.Get("quicSecurity")
quicSettings.Security = quicSecurity
key := query.Get("key")
quicSettings.Key = key
streamSettings.QuicSettings = &quicSettings
case "http":
var httpSettings xrayHttpSettings
host := query.Get("host")
httpSettings.Host = strings.Split(host, ",")
path := query.Get("path")
httpSettings.Path = path
streamSettings.HttpSettings = &httpSettings
}
proxy.parseSecurity(query, &streamSettings)
return &streamSettings
}
func (proxy xrayShareLink) parseSecurity(query url.Values, streamSettings *xrayStreamSettings) {
var tlsSettings xrayTlsSettings
var realitySettings xrayRealitySettings
fp := query.Get("fp")
tlsSettings.Fingerprint = fp
realitySettings.Fingerprint = fp
sni := query.Get("sni")
tlsSettings.ServerName = sni
realitySettings.ServerName = sni
alpn := query.Get("alpn")
if len(alpn) > 0 {
tlsSettings.Alpn = strings.Split(alpn, ",")
}
pbk := query.Get("pbk")
realitySettings.PublicKey = pbk
sid := query.Get("sid")
realitySettings.ShortId = sid
spx := query.Get("spx")
realitySettings.SpiderX = spx
security := query.Get("security")
if len(security) == 0 {
streamSettings.Security = "none"
} else {
streamSettings.Security = security
}
switch streamSettings.Security {
case "tls":
streamSettings.TlsSettings = &tlsSettings
case "reality":
streamSettings.RealitySettings = &realitySettings
}
}

13
uuid.go Normal file
View File

@@ -0,0 +1,13 @@
package libxray
import (
"github.com/xtls/xray-core/common/uuid"
)
func CustomUUID(str string) string {
id, err := uuid.ParseString(str)
if err != nil {
return err.Error()
}
return id.String()
}

71
xray.go Normal file
View File

@@ -0,0 +1,71 @@
package libxray
import (
"os"
"runtime/debug"
"github.com/xtls/xray-core/common/cmdarg"
"github.com/xtls/xray-core/core"
_ "github.com/xtls/xray-core/main/distro/all"
)
var (
coreServer *core.Instance
)
func startXray(configFile string) (*core.Instance, error) {
file := cmdarg.Arg{configFile}
config, err := core.LoadConfig("json", file)
if err != nil {
return nil, err
}
server, err := core.New(config)
if err != nil {
return nil, err
}
return server, nil
}
func initEnv(datDir string) {
os.Setenv("xray.location.asset", datDir)
}
func setMaxMemory(maxMemory int64) {
os.Setenv("XRAY_MEMORY_FORCEFREE", "1")
initForceFree(maxMemory)
}
func RunXray(datDir string, config string, maxMemory int64) string {
initEnv(datDir)
if maxMemory > 0 {
setMaxMemory(maxMemory)
}
coreServer, err := startXray(config)
if err != nil {
return err.Error()
}
if err := coreServer.Start(); err != nil {
return err.Error()
}
debug.FreeOSMemory()
return ""
}
func StopXray() string {
if coreServer != nil {
err := coreServer.Close()
coreServer = nil
if err != nil {
return err.Error()
}
}
return ""
}
func XrayVersion() string {
return core.Version()
}

162
xray_json.go Normal file
View File

@@ -0,0 +1,162 @@
package libxray
import (
"encoding/json"
)
type xrayJson struct {
Outbounds []xrayOutbound `json:"outbounds,omitempty"`
}
type xrayOutbound struct {
Name string `json:"name,omitempty"`
Protocol string `json:"protocol,omitempty"`
Settings *json.RawMessage `json:"settings,omitempty"`
StreamSettings *xrayStreamSettings `json:"streamSettings,omitempty"`
}
type xrayShadowsocks struct {
Servers []xrayShadowsocksServer `json:"servers,omitempty"`
}
type xrayShadowsocksServer struct {
Address string `json:"address,omitempty"`
Port int `json:"port,omitempty"`
Method string `json:"method,omitempty"`
Password string `json:"password,omitempty"`
}
type xraySocks struct {
Servers []xraySocksServer `json:"servers,omitempty"`
}
type xraySocksServer struct {
Address string `json:"address,omitempty"`
Port int `json:"port,omitempty"`
Users []xraySocksServerUser `json:"users,omitempty"`
}
type xraySocksServerUser struct {
User string `json:"user,omitempty"`
Pass string `json:"pass,omitempty"`
}
type xrayTrojan struct {
Servers []xrayTrojanServer `json:"servers,omitempty"`
}
type xrayTrojanServer struct {
Address string `json:"address,omitempty"`
Port int `json:"port,omitempty"`
Password string `json:"password,omitempty"`
}
type xrayVLESS struct {
Vnext []xrayVLESSVnext `json:"vnext,omitempty"`
}
type xrayVLESSVnext struct {
Address string `json:"address,omitempty"`
Port int `json:"port,omitempty"`
Users []xrayVLESSVnextUser `json:"users,omitempty"`
}
type xrayVLESSVnextUser struct {
Id string `json:"id,omitempty"`
Flow string `json:"flow,omitempty"`
}
type xrayVMess struct {
Vnext []xrayVMessVnext `json:"vnext,omitempty"`
}
type xrayVMessVnext struct {
Address string `json:"address,omitempty"`
Port int `json:"port,omitempty"`
Users []xrayVMessVnextUser `json:"users,omitempty"`
}
type xrayVMessVnextUser struct {
Id string `json:"id,omitempty"`
Security string `json:"security,omitempty"`
}
type xrayStreamSettings struct {
Network string `json:"network,omitempty"`
Security string `json:"security,omitempty"`
TlsSettings *xrayTlsSettings `json:"tlsSettings,omitempty"`
RealitySettings *xrayRealitySettings `json:"realitySettings,omitempty"`
TcpSettings *xrayTcpSettings `json:"tcpSettings,omitempty"`
KcpSettings *xrayKcpSettings `json:"kcpSettings,omitempty"`
WsSettings *xrayWsSettings `json:"wsSettings,omitempty"`
HttpSettings *xrayHttpSettings `json:"httpSettings,omitempty"`
QuicSettings *xrayQuicSettings `json:"quicSettings,omitempty"`
GrpcSettings *xrayGrpcSettings `json:"grpcSettings,omitempty"`
}
type xrayTlsSettings struct {
ServerName string `json:"serverName,omitempty"`
AllowInsecure bool `json:"allowInsecure,omitempty"`
Alpn []string `json:"alpn,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
}
type xrayRealitySettings struct {
Fingerprint string `json:"fingerprint,omitempty"`
ServerName string `json:"serverName,omitempty"`
PublicKey string `json:"publicKey,omitempty"`
ShortId string `json:"shortId,omitempty"`
SpiderX string `json:"spiderX,omitempty"`
}
type xrayTcpSettings struct {
Header *xrayTcpSettingsHeader `json:"header,omitempty"`
}
type xrayTcpSettingsHeader struct {
Type string `json:"type,omitempty"`
Request *xrayTcpSettingsHeaderRequest `json:"request,omitempty"`
}
type xrayTcpSettingsHeaderRequest struct {
Path []string `json:"path,omitempty"`
Headers *xrayTcpSettingsHeaderRequestHeaders `json:"headers,omitempty"`
}
type xrayTcpSettingsHeaderRequestHeaders struct {
Host []string `json:"Host,omitempty"`
}
type xrayFakeHeader struct {
Type string `json:"type,omitempty"`
}
type xrayKcpSettings struct {
Header *xrayFakeHeader `json:"header,omitempty"`
Seed string `json:"seed,omitempty"`
}
type xrayWsSettings struct {
Path string `json:"path,omitempty"`
Headers *xrayWsSettingsHeaders `json:"headers,omitempty"`
}
type xrayWsSettingsHeaders struct {
Host string `json:"Host,omitempty"`
}
type xrayHttpSettings struct {
Host []string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
}
type xrayQuicSettings struct {
Security string `json:"security,omitempty"`
Key string `json:"key,omitempty"`
Header *xrayFakeHeader `json:"header,omitempty"`
}
type xrayGrpcSettings struct {
ServiceName string `json:"serviceName,omitempty"`
MultiMode bool `json:"multiMode,omitempty"`
}