github.com/yaling888/clash@v1.53.0/adapter/adapter.go (about)

     1  package adapter
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"net/netip"
    11  	"net/url"
    12  	"os"
    13  	"strconv"
    14  	"sync"
    15  	"time"
    16  
    17  	"go.uber.org/atomic"
    18  
    19  	"github.com/yaling888/clash/common/queue"
    20  	"github.com/yaling888/clash/component/dialer"
    21  	"github.com/yaling888/clash/component/resolver"
    22  	C "github.com/yaling888/clash/constant"
    23  )
    24  
    25  type Proxy struct {
    26  	C.ProxyAdapter
    27  	history *queue.Queue[C.DelayHistory]
    28  	alive   *atomic.Bool
    29  	hasV6   *atomic.Bool
    30  	v6Mux   sync.Mutex
    31  }
    32  
    33  // Alive implements C.Proxy
    34  func (p *Proxy) Alive() bool {
    35  	return p.alive.Load()
    36  }
    37  
    38  // HasV6 implements C.Proxy
    39  func (p *Proxy) HasV6() bool {
    40  	if p.hasV6 == nil {
    41  		return false
    42  	}
    43  	return p.hasV6.Load()
    44  }
    45  
    46  // Dial implements C.Proxy
    47  func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
    48  	ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
    49  	defer cancel()
    50  	return p.DialContext(ctx, metadata)
    51  }
    52  
    53  // DialContext implements C.ProxyAdapter
    54  func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    55  	conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
    56  	if !errors.Is(err, context.Canceled) {
    57  		p.alive.Store(err == nil)
    58  	}
    59  	return conn, err
    60  }
    61  
    62  // DialUDP implements C.ProxyAdapter
    63  func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
    64  	ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
    65  	defer cancel()
    66  	return p.ListenPacketContext(ctx, metadata)
    67  }
    68  
    69  // ListenPacketContext implements C.ProxyAdapter
    70  func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    71  	pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
    72  	if !errors.Is(err, context.Canceled) {
    73  		p.alive.Store(err == nil)
    74  	}
    75  	return pc, err
    76  }
    77  
    78  // DelayHistory implements C.Proxy
    79  func (p *Proxy) DelayHistory() []C.DelayHistory {
    80  	queueM := p.history.Copy()
    81  	histories := []C.DelayHistory{}
    82  	histories = append(histories, queueM...)
    83  	return histories
    84  }
    85  
    86  // LastDelay return last history record. if proxy is not alive, return the max value of uint16.
    87  // implements C.Proxy
    88  func (p *Proxy) LastDelay() (delay uint16) {
    89  	var maxDelay uint16 = 0xffff
    90  	if !p.alive.Load() {
    91  		return maxDelay
    92  	}
    93  
    94  	history := p.history.Last()
    95  	if history.Delay == 0 {
    96  		return maxDelay
    97  	}
    98  	return history.Delay
    99  }
   100  
   101  // MarshalJSON implements C.ProxyAdapter
   102  func (p *Proxy) MarshalJSON() ([]byte, error) {
   103  	inner, err := p.ProxyAdapter.MarshalJSON()
   104  	if err != nil {
   105  		return inner, err
   106  	}
   107  
   108  	mapping := map[string]any{}
   109  	_ = json.Unmarshal(inner, &mapping)
   110  	mapping["history"] = p.DelayHistory()
   111  	mapping["alive"] = p.Alive()
   112  	mapping["name"] = p.Name()
   113  	mapping["udp"] = p.SupportUDP()
   114  	return json.Marshal(mapping)
   115  }
   116  
   117  // URLTest get the delay for the specified URL
   118  // implements C.Proxy
   119  func (p *Proxy) URLTest(ctx context.Context, url string) (delay, avgDelay uint16, err error) {
   120  	defer func() {
   121  		alive := err == nil
   122  		p.alive.Store(alive)
   123  		record := C.DelayHistory{Time: time.Now()}
   124  		if alive {
   125  			record.Delay = delay
   126  			record.AvgDelay = avgDelay
   127  			if p.hasV6 == nil && resolver.RemoteDnsResolve && !p.ProxyAdapter.DisableDnsResolve() {
   128  				go p.v6Test(url)
   129  			}
   130  		}
   131  		p.history.Put(record)
   132  		if p.history.Len() > 10 {
   133  			p.history.Pop()
   134  		}
   135  	}()
   136  
   137  	addr, err := urlToMetadata(url)
   138  	if err != nil {
   139  		return
   140  	}
   141  
   142  	start := time.Now()
   143  	instance, err := p.DialContext(ctx, &addr)
   144  	if err != nil {
   145  		return
   146  	}
   147  	defer func() {
   148  		_ = instance.Close()
   149  	}()
   150  
   151  	req, err := http.NewRequest(http.MethodHead, url, nil)
   152  	if err != nil {
   153  		return
   154  	}
   155  	req = req.WithContext(ctx)
   156  
   157  	transport := &http.Transport{
   158  		DialContext: func(context.Context, string, string) (net.Conn, error) {
   159  			return instance, nil
   160  		},
   161  		// from http.DefaultTransport
   162  		MaxIdleConns:          100,
   163  		IdleConnTimeout:       90 * time.Second,
   164  		TLSHandshakeTimeout:   10 * time.Second,
   165  		ExpectContinueTimeout: 1 * time.Second,
   166  	}
   167  
   168  	client := http.Client{
   169  		Transport: transport,
   170  		CheckRedirect: func(req *http.Request, via []*http.Request) error {
   171  			return http.ErrUseLastResponse
   172  		},
   173  	}
   174  	defer client.CloseIdleConnections()
   175  
   176  	resp, err := client.Do(req)
   177  	if err != nil {
   178  		return
   179  	}
   180  	_ = resp.Body.Close()
   181  	delay = uint16(time.Since(start) / time.Millisecond)
   182  
   183  	resp, err = client.Do(req)
   184  	if err != nil {
   185  		avgDelay = 0
   186  		err = nil
   187  		return
   188  	}
   189  	_ = resp.Body.Close()
   190  	avgDelay = uint16(time.Since(start) / time.Millisecond / 2)
   191  
   192  	return
   193  }
   194  
   195  func (p *Proxy) v6Test(url string) {
   196  	p.v6Mux.Lock()
   197  	if p.hasV6 != nil {
   198  		return
   199  	}
   200  
   201  	var (
   202  		resolved bool
   203  		err      error
   204  	)
   205  
   206  	defer func() {
   207  		if resolved {
   208  			p.hasV6 = atomic.NewBool(err == nil)
   209  		}
   210  		p.v6Mux.Unlock()
   211  	}()
   212  
   213  	addr, err := urlToMetadata(url)
   214  	if err != nil {
   215  		return
   216  	}
   217  
   218  	ips, err := resolver.LookupIPv6ByProxy(context.Background(), addr.Host, p.Name())
   219  	if err != nil {
   220  		if os.IsTimeout(err) || errors.Is(err, resolver.ErrIPNotFound) || errors.Is(err, resolver.ErrIPVersion) {
   221  			resolved = true
   222  		}
   223  		return
   224  	}
   225  	addr.DstIP = ips[0]
   226  	resolved = true
   227  
   228  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   229  	defer cancel()
   230  	instance, err := p.DialContext(ctx, &addr)
   231  	if err != nil {
   232  		return
   233  	}
   234  	defer func() {
   235  		_ = instance.Close()
   236  	}()
   237  
   238  	req, err := http.NewRequest(http.MethodGet, url, nil)
   239  	if err != nil {
   240  		return
   241  	}
   242  	req = req.WithContext(ctx)
   243  
   244  	transport := &http.Transport{
   245  		DialContext: func(context.Context, string, string) (net.Conn, error) {
   246  			return instance, nil
   247  		},
   248  		// from http.DefaultTransport
   249  		MaxIdleConns:          100,
   250  		IdleConnTimeout:       90 * time.Second,
   251  		TLSHandshakeTimeout:   10 * time.Second,
   252  		ExpectContinueTimeout: 1 * time.Second,
   253  	}
   254  
   255  	client := http.Client{
   256  		Transport: transport,
   257  		CheckRedirect: func(req *http.Request, via []*http.Request) error {
   258  			return http.ErrUseLastResponse
   259  		},
   260  	}
   261  	defer client.CloseIdleConnections()
   262  
   263  	resp, err := client.Do(req)
   264  	if err != nil {
   265  		return
   266  	}
   267  	_ = resp.Body.Close()
   268  }
   269  
   270  func NewProxy(adapter C.ProxyAdapter) *Proxy {
   271  	return &Proxy{
   272  		ProxyAdapter: adapter,
   273  		history:      queue.New[C.DelayHistory](10),
   274  		alive:        atomic.NewBool(true),
   275  	}
   276  }
   277  
   278  func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
   279  	u, err := url.Parse(rawURL)
   280  	if err != nil {
   281  		return
   282  	}
   283  
   284  	port := u.Port()
   285  	if port == "" {
   286  		switch u.Scheme {
   287  		case "https":
   288  			port = "443"
   289  		case "http":
   290  			port = "80"
   291  		default:
   292  			err = fmt.Errorf("%s scheme not Support", rawURL)
   293  			return
   294  		}
   295  	}
   296  
   297  	p, _ := strconv.ParseUint(port, 10, 16)
   298  
   299  	addr = C.Metadata{
   300  		Host:    u.Hostname(),
   301  		DstIP:   netip.Addr{},
   302  		DstPort: C.Port(p),
   303  	}
   304  	return
   305  }