mihomo/adapter/adapter.go

364 lines
8 KiB
Go
Raw Normal View History

2021-06-10 14:05:56 +08:00
package adapter
2018-12-22 23:56:42 +08:00
import (
2019-07-02 19:18:03 +08:00
"context"
2018-12-22 23:56:42 +08:00
"encoding/json"
"errors"
2021-06-10 14:05:56 +08:00
"fmt"
"net"
"net/http"
2022-04-20 01:52:51 +08:00
"net/netip"
2021-06-10 14:05:56 +08:00
"net/url"
"strconv"
"time"
2018-12-22 23:56:42 +08:00
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
2018-12-22 23:56:42 +08:00
)
var UnifiedDelay = atomic.NewBool(false)
const (
defaultHistoriesNum = 10
)
type extraProxyState struct {
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
}
type Proxy struct {
C.ProxyAdapter
2022-04-06 01:07:08 +08:00
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
url string
extra map[string]*extraProxyState
}
2021-04-29 11:23:14 +08:00
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
// AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool {
if p.extra != nil {
if state, ok := p.extra[url]; ok {
return state.alive.Load()
}
}
return p.alive.Load()
}
2021-04-29 11:23:14 +08:00
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
2021-06-10 14:05:56 +08:00
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
2021-04-29 11:23:14 +08:00
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
return conn, err
}
// DialUDP implements C.ProxyAdapter
func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel()
return p.ListenPacketContext(ctx, metadata)
}
// ListenPacketContext implements C.ProxyAdapter
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
return pc, err
}
2021-04-29 11:23:14 +08:00
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory {
2022-04-20 01:52:51 +08:00
queueM := p.history.Copy()
histories := []C.DelayHistory{}
2022-04-20 01:52:51 +08:00
for _, item := range queueM {
2022-04-06 01:07:08 +08:00
histories = append(histories, item)
}
return histories
}
// DelayHistoryForTestUrl implements C.Proxy
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
var queueM []C.DelayHistory
if p.extra != nil {
if state, ok := p.extra[url]; ok {
queueM = state.history.Copy()
}
}
if queueM == nil {
queueM = p.history.Copy()
}
histories := []C.DelayHistory{}
for _, item := range queueM {
histories = append(histories, item)
}
return histories
}
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
extra := map[string][]C.DelayHistory{}
if p.extra != nil && len(p.extra) != 0 {
for testUrl, option := range p.extra {
histories := []C.DelayHistory{}
queueM := option.history.Copy()
for _, item := range queueM {
histories = append(histories, item)
}
extra[testUrl] = histories
}
}
return extra
}
2019-09-07 16:23:43 +08:00
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
2021-04-29 11:23:14 +08:00
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) {
2019-03-23 16:29:27 +08:00
var max uint16 = 0xffff
if !p.alive.Load() {
2019-03-23 16:29:27 +08:00
return max
}
2022-04-06 01:07:08 +08:00
history := p.history.Last()
if history.Delay == 0 {
2019-03-23 16:29:27 +08:00
return max
}
return history.Delay
}
// LastDelayForTestUrl implements C.Proxy
func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
var max uint16 = 0xffff
alive := p.alive.Load()
history := p.history.Last()
if p.extra != nil {
if state, ok := p.extra[url]; ok {
alive = state.alive.Load()
history = state.history.Last()
}
}
if !alive {
return max
}
if history.Delay == 0 {
return max
}
return history.Delay
}
2021-04-29 11:23:14 +08:00
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
2022-03-16 12:10:13 +08:00
mapping := map[string]any{}
2022-04-20 01:52:51 +08:00
_ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["extra"] = p.ExtraDelayHistory()
mapping["alive"] = p.Alive()
2019-12-13 00:29:24 +08:00
mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP()
mapping["tfo"] = p.SupportTFO()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
2021-04-29 11:23:14 +08:00
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) {
defer func() {
alive := err == nil
store = p.determineFinalStoreType(store, url)
switch store {
case C.OriginalHistory:
p.alive.Store(alive)
record := C.DelayHistory{Time: time.Now()}
if alive {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > defaultHistoriesNum {
p.history.Pop()
}
// test URL configured by the proxy provider
if len(p.url) == 0 {
p.url = url
}
case C.ExtraHistory:
record := C.DelayHistory{Time: time.Now()}
if alive {
record.Delay = t
}
if p.extra == nil {
p.extra = map[string]*extraProxyState{}
}
state, ok := p.extra[url]
if !ok {
state = &extraProxyState{
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
}
p.extra[url] = state
}
state.alive.Store(alive)
state.history.Put(record)
if state.history.Len() > defaultHistoriesNum {
state.history.Pop()
}
default:
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
}
}()
unifiedDelay := UnifiedDelay.Load()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.DialContext(ctx, &addr)
if err != nil {
return
}
2022-04-20 01:52:51 +08:00
defer func() {
_ = instance.Close()
}()
2019-07-02 19:18:03 +08:00
req, err := http.NewRequest(http.MethodHead, url, nil)
2019-07-02 19:18:03 +08:00
if err != nil {
return
}
req = req.WithContext(ctx)
transport := &http.Transport{
2022-04-20 01:52:51 +08:00
DialContext: func(context.Context, string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
2019-07-02 19:18:03 +08:00
client := http.Client{
Timeout: 30 * time.Second,
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
2022-06-19 17:29:46 +08:00
defer client.CloseIdleConnections()
2022-06-19 17:29:46 +08:00
2019-07-02 19:18:03 +08:00
resp, err := client.Do(req)
2022-06-19 17:29:46 +08:00
if err != nil {
return
}
2022-06-19 17:29:46 +08:00
_ = resp.Body.Close()
if unifiedDelay {
second := time.Now()
resp, err = client.Do(req)
if err == nil {
_ = resp.Body.Close()
start = second
}
}
2022-06-19 17:29:46 +08:00
2023-06-05 12:40:46 +08:00
if expectedStatus != nil && !expectedStatus.Check(uint16(resp.StatusCode)) {
// maybe another value should be returned for differentiation
err = errors.New("response status is inconsistent with the expected status")
}
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}}
}
2021-06-10 14:05:56 +08:00
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return
}
2021-06-10 14:05:56 +08:00
addr = C.Metadata{
2022-11-11 09:19:28 +08:00
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: uint16(uintPort),
2021-06-10 14:05:56 +08:00
}
return
}
func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType {
if store != C.DropHistory {
return store
}
if len(p.url) == 0 || url == p.url {
return C.OriginalHistory
}
if p.extra == nil {
store = C.ExtraHistory
} else {
if _, ok := p.extra[url]; ok {
store = C.ExtraHistory
} else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum {
store = C.ExtraHistory
}
}
return store
}