github.com/metacubex/mihomo@v1.18.5/adapter/adapter.go (about)

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