Split delay/stats helpers into utils file
Moves outbound traffic and delay-related helper methods out of `libv2ray_main.go` into a new `libv2ray_utils.go` to keep core lifecycle code focused. The refactor also trims now-unused imports in the main file, removes the old `QueryStats` helper, and bumps `libVersion` from 39 to 40.
This commit is contained in:
1 parent
b21389865e
commit
fd8b3ce164
2 files changed
+177
-185
No files matched your search
+1
-185
@@ -1,25 +1,18 @@
|
|||||||
package libv2ray
|
package libv2ray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
coreapplog "github.com/xtls/xray-core/app/log"
|
coreapplog "github.com/xtls/xray-core/app/log"
|
||||||
corecommlog "github.com/xtls/xray-core/common/log"
|
corecommlog "github.com/xtls/xray-core/common/log"
|
||||||
corenet "github.com/xtls/xray-core/common/net"
|
|
||||||
corefilesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
corefilesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
corestats "github.com/xtls/xray-core/features/stats"
|
corestats "github.com/xtls/xray-core/features/stats"
|
||||||
coreserial "github.com/xtls/xray-core/infra/conf/serial"
|
coreserial "github.com/xtls/xray-core/infra/conf/serial"
|
||||||
@@ -35,7 +28,7 @@ const (
|
|||||||
xudpBaseKey = "xray.xudp.basekey"
|
xudpBaseKey = "xray.xudp.basekey"
|
||||||
tunFdKey = "xray.tun.fd"
|
tunFdKey = "xray.tun.fd"
|
||||||
browserDialerAddress = "xray.browser.dialer"
|
browserDialerAddress = "xray.browser.dialer"
|
||||||
libVersion = 39 // Library version, update here only
|
libVersion = 40 // Library version, update here only
|
||||||
)
|
)
|
||||||
|
|
||||||
// CoreController represents a controller for managing Xray core instance lifecycle
|
// CoreController represents a controller for managing Xray core instance lifecycle
|
||||||
@@ -141,95 +134,6 @@ func (x *CoreController) StopLoop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryStats retrieves and resets traffic statistics for a specific outbound tag and direction
|
|
||||||
// Returns the accumulated traffic value and resets the counter to zero
|
|
||||||
// Returns 0 if the stats manager is not initialized or the counter doesn't exist
|
|
||||||
func (x *CoreController) QueryStats(tag string, direct string) int64 {
|
|
||||||
if x.statsManager == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
counter := x.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
|
|
||||||
if counter == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return counter.Set(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryAllOutboundTrafficStats retrieves and resets all outbound traffic counters.
|
|
||||||
// Returns a single-line text in format: tag,direction,value;tag,direction,value;
|
|
||||||
// Returns an empty string if the stats manager is not initialized or no counters exist.
|
|
||||||
func (x *CoreController) QueryAllOutboundTrafficStats() string {
|
|
||||||
if x.statsManager == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var b strings.Builder
|
|
||||||
|
|
||||||
x.statsManager.VisitCounters(func(name string, counter corestats.Counter) bool {
|
|
||||||
parts := strings.Split(name, ">>>")
|
|
||||||
if len(parts) != 4 || parts[0] != "outbound" || parts[2] != "traffic" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tag := parts[1]
|
|
||||||
direct := parts[3]
|
|
||||||
value := counter.Set(0)
|
|
||||||
if value <= 0 {
|
|
||||||
return true // Skip counters with non-positive values
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString(tag)
|
|
||||||
b.WriteByte(',')
|
|
||||||
b.WriteString(direct)
|
|
||||||
b.WriteByte(',')
|
|
||||||
b.WriteString(strconv.FormatInt(value, 10))
|
|
||||||
b.WriteByte(';')
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureDelay measures network latency to a specified URL through the current core instance
|
|
||||||
// Uses a 12-second timeout context and returns the round-trip time in milliseconds
|
|
||||||
// An error is returned if the connection fails or returns an unexpected status
|
|
||||||
func (x *CoreController) MeasureDelay(url string) (int64, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return measureInstDelay(ctx, x.coreInstance, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureOutboundDelay measures the outbound delay for a given configuration and URL
|
|
||||||
func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) {
|
|
||||||
config, err := coreserial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
|
||||||
if err != nil {
|
|
||||||
return -1, fmt.Errorf("config load error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simplify config for testing
|
|
||||||
config.Inbound = nil
|
|
||||||
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
|
|
||||||
|
|
||||||
inst, err := core.New(config)
|
|
||||||
if err != nil {
|
|
||||||
return -1, fmt.Errorf("instance creation failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := inst.Start(); err != nil {
|
|
||||||
return -1, fmt.Errorf("startup failed: %w", err)
|
|
||||||
}
|
|
||||||
defer inst.Close()
|
|
||||||
return measureInstDelay(context.Background(), inst, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckVersionX returns the library and Xray versions
|
// CheckVersionX returns the library and Xray versions
|
||||||
func CheckVersionX() string {
|
func CheckVersionX() string {
|
||||||
return fmt.Sprintf("Lib v%d, Xray-core v%s", libVersion, core.Version())
|
return fmt.Sprintf("Lib v%d, Xray-core v%s", libVersion, core.Version())
|
||||||
@@ -282,94 +186,6 @@ func (x *CoreController) doStartLoop(configContent string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// measureInstDelay measures the delay for an instance to a given URL
|
|
||||||
func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int64, error) {
|
|
||||||
if inst == nil {
|
|
||||||
return -1, errors.New("core instance is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if url == "" {
|
|
||||||
url = "https://www.google.com/generate_204"
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSHandshakeTimeout: 6 * time.Second,
|
|
||||||
DisableKeepAlives: false,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
dest, err := corenet.ParseDestination(fmt.Sprintf("%s:%s", network, addr))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return core.Dial(ctx, inst, dest)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: tr,
|
|
||||||
Timeout: 12 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
var minDuration int64 = -1
|
|
||||||
success := false
|
|
||||||
var lastErr error
|
|
||||||
|
|
||||||
// Close idle connections to ensure the temporary instance can be closed safely
|
|
||||||
defer tr.CloseIdleConnections()
|
|
||||||
|
|
||||||
// Add exception handling and increase retry attempts
|
|
||||||
const attempts = 2
|
|
||||||
for i := 0; i < attempts; i++ {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Return immediately when context is canceled
|
|
||||||
if !success {
|
|
||||||
return -1, ctx.Err()
|
|
||||||
}
|
|
||||||
return minDuration, nil
|
|
||||||
default:
|
|
||||||
// Continue execution
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = fmt.Errorf("failed to create HTTP request: %w", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read and close body immediately to allow connection reuse for the next attempt
|
|
||||||
_, err = io.Copy(io.Discard, resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
|
||||||
lastErr = fmt.Errorf("invalid status: %s", resp.Status)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
lastErr = fmt.Errorf("failed to read response body: %w", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
duration := time.Since(start).Milliseconds()
|
|
||||||
if !success || duration < minDuration {
|
|
||||||
minDuration = duration
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
return -1, lastErr
|
|
||||||
}
|
|
||||||
return minDuration, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log writer implementation
|
// Log writer implementation
|
||||||
func (w *consoleLogWriter) Write(s string) error {
|
func (w *consoleLogWriter) Write(s string) error {
|
||||||
w.logger.Print(s)
|
w.logger.Print(s)
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package libv2ray
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
corenet "github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
core "github.com/xtls/xray-core/core"
|
||||||
|
corestats "github.com/xtls/xray-core/features/stats"
|
||||||
|
coreserial "github.com/xtls/xray-core/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryAllOutboundTrafficStats retrieves and resets all outbound traffic counters.
|
||||||
|
// Returns a single-line text in format: tag,direction,value;tag,direction,value;
|
||||||
|
// Returns an empty string if the stats manager is not initialized or no counters exist.
|
||||||
|
func (x *CoreController) QueryAllOutboundTrafficStats() string {
|
||||||
|
if x.statsManager == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
x.statsManager.VisitCounters(func(name string, counter corestats.Counter) bool {
|
||||||
|
parts := strings.Split(name, ">>>")
|
||||||
|
if len(parts) != 4 || parts[0] != "outbound" || parts[2] != "traffic" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := parts[1]
|
||||||
|
direct := parts[3]
|
||||||
|
value := counter.Set(0)
|
||||||
|
if value <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(tag)
|
||||||
|
b.WriteByte(',')
|
||||||
|
b.WriteString(direct)
|
||||||
|
b.WriteByte(',')
|
||||||
|
b.WriteString(strconv.FormatInt(value, 10))
|
||||||
|
b.WriteByte(';')
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureDelay measures network latency to a specified URL through the current core instance
|
||||||
|
// Uses a 12-second timeout context and returns the round-trip time in milliseconds
|
||||||
|
// An error is returned if the connection fails or returns an unexpected status
|
||||||
|
func (x *CoreController) MeasureDelay(url string) (int64, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return measureInstDelay(ctx, x.coreInstance, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureOutboundDelay measures the outbound delay for a given configuration and URL
|
||||||
|
func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) {
|
||||||
|
config, err := coreserial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("config load error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Inbound = nil
|
||||||
|
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
|
||||||
|
|
||||||
|
inst, err := core.New(config)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("instance creation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := inst.Start(); err != nil {
|
||||||
|
return -1, fmt.Errorf("startup failed: %w", err)
|
||||||
|
}
|
||||||
|
defer inst.Close()
|
||||||
|
return measureInstDelay(context.Background(), inst, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureInstDelay measures the delay for an instance to a given URL
|
||||||
|
func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int64, error) {
|
||||||
|
if inst == nil {
|
||||||
|
return -1, errors.New("core instance is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if url == "" {
|
||||||
|
url = "https://www.google.com/generate_204"
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSHandshakeTimeout: 6 * time.Second,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
dest, err := corenet.ParseDestination(fmt.Sprintf("%s:%s", network, addr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return core.Dial(ctx, inst, dest)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: 12 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
var minDuration int64 = -1
|
||||||
|
success := false
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
defer tr.CloseIdleConnections()
|
||||||
|
|
||||||
|
const attempts = 2
|
||||||
|
for i := 0; i < attempts; i++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if !success {
|
||||||
|
return -1, ctx.Err()
|
||||||
|
}
|
||||||
|
return minDuration, nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = fmt.Errorf("failed to create HTTP request: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(io.Discard, resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
||||||
|
lastErr = fmt.Errorf("invalid status: %s", resp.Status)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
lastErr = fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := time.Since(start).Milliseconds()
|
||||||
|
if !success || duration < minDuration {
|
||||||
|
minDuration = duration
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
return -1, lastErr
|
||||||
|
}
|
||||||
|
return minDuration, nil
|
||||||
|
}
|
||||||
Reference in new issue
Block a user