Feature: add regexp filter to use proxy provider in proxy group
This commit is contained in:
35
README.md
35
README.md
@ -216,7 +216,9 @@ Support `Trojan` with XTLS.
|
|||||||
|
|
||||||
Support relay `UDP` traffic.
|
Support relay `UDP` traffic.
|
||||||
|
|
||||||
Currently XTLS only supports TCP transport.
|
Support filtering proxy providers in proxy groups.
|
||||||
|
|
||||||
|
Support custom http request header, prefix name and V2Ray subscription URL in proxy providers.
|
||||||
```yaml
|
```yaml
|
||||||
proxies:
|
proxies:
|
||||||
# VLESS
|
# VLESS
|
||||||
@ -272,6 +274,37 @@ proxy-groups:
|
|||||||
- ss1
|
- ss1
|
||||||
- ss2
|
- ss2
|
||||||
- ss3
|
- ss3
|
||||||
|
|
||||||
|
- name: "filtering-proxy-providers"
|
||||||
|
type: url-test
|
||||||
|
url: "http://www.gstatic.com/generate_204"
|
||||||
|
interval: 300
|
||||||
|
tolerance: 200
|
||||||
|
# lazy: true
|
||||||
|
filter: "XXX" # a regular expression
|
||||||
|
use:
|
||||||
|
- provider1
|
||||||
|
|
||||||
|
proxy-providers:
|
||||||
|
provider1:
|
||||||
|
type: http
|
||||||
|
url: "url" # support V2Ray subscription URL
|
||||||
|
interval: 3600
|
||||||
|
path: ./providers/provider1.yaml
|
||||||
|
# filter: "xxx"
|
||||||
|
# prefix-name: "XXX-"
|
||||||
|
header: # custom http request header
|
||||||
|
User-Agent:
|
||||||
|
- "Clash/v1.10.6"
|
||||||
|
# Accept:
|
||||||
|
# - 'application/vnd.github.v3.raw'
|
||||||
|
# Authorization:
|
||||||
|
# - ' token xxxxxxxxxxx'
|
||||||
|
health-check:
|
||||||
|
enable: false
|
||||||
|
interval: 1200
|
||||||
|
# lazy: false # default value is true
|
||||||
|
url: http://www.gstatic.com/generate_204
|
||||||
```
|
```
|
||||||
|
|
||||||
### IPTABLES configuration
|
### IPTABLES configuration
|
||||||
|
@ -3,6 +3,7 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
@ -29,6 +30,7 @@ type GroupCommonOption struct {
|
|||||||
Interval int `group:"interval,omitempty"`
|
Interval int `group:"interval,omitempty"`
|
||||||
Lazy bool `group:"lazy,omitempty"`
|
Lazy bool `group:"lazy,omitempty"`
|
||||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||||
|
Filter string `group:"filter,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||||
@ -37,10 +39,23 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
|||||||
groupOption := &GroupCommonOption{
|
groupOption := &GroupCommonOption{
|
||||||
Lazy: true,
|
Lazy: true,
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(config, groupOption); err != nil {
|
|
||||||
|
var (
|
||||||
|
filterRegx *regexp.Regexp
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = decoder.Decode(config, groupOption); err != nil {
|
||||||
return nil, errFormat
|
return nil, errFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if groupOption.Filter != "" {
|
||||||
|
filterRegx, err = regexp.Compile(groupOption.Filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if groupOption.Type == "" || groupOption.Name == "" {
|
if groupOption.Type == "" || groupOption.Name == "" {
|
||||||
return nil, errFormat
|
return nil, errFormat
|
||||||
}
|
}
|
||||||
@ -90,7 +105,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(groupOption.Use) != 0 {
|
if len(groupOption.Use) != 0 {
|
||||||
list, err := getProviders(providersMap, groupOption.Use)
|
list, err := getProviders(providersMap, groupOption, filterRegx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -130,8 +145,13 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
|
|||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
|
func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupCommonOption, filterRegx *regexp.Regexp) ([]types.ProxyProvider, error) {
|
||||||
var ps []types.ProxyProvider
|
var (
|
||||||
|
ps []types.ProxyProvider
|
||||||
|
list = groupOption.Use
|
||||||
|
groupName = groupOption.Name
|
||||||
|
)
|
||||||
|
|
||||||
for _, name := range list {
|
for _, name := range list {
|
||||||
p, ok := mapping[name]
|
p, ok := mapping[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -141,6 +161,27 @@ func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]type
|
|||||||
if p.VehicleType() == types.Compatible {
|
if p.VehicleType() == types.Compatible {
|
||||||
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
|
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filterRegx != nil {
|
||||||
|
var hc *provider.HealthCheck
|
||||||
|
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||||
|
hc = provider.NewHealthCheck([]C.Proxy{}, "", 0, true)
|
||||||
|
} else {
|
||||||
|
if groupOption.URL == "" || groupOption.Interval == 0 {
|
||||||
|
return nil, errMissHealthCheck
|
||||||
|
}
|
||||||
|
hc = provider.NewHealthCheck([]C.Proxy{}, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok = mapping[groupName]; ok {
|
||||||
|
groupName += "->" + p.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
pd := p.(*provider.ProxySetProvider)
|
||||||
|
p = provider.NewProxyFilterProvider(groupName, pd, hc, filterRegx)
|
||||||
|
pd.RegisterProvidersInUse(p)
|
||||||
|
}
|
||||||
|
|
||||||
ps = append(ps, p)
|
ps = append(ps, p)
|
||||||
}
|
}
|
||||||
return ps, nil
|
return ps, nil
|
||||||
|
@ -65,8 +65,13 @@ func (hc *HealthCheck) touch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) check() {
|
func (hc *HealthCheck) check() {
|
||||||
|
proxies := hc.proxies
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||||
for _, proxy := range hc.proxies {
|
for _, proxy := range proxies {
|
||||||
p := proxy
|
p := proxy
|
||||||
b.Go(p.Name(), func() (bool, error) {
|
b.Go(p.Name(), func() (bool, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||||
|
@ -31,8 +31,9 @@ type ProxySetProvider struct {
|
|||||||
|
|
||||||
type proxySetProvider struct {
|
type proxySetProvider struct {
|
||||||
*fetcher[[]C.Proxy]
|
*fetcher[[]C.Proxy]
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
healthCheck *HealthCheck
|
healthCheck *HealthCheck
|
||||||
|
providersInUse []types.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||||
@ -87,11 +88,16 @@ func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
|
|||||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||||
pp.proxies = proxies
|
pp.proxies = proxies
|
||||||
pp.healthCheck.setProxy(proxies)
|
pp.healthCheck.setProxy(proxies)
|
||||||
if pp.healthCheck.auto() {
|
|
||||||
go pp.healthCheck.check()
|
for _, use := range pp.providersInUse {
|
||||||
|
_ = use.Update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *proxySetProvider) RegisterProvidersInUse(providers ...types.ProxyProvider) {
|
||||||
|
pp.providersInUse = append(pp.providersInUse, providers...)
|
||||||
|
}
|
||||||
|
|
||||||
func stopProxyProvider(pd *ProxySetProvider) {
|
func stopProxyProvider(pd *ProxySetProvider) {
|
||||||
pd.healthCheck.close()
|
pd.healthCheck.close()
|
||||||
_ = pd.fetcher.Destroy()
|
_ = pd.fetcher.Destroy()
|
||||||
@ -197,6 +203,98 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
|||||||
return wrapper, nil
|
return wrapper, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxyFilterProvider for filter provider
|
||||||
|
type ProxyFilterProvider struct {
|
||||||
|
*proxyFilterProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxyFilterProvider struct {
|
||||||
|
name string
|
||||||
|
psd *ProxySetProvider
|
||||||
|
proxies []C.Proxy
|
||||||
|
filter *regexp.Regexp
|
||||||
|
healthCheck *HealthCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]any{
|
||||||
|
"name": pf.Name(),
|
||||||
|
"type": pf.Type().String(),
|
||||||
|
"vehicleType": pf.VehicleType().String(),
|
||||||
|
"proxies": pf.Proxies(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) Name() string {
|
||||||
|
return pf.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) HealthCheck() {
|
||||||
|
pf.healthCheck.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) Update() error {
|
||||||
|
var proxies []C.Proxy
|
||||||
|
if pf.filter != nil {
|
||||||
|
for _, proxy := range pf.psd.Proxies() {
|
||||||
|
if !pf.filter.MatchString(proxy.Name()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxies = append(proxies, proxy)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxies = pf.psd.Proxies()
|
||||||
|
}
|
||||||
|
|
||||||
|
pf.proxies = proxies
|
||||||
|
pf.healthCheck.setProxy(proxies)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) Initial() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) VehicleType() types.VehicleType {
|
||||||
|
return pf.psd.VehicleType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) Type() types.ProviderType {
|
||||||
|
return types.Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) Proxies() []C.Proxy {
|
||||||
|
return pf.proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *proxyFilterProvider) ProxiesWithTouch() []C.Proxy {
|
||||||
|
pf.healthCheck.touch()
|
||||||
|
return pf.Proxies()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopProxyFilterProvider(pf *ProxyFilterProvider) {
|
||||||
|
pf.healthCheck.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyFilterProvider(name string, psd *ProxySetProvider, hc *HealthCheck, filterRegx *regexp.Regexp) *ProxyFilterProvider {
|
||||||
|
pd := &proxyFilterProvider{
|
||||||
|
psd: psd,
|
||||||
|
name: name,
|
||||||
|
healthCheck: hc,
|
||||||
|
filter: filterRegx,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = pd.Update()
|
||||||
|
|
||||||
|
if hc.auto() {
|
||||||
|
go hc.process()
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := &ProxyFilterProvider{pd}
|
||||||
|
runtime.SetFinalizer(wrapper, stopProxyFilterProvider)
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||||
return func(elm []C.Proxy) {
|
return func(elm []C.Proxy) {
|
||||||
pd.setProxies(elm)
|
pd.setProxies(elm)
|
||||||
|
@ -441,7 +441,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, proxyProvider := range providersMap {
|
for _, proxyProvider := range providersMap {
|
||||||
log.Infoln("Start initial provider %s", proxyProvider.Name())
|
log.Infoln("Start initial proxy provider %s", proxyProvider.Name())
|
||||||
if err := proxyProvider.Initial(); err != nil {
|
if err := proxyProvider.Initial(); err != nil {
|
||||||
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", proxyProvider.Name(), err)
|
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", proxyProvider.Name(), err)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@ -43,6 +44,10 @@ func findProxyByName(next http.Handler) http.Handler {
|
|||||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||||
proxies := tunnel.Proxies()
|
proxies := tunnel.Proxies()
|
||||||
proxy, exist := proxies[name]
|
proxy, exist := proxies[name]
|
||||||
|
if !exist {
|
||||||
|
proxy, exist = findProxyInNonCompatibleProviderByName(name)
|
||||||
|
}
|
||||||
|
|
||||||
if !exist {
|
if !exist {
|
||||||
render.Status(r, http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
render.JSON(w, r, ErrNotFound)
|
render.JSON(w, r, ErrNotFound)
|
||||||
@ -128,3 +133,20 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
"delay": delay,
|
"delay": delay,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findProxyInNonCompatibleProviderByName(name string) (proxy C.Proxy, found bool) {
|
||||||
|
providers := tunnel.Providers()
|
||||||
|
for _, pd := range providers {
|
||||||
|
if pd.VehicleType() == provider.Compatible {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, pp := range pd.Proxies() {
|
||||||
|
found = pp.Name() == name
|
||||||
|
if found {
|
||||||
|
proxy = pp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user