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