diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d95226b0..2639c04b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,7 +143,7 @@ jobs: run: | go test ./... - - name: Update UA + - name: Update CA run: | sudo apt-get install ca-certificates sudo update-ca-certificates diff --git a/adapter/inbound/addition.go b/adapter/inbound/addition.go index c38c1aa1..ed560818 100644 --- a/adapter/inbound/addition.go +++ b/adapter/inbound/addition.go @@ -47,7 +47,7 @@ func WithDstAddr(addr net.Addr) Addition { func WithSrcAddr(addr net.Addr) Addition { return func(metadata *C.Metadata) { m := C.Metadata{} - if err := m.SetRemoteAddr(addr);err ==nil{ + if err := m.SetRemoteAddr(addr); err == nil { metadata.SrcIP = m.DstIP metadata.SrcPort = m.DstPort } @@ -57,7 +57,7 @@ func WithSrcAddr(addr net.Addr) Addition { func WithInAddr(addr net.Addr) Addition { return func(metadata *C.Metadata) { m := C.Metadata{} - if err := m.SetRemoteAddr(addr);err ==nil{ + if err := m.SetRemoteAddr(addr); err == nil { metadata.InIP = m.DstIP metadata.InPort = m.DstPort } diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go index 8f912fbe..f7d45399 100644 --- a/adapter/inbound/http.go +++ b/adapter/inbound/http.go @@ -14,7 +14,7 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A metadata.Type = C.HTTP metadata.RawSrcAddr = srcConn.RemoteAddr() metadata.RawDstAddr = srcConn.LocalAddr() - ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr())) + ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr())) ApplyAdditions(metadata, additions...) return conn, metadata } diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index b39ee3a6..0042f929 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -12,7 +12,6 @@ import ( "github.com/metacubex/mihomo/common/utils" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/provider" - types "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/tunnel" @@ -31,7 +30,7 @@ type GroupBase struct { failedTesting atomic.Bool proxies [][]C.Proxy versions []atomic.Uint32 - TestTimeout int + TestTimeout string maxFailedTimes int } @@ -40,7 +39,7 @@ type GroupBaseOption struct { filter string excludeFilter string excludeType string - TestTimeout int + TestTimeout string maxFailedTimes int providers []provider.ProxyProvider } @@ -74,8 +73,8 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { maxFailedTimes: opt.maxFailedTimes, } - if gb.TestTimeout == 0 { - gb.TestTimeout = 5000 + if gb.TestTimeout == "" { + gb.TestTimeout = "5000" } if gb.maxFailedTimes == 0 { gb.maxFailedTimes = 5 @@ -108,7 +107,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { pd.Touch() } - if pd.VehicleType() == types.Compatible { + if pd.VehicleType() == provider.Compatible { gb.versions[i].Store(pd.Version()) gb.proxies[i] = pd.Proxies() continue @@ -244,6 +243,11 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) { return } + var timeout time.Duration + if gb.TestTimeout != "" { + timeout = utils.ParseDuration(gb.TestTimeout, "ms") + } + go func() { gb.failedTestMux.Lock() defer gb.failedTestMux.Unlock() @@ -253,7 +257,7 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) { log.Debugln("ProxyGroup: %s first failed", gb.Name()) gb.failedTime = time.Now() } else { - if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond { + if time.Since(gb.failedTime) > timeout { gb.failedTimes = 0 return } diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 74947587..e1bd31a3 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -27,8 +27,8 @@ type GroupCommonOption struct { Proxies []string `group:"proxies,omitempty"` Use []string `group:"use,omitempty"` URL string `group:"url,omitempty"` - Interval int `group:"interval,omitempty"` - TestTimeout int `group:"timeout,omitempty"` + Interval string `group:"interval,omitempty"` + TestTimeout string `group:"timeout,omitempty"` MaxFailedTimes int `group:"max-failed-times,omitempty"` Lazy bool `group:"lazy,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"` @@ -88,6 +88,40 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide } groupOption.ExpectedStatus = status + var ( + interval uint + timeout uint + ) + if groupOption.Interval != "" { + interval = uint(utils.ParseDuration(groupOption.Interval, "s").Seconds()) + } + if groupOption.TestTimeout != "" { + timeout = uint(utils.ParseDuration(groupOption.TestTimeout, "ms").Milliseconds()) + } + + if len(groupOption.Use) != 0 { + PDs, err := getProviders(providersMap, groupOption.Use) + if err != nil { + return nil, fmt.Errorf("%s: %w", groupName, err) + } + + // if test URL is empty, use the first health check URL of providers + if groupOption.URL == "" { + for _, pd := range PDs { + if pd.HealthCheckURL() != "" { + groupOption.URL = pd.HealthCheckURL() + break + } + } + if groupOption.URL == "" { + groupOption.URL = C.DefaultTestURL + } + } else { + addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, interval) + } + providers = append(providers, PDs...) + } + if len(groupOption.Proxies) != 0 { ps, err := getProxies(proxyMap, groupOption.Proxies) if err != nil { @@ -98,51 +132,28 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) } - // select don't need health check + if groupOption.URL == "" { + groupOption.URL = C.DefaultTestURL + } + + // select don't need auto health check if groupOption.Type != "select" && groupOption.Type != "relay" { - if groupOption.Interval == 0 { - groupOption.Interval = 300 - } - if groupOption.URL == "" { - groupOption.URL = C.DefaultTestURL + if interval == 0 { + interval = 300 } } - hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus) + hc := provider.NewHealthCheck(ps, groupOption.URL, timeout, interval, groupOption.Lazy, expectedStatus) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, fmt.Errorf("%s: %w", groupName, err) } - providers = append(providers, pd) + providers = append([]types.ProxyProvider{pd}, providers...) providersMap[groupName] = pd } - if len(groupOption.Use) != 0 { - list, err := getProviders(providersMap, groupOption.Use) - if err != nil { - return nil, fmt.Errorf("%s: %w", groupName, err) - } - - if groupOption.URL == "" { - for _, p := range list { - if p.HealthCheckURL() != "" { - groupOption.URL = p.HealthCheckURL() - } - break - } - - if groupOption.URL == "" { - groupOption.URL = C.DefaultTestURL - } - } - - // different proxy groups use different test URL - addTestUrlToProviders(list, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval)) - providers = append(providers, list...) - } - var group C.ProxyAdapter switch groupOption.Type { case "url-test": diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 07fbcd95..4128a4b3 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -160,7 +160,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re "", "", "", - 5000, + "5000", 5, providers, }), diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index 2e436669..780a4c74 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -21,8 +21,8 @@ var ( type healthCheckSchema struct { Enable bool `provider:"enable"` URL string `provider:"url"` - Interval int `provider:"interval"` - TestTimeout int `provider:"timeout,omitempty"` + Interval string `provider:"interval"` + TestTimeout string `provider:"timeout,omitempty"` Lazy bool `provider:"lazy,omitempty"` ExpectedStatus string `provider:"expected-status,omitempty"` } @@ -44,14 +44,16 @@ type proxyProviderSchema struct { Type string `provider:"type"` Path string `provider:"path,omitempty"` URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` + Proxy string `provider:"proxy,omitempty"` + Interval string `provider:"interval,omitempty"` Filter string `provider:"filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"` ExcludeType string `provider:"exclude-type,omitempty"` DialerProxy string `provider:"dialer-proxy,omitempty"` - HealthCheck healthCheckSchema `provider:"health-check,omitempty"` - Override OverrideSchema `provider:"override,omitempty"` + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Override OverrideSchema `provider:"override,omitempty"` + Header map[string][]string `provider:"header,omitempty"` } func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { @@ -71,14 +73,27 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide return nil, err } - var hcInterval uint - if schema.HealthCheck.Enable { - if schema.HealthCheck.Interval == 0 { - schema.HealthCheck.Interval = 300 - } - hcInterval = uint(schema.HealthCheck.Interval) + var ( + interval time.Duration + hcInterval uint + timeout uint + ) + if schema.Interval != "" { + interval = utils.ParseDuration(schema.Interval, "s") } - hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus) + if schema.HealthCheck.Enable { + if schema.HealthCheck.Interval != "" { + hcInterval = uint(utils.ParseDuration(schema.HealthCheck.Interval, "s").Seconds()) + } + if hcInterval == 0 { + hcInterval = 300 + } + } + if schema.HealthCheck.TestTimeout != "" { + timeout = uint(utils.ParseDuration(schema.HealthCheck.TestTimeout, "ms").Milliseconds()) + } + + hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, timeout, hcInterval, schema.HealthCheck.Lazy, expectedStatus) var vehicle types.Vehicle switch schema.Type { @@ -86,21 +101,18 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide path := C.Path.Resolve(schema.Path) vehicle = resource.NewFileVehicle(path) case "http": + path := C.Path.GetPathByHash("proxies", schema.URL) if schema.Path != "" { - path := C.Path.Resolve(schema.Path) + path = C.Path.Resolve(schema.Path) if !features.CMFA && !C.Path.IsSafePath(path) { return nil, fmt.Errorf("%w: %s", errSubPath, path) } - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } else { - path := C.Path.GetPathByHash("proxies", schema.URL) - vehicle = resource.NewHTTPVehicle(schema.URL, path) } + vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header) default: return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) } - interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter excludeFilter := schema.ExcludeFilter excludeType := schema.ExcludeType diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 2715a309..aa5b8233 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -125,7 +125,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), - http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return } @@ -134,7 +134,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() { userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) if userInfoStr == "" { resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), - http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil) + http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, "") if err != nil { return } diff --git a/common/net/tcpip.go b/common/net/tcpip.go index 0499e54c..65a67b99 100644 --- a/common/net/tcpip.go +++ b/common/net/tcpip.go @@ -50,6 +50,7 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) { func TCPKeepAlive(c net.Conn) { if tcp, ok := c.(*net.TCPConn); ok { + fmt.Println(KeepAliveInterval) _ = tcp.SetKeepAlive(true) _ = tcp.SetKeepAlivePeriod(KeepAliveInterval) } diff --git a/common/utils/time.go b/common/utils/time.go new file mode 100644 index 00000000..5a68409a --- /dev/null +++ b/common/utils/time.go @@ -0,0 +1,31 @@ +package utils + +import ( + "strconv" + "time" +) + +func ParseDuration(interval string, unit string) time.Duration { + var Duration time.Duration + switch unit { + case "ms": + _, err := strconv.Atoi(interval) + if err == nil { + interval += "ms" + } + Duration, _ = time.ParseDuration(interval) + case "s": + _, err := strconv.Atoi(interval) + if err == nil { + interval += "s" + } + Duration, _ = time.ParseDuration(interval) + case "h": + _, err := strconv.Atoi(interval) + if err == nil { + interval += "h" + } + Duration, _ = time.ParseDuration(interval) + } + return Duration +} diff --git a/component/geodata/init.go b/component/geodata/init.go index 834567a4..64022f01 100644 --- a/component/geodata/init.go +++ b/component/geodata/init.go @@ -47,7 +47,7 @@ func InitGeoSite() error { func downloadGeoSite(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return } @@ -66,7 +66,7 @@ func downloadGeoSite(path string) (err error) { func downloadGeoIP(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return } diff --git a/component/geodata/utils.go b/component/geodata/utils.go index 981d7eba..9b1063c8 100644 --- a/component/geodata/utils.go +++ b/component/geodata/utils.go @@ -15,7 +15,7 @@ import ( var ( geoMode bool AutoUpdate bool - UpdateInterval int + UpdateInterval string geoLoaderName = "memconservative" geoSiteMatcher = "succinct" ) @@ -30,7 +30,7 @@ func GeoAutoUpdate() bool { return AutoUpdate } -func GeoUpdateInterval() int { +func GeoUpdateInterval() string { return UpdateInterval } @@ -48,7 +48,7 @@ func SetGeodataMode(newGeodataMode bool) { func SetGeoAutoUpdate(newAutoUpdate bool) { AutoUpdate = newAutoUpdate } -func SetGeoUpdateInterval(newGeoUpdateInterval int) { +func SetGeoUpdateInterval(newGeoUpdateInterval string) { UpdateInterval = newGeoUpdateInterval } @@ -144,7 +144,7 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) { /** linear: linear algorithm matcher, err := router.NewDomainMatcher(domains) - mph:minimal perfect hash algorithm + mph: minimal perfect hash algorithm */ var matcher router.DomainMatcher if geoSiteMatcher == "mph" { diff --git a/component/http/http.go b/component/http/http.go index 455db681..ef37e328 100644 --- a/component/http/http.go +++ b/component/http/http.go @@ -16,8 +16,7 @@ import ( "github.com/metacubex/mihomo/listener/inner" ) -func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { - UA := C.UA +func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) { method = strings.ToUpper(method) urlRes, err := URL.Parse(url) if err != nil { @@ -32,7 +31,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st } if _, ok := header["User-Agent"]; !ok { - req.Header.Set("User-Agent", UA) + req.Header.Set("User-Agent", C.UA) } if err != nil { @@ -54,7 +53,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - if conn, err := inner.HandleTcp(address); err == nil { + if conn, err := inner.HandleTcp(address, specialProxy); err == nil { return conn, nil } else { d := net.Dialer{} @@ -66,5 +65,4 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st client := http.Client{Transport: transport} return client.Do(req) - } diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 81156bc6..120739fc 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -82,7 +82,7 @@ func IPInstance() IPReader { func DownloadMMDB(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return } @@ -115,7 +115,7 @@ func ASNInstance() ASNReader { func DownloadASN(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return } diff --git a/component/resource/vehicle.go b/component/resource/vehicle.go index b2e29418..2f71c2e6 100644 --- a/component/resource/vehicle.go +++ b/component/resource/vehicle.go @@ -33,8 +33,10 @@ func NewFileVehicle(path string) *FileVehicle { } type HTTPVehicle struct { - url string - path string + url string + path string + proxy string + header http.Header } func (h *HTTPVehicle) Url() string { @@ -52,7 +54,7 @@ func (h *HTTPVehicle) Path() string { func (h *HTTPVehicle) Read() ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil) + resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, h.header, nil, h.proxy) if err != nil { return nil, err } @@ -67,6 +69,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) { return buf, nil } -func NewHTTPVehicle(url string, path string) *HTTPVehicle { - return &HTTPVehicle{url, path} +func NewHTTPVehicle(url string, path string, proxy string, header http.Header) *HTTPVehicle { + return &HTTPVehicle{url, path, proxy, header} } diff --git a/config/config.go b/config/config.go index c7931573..6117503c 100644 --- a/config/config.go +++ b/config/config.go @@ -56,7 +56,7 @@ type General struct { RoutingMark int `json:"-"` GeoXUrl GeoXUrl `json:"geox-url"` GeoAutoUpdate bool `json:"geo-auto-update"` - GeoUpdateInterval int `json:"geo-update-interval"` + GeoUpdateInterval string `json:"geo-update-interval"` GeodataMode bool `json:"geodata-mode"` GeodataLoader string `json:"geodata-loader"` GeositeMatcher string `json:"geosite-matcher"` @@ -313,7 +313,7 @@ type RawConfig struct { RoutingMark int `yaml:"routing-mark"` Tunnels []LC.Tunnel `yaml:"tunnels"` GeoAutoUpdate bool `yaml:"geo-auto-update" json:"geo-auto-update"` - GeoUpdateInterval int `yaml:"geo-update-interval" json:"geo-update-interval"` + GeoUpdateInterval string `yaml:"geo-update-interval" json:"geo-update-interval"` GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"` GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"` GeositeMatcher string `yaml:"geosite-matcher" json:"geosite-matcher"` @@ -321,7 +321,7 @@ type RawConfig struct { FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` GlobalClientFingerprint string `yaml:"global-client-fingerprint"` GlobalUA string `yaml:"global-ua"` - KeepAliveInterval int `yaml:"keep-alive-interval"` + KeepAliveInterval string `yaml:"keep-alive-interval"` Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -401,7 +401,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { IPv6: true, Mode: T.Rule, GeoAutoUpdate: false, - GeoUpdateInterval: 24, + GeoUpdateInterval: "24h", GeodataMode: C.GeodataMode, GeodataLoader: "memconservative", UnifiedDelay: false, @@ -413,7 +413,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { ProxyGroup: []map[string]any{}, TCPConcurrent: false, FindProcessMode: P.FindProcessStrict, - GlobalUA: "clash.meta", + GlobalUA: "clash.meta/" + C.Version, Tun: RawTun{ Enable: false, Device: "", @@ -631,8 +631,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) { C.ASNUrl = cfg.GeoXUrl.ASN C.GeodataMode = cfg.GeodataMode C.UA = cfg.GlobalUA - if cfg.KeepAliveInterval != 0 { - N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second + if cfg.KeepAliveInterval != "" { + N.KeepAliveInterval = utils.ParseDuration(cfg.KeepAliveInterval, "s") } ExternalUIPath = cfg.ExternalUI diff --git a/config/utils.go b/config/utils.go index 66bf3441..596199ca 100644 --- a/config/utils.go +++ b/config/utils.go @@ -20,7 +20,7 @@ import ( func downloadForBytes(url string) ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return nil, err } diff --git a/constant/geodata.go b/constant/geodata.go index cd3f74e3..35a63257 100644 --- a/constant/geodata.go +++ b/constant/geodata.go @@ -4,7 +4,7 @@ var ( ASNEnable bool GeodataMode bool GeoAutoUpdate bool - GeoUpdateInterval int + GeoUpdateInterval string GeoIpUrl string MmdbUrl string GeoSiteUrl string diff --git a/docs/config.yaml b/docs/config.yaml index 6aa06862..869b5231 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -8,15 +8,15 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口 allow-lan: true # 允许局域网连接 bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址 -authentication: # http,socks入口的验证用户名,密码 +authentication: # http,socks 入口的验证用户名,密码 - "username:password" -skip-auth-prefixes: # 设置跳过验证的IP段 +skip-auth-prefixes: # 设置跳过验证的 IP 段 - 127.0.0.1/8 - ::1/128 -lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0 +lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为 0.0.0.0/0 和::/0 - 0.0.0.0/0 - ::/0 -lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空 +lan-disallowed-ips: # 禁止连接的 IP 地址段,黑名单优先级高于白名单,默认值为空 - 192.168.0.3/32 # find-process-mode has 3 values:always, strict, off @@ -109,9 +109,9 @@ tun: # auto-detect-interface: true # 自动识别出口网卡 # auto-route: true # 配置路由表 # mtu: 9000 # 最大传输单元 - # gso: false # 启用通用分段卸载, 仅支持 Linux + # gso: false # 启用通用分段卸载,仅支持 Linux # gso-max-size: 65536 # 通用分段卸载包的最大大小 - # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 + # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - 0.0.0.0/1 - 128.0.0.0/1 @@ -119,9 +119,9 @@ tun: - "::/1" - "8000::/1" # endpoint-independent-nat: false # 启用独立于端点的 NAT - # include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突 + # include-interface: # 限制被路由的接口。默认不限制,与 `exclude-interface` 冲突 # - "lan0" - # exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突 + # exclude-interface: # 排除路由的接口,与 `include-interface` 冲突 # - "lan1" # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # - 0 @@ -143,7 +143,7 @@ tun: # exclude-package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin -#ebpf配置 +#ebpf 配置 ebpf: auto-redir: # redirect 模式,仅支持 TCP - eth0 @@ -200,7 +200,7 @@ tunnels: # one line config target: target.com proxy: proxy -# DNS配置 +# DNS 配置 dns: cache-algorithm: arc enable: false # 关闭将使用系统 DNS @@ -208,7 +208,7 @@ dns: listen: 0.0.0.0:53 # 开启 DNS 服务器监听 # ipv6: false # false 将返回 AAAA 的空结果 # ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms - # 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名 + # 用于解析 nameserver,fallback 以及其他 DNS 服务器配置的,DNS 服务域名 # 只能使用纯 IP 地址,可使用加密 DNS default-nameserver: - 114.114.114.114 @@ -222,12 +222,12 @@ dns: # use-hosts: true # 查询 hosts - # 配置不使用fake-ip的域名 + # 配置不使用 fake-ip 的域名 # fake-ip-filter: # - '*.lan' # - localhost.ptlogin2.qq.com - # DNS主要域名配置 + # DNS 主要域名配置 # 支持 UDP,TCP,DoT,DoH,DoQ # 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS nameserver: @@ -239,7 +239,7 @@ dns: - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3 - dhcp://en0 # dns from dhcp - quic://dns.adguard.com:784 # DNS over QUIC - # - '8.8.8.8#en0' # 兼容指定DNS出口网卡 + # - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡 # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置 # 当不是 CN,则使用 fallback 中的 DNS 查询结果 @@ -338,7 +338,7 @@ proxies: # socks5 # udp-over-tcp: false # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 - # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, + # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, # UDP 则为双栈解析,获取结果中的第一个 IPv4 # ipv6-prefer 同 ipv4-prefer # 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效 @@ -350,7 +350,7 @@ proxies: # socks5 # max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams. # padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later. # statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接 - # only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议 + # only-tcp: false # 如果设置为 true, smux 的设置将不会对 udp 生效,udp 连接会直接走底层协议 - name: "ss2" type: ss @@ -406,18 +406,18 @@ proxies: # socks5 password: [YOUR_SS_PASSWORD] client-fingerprint: chrome # One of: chrome, ios, firefox or safari - # 可以是chrome, ios, firefox, safari中的一个 + # 可以是 chrome, ios, firefox, safari 中的一个 plugin: restls plugin-opts: host: "www.microsoft.com" # Must be a TLS 1.3 server - # 应当是一个TLS 1.3 服务器 + # 应当是一个 TLS 1.3 服务器 password: [YOUR_RESTLS_PASSWORD] version-hint: "tls13" # Control your post-handshake traffic through restls-script # Hide proxy behaviors like "tls in tls". # see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md - # 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征 + # 用 restls 剧本来控制握手后的行为,隐藏"tls in tls"等特征 # 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100" @@ -429,18 +429,18 @@ proxies: # socks5 password: [YOUR_SS_PASSWORD] client-fingerprint: chrome # One of: chrome, ios, firefox or safari - # 可以是chrome, ios, firefox, safari中的一个 + # 可以是 chrome, ios, firefox, safari 中的一个 plugin: restls plugin-opts: host: "vscode.dev" # Must be a TLS 1.2 server - # 应当是一个TLS 1.2 服务器 + # 应当是一个 TLS 1.2 服务器 password: [YOUR_RESTLS_PASSWORD] version-hint: "tls12" restls-script: "1000?100<1,500~100,350~100,600~100,400~200" # vmess - # cipher支持 auto/aes-128-gcm/chacha20-poly1305/none + # cipher 支持 auto/aes-128-gcm/chacha20-poly1305/none - name: "vmess" type: vmess server: server @@ -680,11 +680,11 @@ proxies: # socks5 port: 443 # ports: 1000,2000-3000,5000 # port 不可省略 # hop-interval: 15 - # up和down均不写或为0则使用BBR流控 + # up 和 down 均不写或为 0 则使用 BBR 流控 # up: "30 Mbps" # 若不写单位,默认为 Mbps # down: "200 Mbps" # 若不写单位,默认为 Mbps password: yourpassword - # obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander + # obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander # obfs-password: yourpassword # sni: server.com # skip-cert-verify: false @@ -710,9 +710,9 @@ proxies: # socks5 # reserved: [209,98,59] # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接 # dialer-proxy: "ss1" - # remote-dns-resolve: true # 强制dns远程解析,默认值为false - # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 - # 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 + # remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false + # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve 为 true 时生效 + # 如果 peers 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1 # port: 2480 @@ -726,9 +726,9 @@ proxies: # socks5 server: www.example.com port: 10443 type: tuic - # tuicV4必须填写token (不可同时填写uuid和password) + # tuicV4 必须填写 token(不可同时填写 uuid 和 password) token: TOKEN - # tuicV5必须填写uuid和password(不可同时填写token) + # tuicV5 必须填写 uuid 和 password(不可同时填写 token) uuid: 00000000-0000-0000-0000-000000000001 password: PASSWORD_1 # ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server' @@ -746,8 +746,8 @@ proxies: # socks5 # max-open-streams: 20 # default 100, too many open streams may hurt performance # sni: example.com # - # meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效 - # 警告,与原版tuic不兼容!!! + # meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效 + # 警告,与原版 tuic 不兼容!!! # udp-over-stream: false # udp-over-stream-version: 1 @@ -780,12 +780,12 @@ proxies: # socks5 password: password privateKey: path -# dns出站会将请求劫持到内部dns模块,所有请求均在内部处理 +# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理 - name: "dns-out" type: dns proxy-groups: - # 代理链,目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic - # wireguard目前不支持在relay中使用,请使用proxy中的dialer-proxy配置项 + # 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic + # wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项 # Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet - name: "relay" type: relay @@ -859,10 +859,19 @@ proxy-groups: # Mihomo 格式的节点或支持 *ray 的分享格式 proxy-providers: provider1: - type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5 + type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5 url: "url" interval: 3600 path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 + proxy: DIRECT + header: + User-Agent: + - "Clash/v1.18.0" + - "mihomo/1.18.3" + # Accept: + # - 'application/vnd.github.v3.raw' + # Authorization: + # - 'token 1231231' health-check: enable: true interval: 600 @@ -892,8 +901,9 @@ rule-providers: behavior: classical # domain ipcidr interval: 259200 path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 - type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5 + type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5 url: "url" + proxy: DIRECT rule2: behavior: classical interval: 259200 @@ -942,7 +952,7 @@ listeners: port: 10808 #listen: 0.0.0.0 # 默认监听 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理 + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 # udp: false # 默认 true - name: http-in-1 @@ -950,14 +960,14 @@ listeners: port: 10809 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) - name: mixed-in-1 type: mixed # HTTP(S) 和 SOCKS 代理混合 port: 10810 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) # udp: false # 默认 true - name: reidr-in-1 @@ -965,14 +975,14 @@ listeners: port: 10811 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) - name: tproxy-in-1 type: tproxy port: 10812 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) # udp: false # 默认 true - name: shadowsocks-in-1 @@ -980,7 +990,7 @@ listeners: port: 10813 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg= cipher: 2022-blake3-aes-256-gcm @@ -989,13 +999,13 @@ listeners: port: 10814 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) users: - username: 1 uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 alterId: 1 - # ws-path: "/" # 如果不为空则开启websocket传输层 - # 下面两项如果填写则开启tls(需要同时填写) + # ws-path: "/" # 如果不为空则开启 websocket 传输层 + # 下面两项如果填写则开启 tls(需要同时填写) # certificate: ./server.crt # private-key: ./server.key @@ -1004,10 +1014,10 @@ listeners: port: 10815 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - # token: # tuicV4填写(可以同时填写users) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) + # token: # tuicV4 填写(可以同时填写 users) # - TOKEN - # users: # tuicV5填写(可以同时填写token) + # users: # tuicV5 填写(可以同时填写 token) # 00000000-0000-0000-0000-000000000000: PASSWORD_0 # 00000000-0000-0000-0000-000000000001: PASSWORD_1 # certificate: ./server.crt @@ -1024,25 +1034,25 @@ listeners: port: 10816 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) network: [tcp, udp] target: target.com - name: tun-in-1 type: tun # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) stack: system # gvisor / mixed dns-hijack: - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: false # 自动识别出口网卡 # auto-route: false # 配置路由表 # mtu: 9000 # 最大传输单元 - inet4-address: # 必须手动设置ipv4地址段 + inet4-address: # 必须手动设置 ipv4 地址段 - 198.19.0.1/30 - inet6-address: # 必须手动设置ipv6地址段 + inet6-address: # 必须手动设置 ipv6 地址段 - "fdfe:dcba:9877::1/126" - # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 + # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 # - 0.0.0.0/1 # - 128.0.0.0/1 @@ -1070,17 +1080,17 @@ listeners: # exclude-package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin # 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理 -# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) +# shadowsocks,vmess 入口配置(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理) # ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456 # vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345 -# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) +# tuic 服务器入口(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理) # tuic-server: # enable: true # listen: 127.0.0.1:10443 -# token: # tuicV4填写(可以同时填写users) +# token: # tuicV4 填写(可以同时填写 users) # - TOKEN -# users: # tuicV5填写(可以同时填写token) +# users: # tuicV5 填写(可以同时填写 token) # 00000000-0000-0000-0000-000000000000: PASSWORD_0 # 00000000-0000-0000-0000-000000000001: PASSWORD_1 # certificate: ./server.crt diff --git a/go.mod b/go.mod index 78f50d02..7270049a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.0 - github.com/metacubex/sing-tun v0.2.1-0.20240402145739-0223b8bb1c85 + github.com/metacubex/sing-tun v0.2.1-0.20240405021556-f37a4aa3d060 github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 diff --git a/go.sum b/go.sum index 12c207e4..4bddc40e 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwV github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.1-0.20240402145739-0223b8bb1c85 h1:r7XXIvooixabmv2Ry95I1Xv3T0c+9VWtes9LhkXGg34= -github.com/metacubex/sing-tun v0.2.1-0.20240402145739-0223b8bb1c85/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= +github.com/metacubex/sing-tun v0.2.1-0.20240405021556-f37a4aa3d060 h1:SEkMqQlInU4KoyaISvEPKEzhDw0CnTr2TvIuj/hmEQ0= +github.com/metacubex/sing-tun v0.2.1-0.20240405021556-f37a4aa3d060/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= diff --git a/hub/updater/updater.go b/hub/updater/updater.go index 02ff07ba..e0d3a39c 100644 --- a/hub/updater/updater.go +++ b/hub/updater/updater.go @@ -234,7 +234,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024 func downloadPackageFile() (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return fmt.Errorf("http request failed: %w", err) } @@ -415,7 +415,7 @@ func copyFile(src, dst string) error { func getLatestVersion() (version string, err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "") if err != nil { return "", fmt.Errorf("get Latest Version fail: %w", err) } diff --git a/listener/inner/tcp.go b/listener/inner/tcp.go index dac3d721..ee34ada6 100644 --- a/listener/inner/tcp.go +++ b/listener/inner/tcp.go @@ -16,7 +16,7 @@ func New(t C.Tunnel) { tunnel = t } -func HandleTcp(address string) (conn net.Conn, err error) { +func HandleTcp(address string, proxy string) (conn net.Conn, err error) { if tunnel == nil { return nil, errors.New("tcp uninitialized") } @@ -28,6 +28,9 @@ func HandleTcp(address string) (conn net.Conn, err error) { metadata.Type = C.INNER metadata.DNSMode = C.DNSNormal metadata.Process = C.MihomoName + if proxy != "" { + metadata.SpecialProxy = proxy + } if h, port, err := net.SplitHostPort(address); err == nil { if port, err := strconv.ParseUint(port, 10, 16); err == nil { metadata.DstPort = uint16(port) diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index efb144a9..fa7e7dbe 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -34,6 +34,8 @@ func (l *Listener) Close() error { func (l *Listener) handleTProxy(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) N.TCPKeepAlive(conn) + // TProxy's conn.LocalAddr() is target address, so we set from l.listener + additions = append([]inbound.Addition{inbound.WithInAddr(l.listener.Addr())}, additions...) tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TPROXY, additions...)) } diff --git a/main.go b/main.go index 748fa2e3..85329a4d 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/config" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/features" @@ -111,9 +112,9 @@ func main() { } if C.GeoAutoUpdate { - ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) + ticker := time.NewTicker(utils.ParseDuration(C.GeoUpdateInterval, "h")) - log.Infoln("[GEO] Start update GEO database every %d hours", C.GeoUpdateInterval) + log.Infoln("[GEO] Start update GEO database every %s", utils.ParseDuration(C.GeoUpdateInterval, "h")) go func() { for range ticker.C { updateGeoDatabases() diff --git a/rules/provider/parse.go b/rules/provider/parse.go index a867d570..4e82d70d 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -6,6 +6,7 @@ import ( "time" "github.com/metacubex/mihomo/common/structure" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/resource" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/features" @@ -21,8 +22,9 @@ type ruleProviderSchema struct { Behavior string `provider:"behavior"` Path string `provider:"path,omitempty"` URL string `provider:"url,omitempty"` + Proxy string `provider:"proxy,omitempty"` Format string `provider:"format,omitempty"` - Interval int `provider:"interval,omitempty"` + Interval string `provider:"interval,omitempty"` } func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) { @@ -55,26 +57,28 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t return nil, fmt.Errorf("unsupported format type: %s", schema.Format) } + var interval time.Duration + if schema.Interval != "" { + interval = utils.ParseDuration(schema.Interval, "s") + } + var vehicle P.Vehicle switch schema.Type { case "file": path := C.Path.Resolve(schema.Path) vehicle = resource.NewFileVehicle(path) case "http": + path := C.Path.GetPathByHash("rules", schema.URL) if schema.Path != "" { - path := C.Path.Resolve(schema.Path) + path = C.Path.Resolve(schema.Path) if !features.CMFA && !C.Path.IsSafePath(path) { return nil, fmt.Errorf("%w: %s", errSubPath, path) } - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } else { - path := C.Path.GetPathByHash("rules", schema.URL) - vehicle = resource.NewHTTPVehicle(schema.URL, path) } - + vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil) default: return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) } - return NewRuleSetProvider(name, behavior, format, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil + return NewRuleSetProvider(name, behavior, format, interval, vehicle, parse), nil }