github.com/igoogolx/clash@v1.19.8/adapter/adapter.go (about)

     1  package adapter
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/igoogolx/clash/common/queue"
    14  	"github.com/igoogolx/clash/component/dialer"
    15  	C "github.com/igoogolx/clash/constant"
    16  
    17  	"go.uber.org/atomic"
    18  )
    19  
    20  type Proxy struct {
    21  	C.ProxyAdapter
    22  	history *queue.Queue
    23  	alive   *atomic.Bool
    24  }
    25  
    26  // Alive implements C.Proxy
    27  func (p *Proxy) Alive() bool {
    28  	return p.alive.Load()
    29  }
    30  
    31  // Dial implements C.Proxy
    32  func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
    33  	ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
    34  	defer cancel()
    35  	return p.DialContext(ctx, metadata)
    36  }
    37  
    38  // DialContext implements C.ProxyAdapter
    39  func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    40  	conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
    41  	p.alive.Store(err == nil)
    42  	return conn, err
    43  }
    44  
    45  // DialUDP implements C.ProxyAdapter
    46  func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
    47  	ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
    48  	defer cancel()
    49  	return p.ListenPacketContext(ctx, metadata)
    50  }
    51  
    52  // ListenPacketContext implements C.ProxyAdapter
    53  func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    54  	pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
    55  	p.alive.Store(err == nil)
    56  	return pc, err
    57  }
    58  
    59  // DelayHistory implements C.Proxy
    60  func (p *Proxy) DelayHistory() []C.DelayHistory {
    61  	queue := p.history.Copy()
    62  	histories := []C.DelayHistory{}
    63  	for _, item := range queue {
    64  		histories = append(histories, item.(C.DelayHistory))
    65  	}
    66  	return histories
    67  }
    68  
    69  // LastDelay return last history record. if proxy is not alive, return the max value of uint16.
    70  // implements C.Proxy
    71  func (p *Proxy) LastDelay() (delay uint16) {
    72  	var max uint16 = 0xffff
    73  	if !p.alive.Load() {
    74  		return max
    75  	}
    76  
    77  	last := p.history.Last()
    78  	if last == nil {
    79  		return max
    80  	}
    81  	history := last.(C.DelayHistory)
    82  	if history.Delay == 0 {
    83  		return max
    84  	}
    85  	return history.Delay
    86  }
    87  
    88  // MarshalJSON implements C.ProxyAdapter
    89  func (p *Proxy) MarshalJSON() ([]byte, error) {
    90  	inner, err := p.ProxyAdapter.MarshalJSON()
    91  	if err != nil {
    92  		return inner, err
    93  	}
    94  
    95  	mapping := map[string]any{}
    96  	json.Unmarshal(inner, &mapping)
    97  	mapping["history"] = p.DelayHistory()
    98  	mapping["alive"] = p.Alive()
    99  	mapping["name"] = p.Name()
   100  	mapping["udp"] = p.SupportUDP()
   101  	return json.Marshal(mapping)
   102  }
   103  
   104  // URLTest get the delay for the specified URL
   105  // implements C.Proxy
   106  func (p *Proxy) URLTest(ctx context.Context, url string) (delay, meanDelay uint16, err error) {
   107  	defer func() {
   108  		p.alive.Store(err == nil)
   109  		record := C.DelayHistory{Time: time.Now()}
   110  		if err == nil {
   111  			record.Delay = delay
   112  			record.MeanDelay = meanDelay
   113  		}
   114  		p.history.Put(record)
   115  		if p.history.Len() > 10 {
   116  			p.history.Pop()
   117  		}
   118  	}()
   119  
   120  	addr, err := urlToMetadata(url)
   121  	if err != nil {
   122  		return
   123  	}
   124  
   125  	start := time.Now()
   126  	instance, err := p.DialContext(ctx, &addr)
   127  	if err != nil {
   128  		return
   129  	}
   130  	defer instance.Close()
   131  
   132  	req, err := http.NewRequest(http.MethodHead, url, nil)
   133  	if err != nil {
   134  		return
   135  	}
   136  	req = req.WithContext(ctx)
   137  
   138  	transport := &http.Transport{
   139  		Dial: func(string, string) (net.Conn, error) {
   140  			return instance, nil
   141  		},
   142  		// from http.DefaultTransport
   143  		MaxIdleConns:          100,
   144  		IdleConnTimeout:       90 * time.Second,
   145  		TLSHandshakeTimeout:   10 * time.Second,
   146  		ExpectContinueTimeout: 1 * time.Second,
   147  	}
   148  
   149  	client := http.Client{
   150  		Transport: transport,
   151  		CheckRedirect: func(req *http.Request, via []*http.Request) error {
   152  			return http.ErrUseLastResponse
   153  		},
   154  	}
   155  	defer client.CloseIdleConnections()
   156  
   157  	resp, err := client.Do(req)
   158  	if err != nil {
   159  		return
   160  	}
   161  	resp.Body.Close()
   162  	delay = uint16(time.Since(start) / time.Millisecond)
   163  
   164  	resp, err = client.Do(req)
   165  	if err != nil {
   166  		// ignore error because some server will hijack the connection and close immediately
   167  		return delay, 0, nil
   168  	}
   169  	resp.Body.Close()
   170  	meanDelay = uint16(time.Since(start) / time.Millisecond / 2)
   171  
   172  	return
   173  }
   174  
   175  func NewProxy(adapter C.ProxyAdapter) *Proxy {
   176  	return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
   177  }
   178  
   179  func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
   180  	u, err := url.Parse(rawURL)
   181  	if err != nil {
   182  		return
   183  	}
   184  
   185  	port := u.Port()
   186  	if port == "" {
   187  		switch u.Scheme {
   188  		case "https":
   189  			port = "443"
   190  		case "http":
   191  			port = "80"
   192  		default:
   193  			err = fmt.Errorf("%s scheme not Support", rawURL)
   194  			return
   195  		}
   196  	}
   197  
   198  	p, _ := strconv.ParseUint(port, 10, 16)
   199  
   200  	addr = C.Metadata{
   201  		Host:    u.Hostname(),
   202  		DstIP:   nil,
   203  		DstPort: C.Port(p),
   204  	}
   205  	return
   206  }