2020-11-26 13:35:52 +08:00
|
|
|
package libv2ray
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2025-01-07 13:45:25 +08:00
|
|
|
mobasset "golang.org/x/mobile/asset"
|
2020-11-26 13:35:52 +08:00
|
|
|
|
2020-12-04 11:10:47 +08:00
|
|
|
v2net "github.com/xtls/xray-core/common/net"
|
|
|
|
v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
2024-12-29 01:11:08 -05:00
|
|
|
"github.com/xtls/xray-core/common/serial"
|
2024-05-12 11:15:04 +08:00
|
|
|
v2core "github.com/xtls/xray-core/core"
|
2020-12-04 11:10:47 +08:00
|
|
|
v2stats "github.com/xtls/xray-core/features/stats"
|
|
|
|
v2serial "github.com/xtls/xray-core/infra/conf/serial"
|
|
|
|
_ "github.com/xtls/xray-core/main/distro/all"
|
|
|
|
v2internet "github.com/xtls/xray-core/transport/internet"
|
2020-11-26 13:35:52 +08:00
|
|
|
|
2020-12-04 11:10:47 +08:00
|
|
|
v2applog "github.com/xtls/xray-core/app/log"
|
|
|
|
v2commlog "github.com/xtls/xray-core/common/log"
|
2020-11-26 13:35:52 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2024-05-12 11:15:04 +08:00
|
|
|
v2Asset = "xray.location.asset"
|
2025-03-26 15:34:16 +03:30
|
|
|
v2Cert = "xray.location.cert"
|
2023-12-03 14:28:36 -05:00
|
|
|
xudpBaseKey = "xray.xudp.basekey"
|
2020-11-26 13:35:52 +08:00
|
|
|
)
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
/*
|
|
|
|
V2RayPoint V2Ray Point Server
|
2020-11-26 13:35:52 +08:00
|
|
|
This is territory of Go, so no getter and setters!
|
|
|
|
*/
|
|
|
|
type V2RayPoint struct {
|
|
|
|
SupportSet V2RayVPNServiceSupportsSet
|
|
|
|
statsManager v2stats.Manager
|
|
|
|
|
2022-06-26 20:18:07 +08:00
|
|
|
dialer *ProtectedDialer
|
2020-11-26 13:35:52 +08:00
|
|
|
v2rayOP sync.Mutex
|
|
|
|
closeChan chan struct{}
|
|
|
|
|
|
|
|
Vpoint *v2core.Instance
|
|
|
|
IsRunning bool
|
|
|
|
|
|
|
|
DomainName string
|
|
|
|
ConfigureFileContent string
|
|
|
|
AsyncResolve bool
|
|
|
|
}
|
|
|
|
|
|
|
|
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
|
|
|
type V2RayVPNServiceSupportsSet interface {
|
|
|
|
Setup(Conf string) int
|
|
|
|
Prepare() int
|
|
|
|
Shutdown() int
|
|
|
|
Protect(int) bool
|
|
|
|
OnEmitStatus(int, string) int
|
|
|
|
}
|
|
|
|
|
|
|
|
/*RunLoop Run V2Ray main loop
|
|
|
|
*/
|
|
|
|
func (v *V2RayPoint) RunLoop(prefIPv6 bool) (err error) {
|
|
|
|
v.v2rayOP.Lock()
|
|
|
|
defer v.v2rayOP.Unlock()
|
|
|
|
|
2025-03-27 14:54:48 +03:30
|
|
|
if v.IsRunning {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
v.closeChan = make(chan struct{})
|
|
|
|
v.dialer.PrepareResolveChan()
|
|
|
|
|
|
|
|
go v.handleResolve()
|
|
|
|
|
|
|
|
prepareDomain := func() {
|
|
|
|
v.dialer.PrepareDomain(v.DomainName, v.closeChan, prefIPv6)
|
|
|
|
close(v.dialer.ResolveChan())
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
2025-03-27 14:54:48 +03:30
|
|
|
|
|
|
|
if v.AsyncResolve {
|
|
|
|
go prepareDomain()
|
|
|
|
} else {
|
|
|
|
prepareDomain()
|
|
|
|
}
|
|
|
|
|
|
|
|
err = v.pointloop()
|
2020-11-26 13:35:52 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-03-27 14:54:48 +03:30
|
|
|
func (v *V2RayPoint) handleResolve() {
|
|
|
|
select {
|
|
|
|
case <-v.dialer.ResolveChan():
|
|
|
|
if !v.dialer.IsVServerReady() {
|
|
|
|
log.Println("vServer cannot resolved, shutdown")
|
|
|
|
v.StopLoop()
|
|
|
|
v.SupportSet.Shutdown()
|
|
|
|
}
|
|
|
|
case <-v.closeChan:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-26 13:35:52 +08:00
|
|
|
/*StopLoop Stop V2Ray main loop
|
|
|
|
*/
|
|
|
|
func (v *V2RayPoint) StopLoop() (err error) {
|
|
|
|
v.v2rayOP.Lock()
|
|
|
|
defer v.v2rayOP.Unlock()
|
|
|
|
if v.IsRunning {
|
|
|
|
close(v.closeChan)
|
|
|
|
v.shutdownInit()
|
|
|
|
v.SupportSet.OnEmitStatus(0, "Closed")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-03-27 14:54:48 +03:30
|
|
|
// Delegate Function
|
2020-11-26 13:35:52 +08:00
|
|
|
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
|
|
|
if v.statsManager == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
counter := v.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
|
|
|
|
if counter == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return counter.Set(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *V2RayPoint) shutdownInit() {
|
|
|
|
v.IsRunning = false
|
|
|
|
v.Vpoint.Close()
|
|
|
|
v.Vpoint = nil
|
|
|
|
v.statsManager = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *V2RayPoint) pointloop() error {
|
2020-11-27 11:26:32 +08:00
|
|
|
log.Println("loading core config")
|
2020-11-26 13:35:52 +08:00
|
|
|
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-27 11:26:32 +08:00
|
|
|
log.Println("new core")
|
2020-11-26 13:35:52 +08:00
|
|
|
v.Vpoint, err = v2core.New(config)
|
|
|
|
if err != nil {
|
|
|
|
v.Vpoint = nil
|
|
|
|
log.Println(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
v.statsManager = v.Vpoint.GetFeature(v2stats.ManagerType()).(v2stats.Manager)
|
|
|
|
|
2020-11-27 11:26:32 +08:00
|
|
|
log.Println("start core")
|
2020-11-26 13:35:52 +08:00
|
|
|
v.IsRunning = true
|
|
|
|
if err := v.Vpoint.Start(); err != nil {
|
|
|
|
v.IsRunning = false
|
|
|
|
log.Println(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
v.SupportSet.Prepare()
|
|
|
|
v.SupportSet.Setup("")
|
|
|
|
v.SupportSet.OnEmitStatus(0, "Running")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
func (v *V2RayPoint) MeasureDelay(url string) (int64, error) {
|
2020-11-26 13:35:52 +08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-v.closeChan:
|
2025-03-27 14:54:48 +03:30
|
|
|
// cancel request if close called during measure
|
2020-11-26 13:35:52 +08:00
|
|
|
cancel()
|
|
|
|
case <-ctx.Done():
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
return measureInstDelay(ctx, v.Vpoint, url)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// InitV2Env set v2 asset path
|
2023-12-03 14:28:36 -05:00
|
|
|
func InitV2Env(envPath string, key string) {
|
2020-11-26 13:35:52 +08:00
|
|
|
//Initialize asset API, Since Raymond Will not let notify the asset location inside Process,
|
|
|
|
//We need to set location outside V2Ray
|
|
|
|
if len(envPath) > 0 {
|
|
|
|
os.Setenv(v2Asset, envPath)
|
2025-03-26 15:34:16 +03:30
|
|
|
os.Setenv(v2Cert, envPath)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
2023-12-03 14:28:36 -05:00
|
|
|
if len(key) > 0 {
|
|
|
|
os.Setenv(xudpBaseKey, key)
|
|
|
|
}
|
2020-11-26 13:35:52 +08:00
|
|
|
|
|
|
|
//Now we handle read, fallback to gomobile asset (apk assets)
|
|
|
|
v2filesystem.NewFileReader = func(path string) (io.ReadCloser, error) {
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
|
|
_, file := filepath.Split(path)
|
|
|
|
return mobasset.Open(file)
|
|
|
|
}
|
|
|
|
return os.Open(path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) {
|
2020-11-26 13:35:52 +08:00
|
|
|
config, err := v2serial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
|
2025-03-27 14:54:48 +03:30
|
|
|
// don't listen to anything for test purpose
|
2020-11-26 13:35:52 +08:00
|
|
|
config.Inbound = nil
|
2022-05-01 18:34:35 -04:00
|
|
|
// config.App: (fakedns), log, dispatcher, InboundConfig, OutboundConfig, (stats), router, dns, (policy)
|
2020-11-26 13:35:52 +08:00
|
|
|
// keep only basic features
|
2024-12-29 01:11:08 -05:00
|
|
|
var essentialApp []*serial.TypedMessage
|
|
|
|
for _, app := range config.App {
|
|
|
|
if app.Type == "xray.app.proxyman.OutboundConfig" || app.Type == "xray.app.dispatcher.Config" || app.Type == "xray.app.log.Config" {
|
|
|
|
essentialApp = append(essentialApp, app)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
config.App = essentialApp
|
2020-11-26 13:35:52 +08:00
|
|
|
|
|
|
|
inst, err := v2core.New(config)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
|
|
|
|
inst.Start()
|
2024-05-12 11:15:04 +08:00
|
|
|
delay, err := measureInstDelay(context.Background(), inst, url)
|
2020-11-26 13:35:52 +08:00
|
|
|
inst.Close()
|
|
|
|
return delay, err
|
|
|
|
}
|
|
|
|
|
|
|
|
/*NewV2RayPoint new V2RayPoint*/
|
|
|
|
func NewV2RayPoint(s V2RayVPNServiceSupportsSet, adns bool) *V2RayPoint {
|
|
|
|
// inject our own log writer
|
|
|
|
v2applog.RegisterHandlerCreator(v2applog.LogType_Console,
|
|
|
|
func(lt v2applog.LogType,
|
|
|
|
options v2applog.HandlerCreatorOptions) (v2commlog.Handler, error) {
|
|
|
|
return v2commlog.NewLogger(createStdoutLogWriter()), nil
|
|
|
|
})
|
|
|
|
|
2025-03-27 14:54:48 +03:30
|
|
|
dialer := NewProtectedDialer(s)
|
2020-11-26 13:35:52 +08:00
|
|
|
v2internet.UseAlternativeSystemDialer(dialer)
|
|
|
|
return &V2RayPoint{
|
|
|
|
SupportSet: s,
|
|
|
|
dialer: dialer,
|
|
|
|
AsyncResolve: adns,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
/*
|
|
|
|
CheckVersionX string
|
2020-11-26 13:35:52 +08:00
|
|
|
This func will return libv2ray binding version and V2Ray version used.
|
|
|
|
*/
|
|
|
|
func CheckVersionX() string {
|
2024-12-28 14:57:59 +03:30
|
|
|
var version = 30
|
2022-06-26 20:18:07 +08:00
|
|
|
return fmt.Sprintf("Lib v%d, Xray-core v%s", version, v2core.Version())
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
func measureInstDelay(ctx context.Context, inst *v2core.Instance, url string) (int64, error) {
|
2020-11-26 13:35:52 +08:00
|
|
|
if inst == nil {
|
|
|
|
return -1, errors.New("core instance nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
tr := &http.Transport{
|
|
|
|
TLSHandshakeTimeout: 6 * time.Second,
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
dest, err := v2net.ParseDestination(fmt.Sprintf("%s:%s", network, addr))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return v2core.Dial(ctx, inst, dest)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &http.Client{
|
|
|
|
Transport: tr,
|
|
|
|
Timeout: 12 * time.Second,
|
|
|
|
}
|
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
if len(url) <= 0 {
|
|
|
|
url = "https://www.google.com/generate_204"
|
|
|
|
}
|
|
|
|
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
2020-11-26 13:35:52 +08:00
|
|
|
start := time.Now()
|
|
|
|
resp, err := c.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
2024-05-12 11:15:04 +08:00
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
|
|
|
return -1, fmt.Errorf("status != 20x: %s", resp.Status)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
resp.Body.Close()
|
|
|
|
return time.Since(start).Milliseconds(), nil
|
|
|
|
}
|
2022-06-26 20:18:07 +08:00
|
|
|
|
2025-03-27 14:54:48 +03:30
|
|
|
// This struct creates our own log writer without datetime stamp
|
2022-06-26 20:18:07 +08:00
|
|
|
// As Android adds time stamps on each line
|
|
|
|
type consoleLogWriter struct {
|
|
|
|
logger *log.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *consoleLogWriter) Write(s string) error {
|
|
|
|
w.logger.Print(s)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *consoleLogWriter) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This logger won't print data/time stamps
|
|
|
|
func createStdoutLogWriter() v2commlog.WriterCreator {
|
|
|
|
return func() v2commlog.Writer {
|
|
|
|
return &consoleLogWriter{
|
|
|
|
logger: log.New(os.Stdout, "", 0)}
|
|
|
|
}
|
2024-05-12 11:15:04 +08:00
|
|
|
}
|