1 parent
acb16186b2
commit
6f650d49f3
4 files changed
+127
-154
No files matched your search
+15
-1
@@ -3,7 +3,21 @@ gradle/build.gradle
|
|||||||
*.pb.go
|
*.pb.go
|
||||||
binary*.go
|
binary*.go
|
||||||
conf/demo/
|
conf/demo/
|
||||||
|
|
||||||
demo/
|
demo/
|
||||||
assets/
|
assets/
|
||||||
libv2ray*.[a|j]ar
|
libv2ray*.[a|j]ar
|
||||||
|
|
||||||
|
# Ignore binary files
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Ignore backup and temporary files
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Ignore build and log directories
|
||||||
|
build/
|
||||||
|
logs/
|
||||||
+44
-54
@@ -35,10 +35,7 @@ const (
|
|||||||
xudpBaseKey = "xray.xudp.basekey"
|
xudpBaseKey = "xray.xudp.basekey"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
// V2RayPoint represents a V2Ray Point Server
|
||||||
V2RayPoint V2Ray Point Server
|
|
||||||
This is territory of Go, so no getter and setters!
|
|
||||||
*/
|
|
||||||
type V2RayPoint struct {
|
type V2RayPoint struct {
|
||||||
SupportSet V2RayVPNServiceSupportsSet
|
SupportSet V2RayVPNServiceSupportsSet
|
||||||
statsManager v2stats.Manager
|
statsManager v2stats.Manager
|
||||||
@@ -55,7 +52,7 @@ type V2RayPoint struct {
|
|||||||
AsyncResolve bool
|
AsyncResolve bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
// V2RayVPNServiceSupportsSet is an interface to support Android VPN mode
|
||||||
type V2RayVPNServiceSupportsSet interface {
|
type V2RayVPNServiceSupportsSet interface {
|
||||||
Setup(Conf string) int
|
Setup(Conf string) int
|
||||||
Prepare() int
|
Prepare() int
|
||||||
@@ -64,14 +61,13 @@ type V2RayVPNServiceSupportsSet interface {
|
|||||||
OnEmitStatus(int, string) int
|
OnEmitStatus(int, string) int
|
||||||
}
|
}
|
||||||
|
|
||||||
/*RunLoop Run V2Ray main loop
|
// RunLoop runs the V2Ray main loop
|
||||||
*/
|
|
||||||
func (v *V2RayPoint) RunLoop(prefIPv6 bool) (err error) {
|
func (v *V2RayPoint) RunLoop(prefIPv6 bool) (err error) {
|
||||||
v.v2rayOP.Lock()
|
v.v2rayOP.Lock()
|
||||||
defer v.v2rayOP.Unlock()
|
defer v.v2rayOP.Unlock()
|
||||||
|
|
||||||
if v.IsRunning {
|
if v.IsRunning {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v.closeChan = make(chan struct{})
|
v.closeChan = make(chan struct{})
|
||||||
@@ -94,11 +90,12 @@ func (v *V2RayPoint) RunLoop(prefIPv6 bool) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleResolve handles the resolution process for domains
|
||||||
func (v *V2RayPoint) handleResolve() {
|
func (v *V2RayPoint) handleResolve() {
|
||||||
select {
|
select {
|
||||||
case <-v.dialer.ResolveChan():
|
case <-v.dialer.ResolveChan():
|
||||||
if !v.dialer.IsVServerReady() {
|
if !v.dialer.IsVServerReady() {
|
||||||
log.Println("vServer cannot resolved, shutdown")
|
log.Println("vServer cannot resolve, shutting down")
|
||||||
v.StopLoop()
|
v.StopLoop()
|
||||||
v.SupportSet.Shutdown()
|
v.SupportSet.Shutdown()
|
||||||
}
|
}
|
||||||
@@ -106,20 +103,20 @@ func (v *V2RayPoint) handleResolve() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*StopLoop Stop V2Ray main loop
|
// StopLoop stops the V2Ray main loop
|
||||||
*/
|
func (v *V2RayPoint) StopLoop() error {
|
||||||
func (v *V2RayPoint) StopLoop() (err error) {
|
|
||||||
v.v2rayOP.Lock()
|
v.v2rayOP.Lock()
|
||||||
defer v.v2rayOP.Unlock()
|
defer v.v2rayOP.Unlock()
|
||||||
|
|
||||||
if v.IsRunning {
|
if v.IsRunning {
|
||||||
close(v.closeChan)
|
close(v.closeChan)
|
||||||
v.shutdownInit()
|
v.shutdownInit()
|
||||||
v.SupportSet.OnEmitStatus(0, "Closed")
|
v.SupportSet.OnEmitStatus(0, "Closed")
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delegate Function
|
// QueryStats returns the traffic stats for a given tag and direction
|
||||||
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
||||||
if v.statsManager == nil {
|
if v.statsManager == nil {
|
||||||
return 0
|
return 0
|
||||||
@@ -131,36 +128,36 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
|||||||
return counter.Set(0)
|
return counter.Set(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shutdownInit shuts down the V2Ray instance and cleans up resources
|
||||||
func (v *V2RayPoint) shutdownInit() {
|
func (v *V2RayPoint) shutdownInit() {
|
||||||
|
if v.Vpoint != nil {
|
||||||
|
v.Vpoint.Close()
|
||||||
|
v.Vpoint = nil
|
||||||
|
}
|
||||||
v.IsRunning = false
|
v.IsRunning = false
|
||||||
v.Vpoint.Close()
|
|
||||||
v.Vpoint = nil
|
|
||||||
v.statsManager = nil
|
v.statsManager = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pointloop sets up and starts the V2Ray core
|
||||||
func (v *V2RayPoint) pointloop() error {
|
func (v *V2RayPoint) pointloop() error {
|
||||||
log.Println("loading core config")
|
log.Println("Loading core config")
|
||||||
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
return fmt.Errorf("failed to load core config: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("new core")
|
log.Println("Creating new core instance")
|
||||||
v.Vpoint, err = v2core.New(config)
|
v.Vpoint, err = v2core.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.Vpoint = nil
|
return fmt.Errorf("failed to create core instance: %w", err)
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
v.statsManager = v.Vpoint.GetFeature(v2stats.ManagerType()).(v2stats.Manager)
|
v.statsManager = v.Vpoint.GetFeature(v2stats.ManagerType()).(v2stats.Manager)
|
||||||
|
|
||||||
log.Println("start core")
|
log.Println("Starting core")
|
||||||
v.IsRunning = true
|
v.IsRunning = true
|
||||||
if err := v.Vpoint.Start(); err != nil {
|
if err := v.Vpoint.Start(); err != nil {
|
||||||
v.IsRunning = false
|
v.IsRunning = false
|
||||||
log.Println(err)
|
return fmt.Errorf("failed to start core: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v.SupportSet.Prepare()
|
v.SupportSet.Prepare()
|
||||||
@@ -169,13 +166,14 @@ func (v *V2RayPoint) pointloop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MeasureDelay measures the delay to a given URL
|
||||||
func (v *V2RayPoint) MeasureDelay(url string) (int64, error) {
|
func (v *V2RayPoint) MeasureDelay(url string) (int64, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-v.closeChan:
|
case <-v.closeChan:
|
||||||
// cancel request if close called during measure
|
|
||||||
cancel()
|
cancel()
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
@@ -184,10 +182,8 @@ func (v *V2RayPoint) MeasureDelay(url string) (int64, error) {
|
|||||||
return measureInstDelay(ctx, v.Vpoint, url)
|
return measureInstDelay(ctx, v.Vpoint, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitV2Env set v2 asset path
|
// InitV2Env sets the V2Ray asset path
|
||||||
func InitV2Env(envPath string, key string) {
|
func InitV2Env(envPath string, key string) {
|
||||||
//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 {
|
if len(envPath) > 0 {
|
||||||
os.Setenv(v2Asset, envPath)
|
os.Setenv(v2Asset, envPath)
|
||||||
os.Setenv(v2Cert, envPath)
|
os.Setenv(v2Cert, envPath)
|
||||||
@@ -196,7 +192,6 @@ func InitV2Env(envPath string, key string) {
|
|||||||
os.Setenv(xudpBaseKey, key)
|
os.Setenv(xudpBaseKey, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Now we handle read, fallback to gomobile asset (apk assets)
|
|
||||||
v2filesystem.NewFileReader = func(path string) (io.ReadCloser, error) {
|
v2filesystem.NewFileReader = func(path string) (io.ReadCloser, error) {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
_, file := filepath.Split(path)
|
_, file := filepath.Split(path)
|
||||||
@@ -206,16 +201,14 @@ func InitV2Env(envPath string, key string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MeasureOutboundDelay measures the outbound delay for a given configuration and URL
|
||||||
func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) {
|
func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) {
|
||||||
config, err := v2serial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
config, err := v2serial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, fmt.Errorf("failed to load JSON config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't listen to anything for test purpose
|
|
||||||
config.Inbound = nil
|
config.Inbound = nil
|
||||||
// config.App: (fakedns), log, dispatcher, InboundConfig, OutboundConfig, (stats), router, dns, (policy)
|
|
||||||
// keep only basic features
|
|
||||||
var essentialApp []*serial.TypedMessage
|
var essentialApp []*serial.TypedMessage
|
||||||
for _, app := range config.App {
|
for _, app := range config.App {
|
||||||
if app.Type == "xray.app.proxyman.OutboundConfig" || app.Type == "xray.app.dispatcher.Config" || app.Type == "xray.app.log.Config" {
|
if app.Type == "xray.app.proxyman.OutboundConfig" || app.Type == "xray.app.dispatcher.Config" || app.Type == "xray.app.log.Config" {
|
||||||
@@ -226,18 +219,16 @@ func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error
|
|||||||
|
|
||||||
inst, err := v2core.New(config)
|
inst, err := v2core.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, fmt.Errorf("failed to create core instance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.Start()
|
inst.Start()
|
||||||
delay, err := measureInstDelay(context.Background(), inst, url)
|
defer inst.Close()
|
||||||
inst.Close()
|
return measureInstDelay(context.Background(), inst, url)
|
||||||
return delay, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*NewV2RayPoint new V2RayPoint*/
|
// NewV2RayPoint creates a new V2RayPoint instance
|
||||||
func NewV2RayPoint(s V2RayVPNServiceSupportsSet, adns bool) *V2RayPoint {
|
func NewV2RayPoint(s V2RayVPNServiceSupportsSet, adns bool) *V2RayPoint {
|
||||||
// inject our own log writer
|
|
||||||
v2applog.RegisterHandlerCreator(v2applog.LogType_Console,
|
v2applog.RegisterHandlerCreator(v2applog.LogType_Console,
|
||||||
func(lt v2applog.LogType,
|
func(lt v2applog.LogType,
|
||||||
options v2applog.HandlerCreatorOptions) (v2commlog.Handler, error) {
|
options v2applog.HandlerCreatorOptions) (v2commlog.Handler, error) {
|
||||||
@@ -253,18 +244,16 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet, adns bool) *V2RayPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// CheckVersionX returns the library and V2Ray versions
|
||||||
CheckVersionX string
|
|
||||||
This func will return libv2ray binding version and V2Ray version used.
|
|
||||||
*/
|
|
||||||
func CheckVersionX() string {
|
func CheckVersionX() string {
|
||||||
var version = 30
|
var version = 30
|
||||||
return fmt.Sprintf("Lib v%d, Xray-core v%s", version, v2core.Version())
|
return fmt.Sprintf("Lib v%d, Xray-core v%s", version, v2core.Version())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// measureInstDelay measures the delay for an instance to a given URL
|
||||||
func measureInstDelay(ctx context.Context, inst *v2core.Instance, url string) (int64, error) {
|
func measureInstDelay(ctx context.Context, inst *v2core.Instance, url string) (int64, error) {
|
||||||
if inst == nil {
|
if inst == nil {
|
||||||
return -1, errors.New("core instance nil")
|
return -1, errors.New("core instance is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
@@ -279,29 +268,29 @@ func measureInstDelay(ctx context.Context, inst *v2core.Instance, url string) (i
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &http.Client{
|
client := &http.Client{
|
||||||
Transport: tr,
|
Transport: tr,
|
||||||
Timeout: 12 * time.Second,
|
Timeout: 12 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(url) <= 0 {
|
if len(url) == 0 {
|
||||||
url = "https://www.google.com/generate_204"
|
url = "https://www.google.com/generate_204"
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, err := c.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
||||||
return -1, fmt.Errorf("status != 20x: %s", resp.Status)
|
return -1, fmt.Errorf("unexpected status code: %s", resp.Status)
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
|
||||||
return time.Since(start).Milliseconds(), nil
|
return time.Since(start).Milliseconds(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This struct creates our own log writer without datetime stamp
|
// consoleLogWriter creates our own log writer without datetime stamp
|
||||||
// As Android adds time stamps on each line
|
// As Android adds time stamps on each line
|
||||||
type consoleLogWriter struct {
|
type consoleLogWriter struct {
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
@@ -316,10 +305,11 @@ func (w *consoleLogWriter) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This logger won't print data/time stamps
|
// createStdoutLogWriter creates a logger that won't print date/time stamps
|
||||||
func createStdoutLogWriter() v2commlog.WriterCreator {
|
func createStdoutLogWriter() v2commlog.WriterCreator {
|
||||||
return func() v2commlog.Writer {
|
return func() v2commlog.Writer {
|
||||||
return &consoleLogWriter{
|
return &consoleLogWriter{
|
||||||
logger: log.New(os.Stdout, "", 0)}
|
logger: log.New(os.Stdout, "", 0),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+37
-62
@@ -17,10 +17,12 @@ import (
|
|||||||
v2internet "github.com/xtls/xray-core/transport/internet"
|
v2internet "github.com/xtls/xray-core/transport/internet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// protectSet defines an interface for protecting sockets
|
||||||
type protectSet interface {
|
type protectSet interface {
|
||||||
Protect(int) bool
|
Protect(int) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolved holds the resolved IP addresses and associated metadata
|
||||||
type resolved struct {
|
type resolved struct {
|
||||||
domain string
|
domain string
|
||||||
IPs []net.IP
|
IPs []net.IP
|
||||||
@@ -31,27 +33,19 @@ type resolved struct {
|
|||||||
lastSwitched time.Time
|
lastSwitched time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextIP switch to another resolved result.
|
// NextIP switches to another resolved IP address
|
||||||
// there still be race-condition here if multiple err concurently occured
|
|
||||||
// may cause idx keep switching,
|
|
||||||
// but that's an outside error can hardly handled here
|
|
||||||
func (r *resolved) NextIP() {
|
func (r *resolved) NextIP() {
|
||||||
r.ipLock.Lock()
|
r.ipLock.Lock()
|
||||||
defer r.ipLock.Unlock()
|
defer r.ipLock.Unlock()
|
||||||
|
|
||||||
if len(r.IPs) > 1 {
|
if len(r.IPs) > 1 {
|
||||||
|
|
||||||
// throttle, don't switch too quickly
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(r.lastSwitched) < time.Second*5 {
|
if now.Sub(r.lastSwitched) < 5*time.Second {
|
||||||
log.Println("switch too quickly")
|
log.Println("switch too quickly")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.lastSwitched = now
|
r.lastSwitched = now
|
||||||
r.ipIdx++
|
r.ipIdx++
|
||||||
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.ipIdx >= uint8(len(r.IPs)) {
|
if r.ipIdx >= uint8(len(r.IPs)) {
|
||||||
@@ -61,27 +55,25 @@ func (r *resolved) NextIP() {
|
|||||||
log.Printf("switched to next IP: %v", r.IPs[r.ipIdx])
|
log.Printf("switched to next IP: %v", r.IPs[r.ipIdx])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currentIP returns the current IP address
|
||||||
func (r *resolved) currentIP() net.IP {
|
func (r *resolved) currentIP() net.IP {
|
||||||
r.ipLock.Lock()
|
r.ipLock.Lock()
|
||||||
defer r.ipLock.Unlock()
|
defer r.ipLock.Unlock()
|
||||||
if len(r.IPs) > 0 {
|
if len(r.IPs) > 0 {
|
||||||
return r.IPs[r.ipIdx]
|
return r.IPs[r.ipIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProtectedDialer ...
|
// NewProtectedDialer creates a new ProtectedDialer
|
||||||
func NewProtectedDialer(p protectSet) *ProtectedDialer {
|
func NewProtectedDialer(p protectSet) *ProtectedDialer {
|
||||||
d := &ProtectedDialer{
|
return &ProtectedDialer{
|
||||||
// prefer native lookup on Android
|
|
||||||
resolver: &net.Resolver{PreferGo: false},
|
resolver: &net.Resolver{PreferGo: false},
|
||||||
protectSet: p,
|
protectSet: p,
|
||||||
}
|
}
|
||||||
return d
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProtectedDialer ...
|
// ProtectedDialer handles protected dialing
|
||||||
type ProtectedDialer struct {
|
type ProtectedDialer struct {
|
||||||
currentServer string
|
currentServer string
|
||||||
resolveChan chan struct{}
|
resolveChan chan struct{}
|
||||||
@@ -93,21 +85,23 @@ type ProtectedDialer struct {
|
|||||||
protectSet
|
protectSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVServerReady checks if the virtual server is ready
|
||||||
func (d *ProtectedDialer) IsVServerReady() bool {
|
func (d *ProtectedDialer) IsVServerReady() bool {
|
||||||
return (d.vServer != nil)
|
return d.vServer != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrepareResolveChan prepares the resolve channel
|
||||||
func (d *ProtectedDialer) PrepareResolveChan() {
|
func (d *ProtectedDialer) PrepareResolveChan() {
|
||||||
d.resolveChan = make(chan struct{})
|
d.resolveChan = make(chan struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveChan returns the resolve channel
|
||||||
func (d *ProtectedDialer) ResolveChan() chan struct{} {
|
func (d *ProtectedDialer) ResolveChan() chan struct{} {
|
||||||
return d.resolveChan
|
return d.resolveChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// simplicated version of golang: internetAddrList in src/net/ipsock.go
|
// lookupAddr performs DNS resolution for the given address
|
||||||
func (d *ProtectedDialer) lookupAddr(addr string) (*resolved, error) {
|
func (d *ProtectedDialer) lookupAddr(addr string) (*resolved, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
host, port string
|
host, port string
|
||||||
@@ -131,44 +125,41 @@ func (d *ProtectedDialer) lookupAddr(addr string) (*resolved, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(addrs) == 0 {
|
if len(addrs) == 0 {
|
||||||
return nil, fmt.Errorf("domain %s Failed to resolve", addr)
|
return nil, fmt.Errorf("domain %s Failed to resolve", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
IPs := make([]net.IP, 0)
|
IPs := make([]net.IP, 0)
|
||||||
//ipv6 is prefer, append ipv6 then ipv4
|
if d.preferIPv6 {
|
||||||
//ipv6 is not prefer, append ipv4 then ipv6
|
|
||||||
if(d.preferIPv6) {
|
|
||||||
for _, ia := range addrs {
|
for _, ia := range addrs {
|
||||||
if(ia.IP.To4() == nil) {
|
if ia.IP.To4() == nil {
|
||||||
IPs = append(IPs, ia.IP)
|
IPs = append(IPs, ia.IP)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ia := range addrs {
|
|
||||||
if(ia.IP.To4() != nil) {
|
|
||||||
IPs = append(IPs, ia.IP)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!d.preferIPv6) {
|
for _, ia := range addrs {
|
||||||
|
if ia.IP.To4() != nil {
|
||||||
|
IPs = append(IPs, ia.IP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !d.preferIPv6 {
|
||||||
for _, ia := range addrs {
|
for _, ia := range addrs {
|
||||||
if(ia.IP.To4() == nil) {
|
if ia.IP.To4() == nil {
|
||||||
IPs = append(IPs, ia.IP)
|
IPs = append(IPs, ia.IP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := &resolved{
|
return &resolved{
|
||||||
domain: host,
|
domain: host,
|
||||||
IPs: IPs,
|
IPs: IPs,
|
||||||
Port: portnum,
|
Port: portnum,
|
||||||
lastResolved: time.Now(),
|
lastResolved: time.Now(),
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
return rs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareDomain caches direct v2ray server host
|
// PrepareDomain caches the resolved IP addresses for the given domain
|
||||||
func (d *ProtectedDialer) PrepareDomain(domainName string, closeCh <-chan struct{}, prefIPv6 bool) {
|
func (d *ProtectedDialer) PrepareDomain(domainName string, closeCh <-chan struct{}, prefIPv6 bool) {
|
||||||
log.Printf("Preparing Domain: %s", domainName)
|
log.Printf("Preparing Domain: %s", domainName)
|
||||||
d.currentServer = domainName
|
d.currentServer = domainName
|
||||||
@@ -189,7 +180,7 @@ func (d *ProtectedDialer) PrepareDomain(domainName string, closeCh <-chan struct
|
|||||||
case <-closeCh:
|
case <-closeCh:
|
||||||
log.Printf("PrepareDomain exit due to core closed")
|
log.Printf("PrepareDomain exit due to core closed")
|
||||||
return
|
return
|
||||||
case <-time.After(time.Second * 2):
|
case <-time.After(2 * time.Second):
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -201,6 +192,7 @@ func (d *ProtectedDialer) PrepareDomain(domainName string, closeCh <-chan struct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFd returns a file descriptor for the given network
|
||||||
func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
|
func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
|
||||||
switch network {
|
switch network {
|
||||||
case v2net.Network_TCP:
|
case v2net.Network_TCP:
|
||||||
@@ -208,42 +200,32 @@ func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
|
|||||||
case v2net.Network_UDP:
|
case v2net.Network_UDP:
|
||||||
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
|
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknow network")
|
err = fmt.Errorf("unknown network")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init implement internet.SystemDialer
|
// Init initializes the dialer
|
||||||
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
|
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial exported as the protected dial method
|
// Dial performs a protected dial
|
||||||
func (d *ProtectedDialer) Dial(ctx context.Context,
|
func (d *ProtectedDialer) Dial(ctx context.Context,
|
||||||
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {
|
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {
|
||||||
|
|
||||||
// network := dest.Network.SystemString()
|
|
||||||
Address := dest.NetAddr()
|
Address := dest.NetAddr()
|
||||||
|
|
||||||
// v2ray server address,
|
|
||||||
// try to connect fixed IP if multiple IP parsed from domain,
|
|
||||||
// and switch to next IP if error occurred
|
|
||||||
if Address == d.currentServer {
|
if Address == d.currentServer {
|
||||||
if d.vServer == nil {
|
if d.vServer == nil {
|
||||||
log.Println("Dial pending prepare ...", Address)
|
log.Println("Dial pending prepare ...", Address)
|
||||||
<-d.resolveChan
|
<-d.resolveChan
|
||||||
|
|
||||||
// user may close connection during PrepareDomain,
|
|
||||||
// fast return release resources.
|
|
||||||
if d.vServer == nil {
|
if d.vServer == nil {
|
||||||
return nil, fmt.Errorf("fail to prepare domain %s", d.currentServer)
|
return nil, fmt.Errorf("fail to prepare domain %s", d.currentServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if time.Since(d.vServer.lastResolved) > time.Minute*30 {
|
|
||||||
// go d.PrepareDomain(Address, nil, d.preferIPv6)
|
|
||||||
// }
|
|
||||||
|
|
||||||
fd, err := d.getFd(dest.Network)
|
fd, err := d.getFd(dest.Network)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -259,8 +241,6 @@ func (d *ProtectedDialer) Dial(ctx context.Context,
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2ray connecting to "domestic" servers, no caching results
|
|
||||||
// log.Printf("Not Using Prepared: %s,%s", network, Address)
|
|
||||||
resolved, err := d.lookupAddr(Address)
|
resolved, err := d.lookupAddr(Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -271,20 +251,18 @@ func (d *ProtectedDialer) Dial(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the first resolved address.
|
|
||||||
// the result IP may vary, eg: IPv6 addrs comes first if client has ipv6 address
|
|
||||||
return d.fdConn(ctx, resolved.IPs[0], resolved.Port, dest.Network, fd)
|
return d.fdConn(ctx, resolved.IPs[0], resolved.Port, dest.Network, fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DestIpAddress returns the destination IP address
|
||||||
func (d *ProtectedDialer) DestIpAddress() net.IP {
|
func (d *ProtectedDialer) DestIpAddress() net.IP {
|
||||||
return d.vServer.currentIP()
|
return d.vServer.currentIP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fdConn establishes a connection using the given file descriptor
|
||||||
func (d *ProtectedDialer) fdConn(ctx context.Context, ip net.IP, port int, network v2net.Network, fd int) (net.Conn, error) {
|
func (d *ProtectedDialer) fdConn(ctx context.Context, ip net.IP, port int, network v2net.Network, fd int) (net.Conn, error) {
|
||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// call android VPN service to "protect" the fd connecting straight out
|
|
||||||
if !d.Protect(fd) {
|
if !d.Protect(fd) {
|
||||||
log.Printf("fdConn fail to protect, Close Fd: %d", fd)
|
log.Printf("fdConn fail to protect, Close Fd: %d", fd)
|
||||||
return nil, errors.New("fail to protect")
|
return nil, errors.New("fail to protect")
|
||||||
@@ -302,19 +280,16 @@ func (d *ProtectedDialer) fdConn(ctx context.Context, ip net.IP, port int, netwo
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := unix.Connect(fd, sa); err != nil {
|
if err := unix.Connect(fd, sa); err != nil {
|
||||||
// log.Printf("fdConn unix.Connect err, Close Fd: %d Err: %v", fd, err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file := os.NewFile(uintptr(fd), "Socket")
|
file := os.NewFile(uintptr(fd), "Socket")
|
||||||
if file == nil {
|
if file == nil {
|
||||||
// returned value will be nil if fd is not a valid file descriptor
|
|
||||||
return nil, errors.New("fdConn fd invalid")
|
return nil, errors.New("fdConn fd invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
//Closing conn does not affect file, and closing file does not affect conn.
|
|
||||||
if network == v2net.Network_UDP {
|
if network == v2net.Network_UDP {
|
||||||
packetConn, err := net.FilePacketConn(file)
|
packetConn, err := net.FilePacketConn(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+31
-37
@@ -12,12 +12,15 @@ import (
|
|||||||
v2net "github.com/xtls/xray-core/common/net"
|
v2net "github.com/xtls/xray-core/common/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fakeSupportSet is a mock implementation of the protectSet interface
|
||||||
type fakeSupportSet struct{}
|
type fakeSupportSet struct{}
|
||||||
|
|
||||||
|
// Protect is a mock implementation that always returns true
|
||||||
func (f fakeSupportSet) Protect(int) bool {
|
func (f fakeSupportSet) Protect(int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestProtectedDialer_PrepareDomain tests the PrepareDomain method of the ProtectedDialer
|
||||||
func TestProtectedDialer_PrepareDomain(t *testing.T) {
|
func TestProtectedDialer_PrepareDomain(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
domainName string
|
domainName string
|
||||||
@@ -26,21 +29,17 @@ func TestProtectedDialer_PrepareDomain(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
}{
|
}{
|
||||||
// TODO: Add test cases.
|
{"Test with baidu.com", args{"baidu.com:80"}},
|
||||||
{"", args{"baidu.com:80"}},
|
// Add more test cases if needed
|
||||||
// {"", args{"cloudflare.com:443"}},
|
|
||||||
// {"", args{"apple.com:443"}},
|
|
||||||
// {"", args{"110.110.110.110:443"}},
|
|
||||||
// {"", args{"[2002:1234::1]:443"}},
|
|
||||||
}
|
}
|
||||||
d := NewProtectedDialer(fakeSupportSet{})
|
d := NewProtectedDialer(fakeSupportSet{})
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
ch := make(chan struct{})
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ch := make(chan struct{})
|
||||||
go d.PrepareDomain(tt.args.domainName, ch, false)
|
go d.PrepareDomain(tt.args.domainName, ch, false)
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
go d.vServer.NextIP()
|
d.vServer.NextIP()
|
||||||
t.Log(d.vServer.currentIP())
|
t.Log(d.vServer.currentIP())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -48,19 +47,16 @@ func TestProtectedDialer_PrepareDomain(t *testing.T) {
|
|||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestProtectedDialer_Dial tests the Dial method of the ProtectedDialer
|
||||||
func TestProtectedDialer_Dial(t *testing.T) {
|
func TestProtectedDialer_Dial(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
// TODO: Add test cases.
|
|
||||||
{"baidu.com:80", false},
|
{"baidu.com:80", false},
|
||||||
{"cloudflare.com:80", false},
|
{"cloudflare.com:80", false},
|
||||||
{"172.16.192.11:80", true},
|
{"172.16.192.11:80", true},
|
||||||
// {"172.16.192.10:80", true},
|
// Add more test cases if needed
|
||||||
// {"[2fff:4322::1]:443", true},
|
|
||||||
// {"[fc00::1]:443", true},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -84,18 +80,17 @@ func TestProtectedDialer_Dial(t *testing.T) {
|
|||||||
t.Log(err)
|
t.Log(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_host, _, _ := net.SplitHostPort(tt.name)
|
defer conn.Close()
|
||||||
fmt.Fprintf(conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", _host))
|
|
||||||
|
host, _, _ := net.SplitHostPort(tt.name)
|
||||||
|
fmt.Fprintf(conn, "GET / HTTP/1.1\r\nHost: %s\r\n\r\n", host)
|
||||||
status, err := bufio.NewReader(conn).ReadString('\n')
|
status, err := bufio.NewReader(conn).ReadString('\n')
|
||||||
t.Logf("%#v, %#v\n", status, err)
|
t.Logf("Status: %s, Error: %v", status, err)
|
||||||
conn.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for n := 0; n < 3; n++ {
|
for n := 0; n < 3; n++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go dial()
|
go dial()
|
||||||
// time.Sleep(time.Millisecond * 10)
|
|
||||||
// d.pendingMap[tt.name] = make(chan struct{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -103,6 +98,7 @@ func TestProtectedDialer_Dial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test_resolved_NextIP tests the NextIP method of the resolved struct
|
||||||
func Test_resolved_NextIP(t *testing.T) {
|
func Test_resolved_NextIP(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
domain string
|
domain string
|
||||||
@@ -113,17 +109,15 @@ func Test_resolved_NextIP(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
fields fields
|
fields fields
|
||||||
}{
|
}{
|
||||||
// TODO: Add test cases.
|
{"test1", fields{
|
||||||
{"test1",
|
domain: "www.baidu.com",
|
||||||
fields{
|
IPs: []net.IP{
|
||||||
domain: "www.baidu.com",
|
net.ParseIP("1.2.3.4"),
|
||||||
IPs: []net.IP{
|
net.ParseIP("4.3.2.1"),
|
||||||
net.ParseIP("1.2.3.4"),
|
net.ParseIP("1234::1"),
|
||||||
net.ParseIP("4.3.2.1"),
|
net.ParseIP("4321::1"),
|
||||||
net.ParseIP("1234::1"),
|
},
|
||||||
net.ParseIP("4321::1"),
|
}},
|
||||||
},
|
|
||||||
}},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -132,20 +126,20 @@ func Test_resolved_NextIP(t *testing.T) {
|
|||||||
IPs: tt.fields.IPs,
|
IPs: tt.fields.IPs,
|
||||||
Port: tt.fields.Port,
|
Port: tt.fields.Port,
|
||||||
}
|
}
|
||||||
t.Logf("%v", r.IPs)
|
t.Logf("Initial IPs: %v", r.IPs)
|
||||||
t.Logf("%v", r.currentIP())
|
t.Logf("Current IP: %v", r.currentIP())
|
||||||
r.NextIP()
|
r.NextIP()
|
||||||
t.Logf("%v", r.currentIP())
|
t.Logf("Next IP: %v", r.currentIP())
|
||||||
r.NextIP()
|
r.NextIP()
|
||||||
t.Logf("%v", r.currentIP())
|
t.Logf("Next IP: %v", r.currentIP())
|
||||||
r.NextIP()
|
r.NextIP()
|
||||||
t.Logf("%v", r.currentIP())
|
t.Logf("Next IP: %v", r.currentIP())
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
r.NextIP()
|
r.NextIP()
|
||||||
t.Logf("%v", r.currentIP())
|
t.Logf("Next IP: %v", r.currentIP())
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
r.NextIP()
|
r.NextIP()
|
||||||
t.Logf("%v", r.currentIP())
|
t.Logf("Next IP: %v", r.currentIP())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user