1package provider 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "runtime" 8 "time" 9 10 "github.com/Dreamacro/clash/adapters/outbound" 11 C "github.com/Dreamacro/clash/constant" 12 13 "gopkg.in/yaml.v2" 14) 15 16const ( 17 ReservedName = "default" 18) 19 20// Provider Type 21const ( 22 Proxy ProviderType = iota 23 Rule 24) 25 26// ProviderType defined 27type ProviderType int 28 29func (pt ProviderType) String() string { 30 switch pt { 31 case Proxy: 32 return "Proxy" 33 case Rule: 34 return "Rule" 35 default: 36 return "Unknown" 37 } 38} 39 40// Provider interface 41type Provider interface { 42 Name() string 43 VehicleType() VehicleType 44 Type() ProviderType 45 Initial() error 46 Update() error 47} 48 49// ProxyProvider interface 50type ProxyProvider interface { 51 Provider 52 Proxies() []C.Proxy 53 // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. 54 // Commonly used in Dial and DialUDP 55 ProxiesWithTouch() []C.Proxy 56 HealthCheck() 57} 58 59type ProxySchema struct { 60 Proxies []map[string]interface{} `yaml:"proxies"` 61} 62 63// for auto gc 64type ProxySetProvider struct { 65 *proxySetProvider 66} 67 68type proxySetProvider struct { 69 *fetcher 70 proxies []C.Proxy 71 healthCheck *HealthCheck 72} 73 74func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { 75 return json.Marshal(map[string]interface{}{ 76 "name": pp.Name(), 77 "type": pp.Type().String(), 78 "vehicleType": pp.VehicleType().String(), 79 "proxies": pp.Proxies(), 80 "updatedAt": pp.updatedAt, 81 }) 82} 83 84func (pp *proxySetProvider) Name() string { 85 return pp.name 86} 87 88func (pp *proxySetProvider) HealthCheck() { 89 pp.healthCheck.check() 90} 91 92func (pp *proxySetProvider) Update() error { 93 elm, same, err := pp.fetcher.Update() 94 if err == nil && !same { 95 pp.onUpdate(elm) 96 } 97 return err 98} 99 100func (pp *proxySetProvider) Initial() error { 101 elm, err := pp.fetcher.Initial() 102 if err != nil { 103 return err 104 } 105 106 pp.onUpdate(elm) 107 return nil 108} 109 110func (pp *proxySetProvider) Type() ProviderType { 111 return Proxy 112} 113 114func (pp *proxySetProvider) Proxies() []C.Proxy { 115 return pp.proxies 116} 117 118func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy { 119 pp.healthCheck.touch() 120 return pp.Proxies() 121} 122 123func proxiesParse(buf []byte) (interface{}, error) { 124 schema := &ProxySchema{} 125 126 if err := yaml.Unmarshal(buf, schema); err != nil { 127 return nil, err 128 } 129 130 if schema.Proxies == nil { 131 return nil, errors.New("file must have a `proxies` field") 132 } 133 134 proxies := []C.Proxy{} 135 for idx, mapping := range schema.Proxies { 136 proxy, err := outbound.ParseProxy(mapping) 137 if err != nil { 138 return nil, fmt.Errorf("proxy %d error: %w", idx, err) 139 } 140 proxies = append(proxies, proxy) 141 } 142 143 if len(proxies) == 0 { 144 return nil, errors.New("file doesn't have any valid proxy") 145 } 146 147 return proxies, nil 148} 149 150func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { 151 pp.proxies = proxies 152 pp.healthCheck.setProxy(proxies) 153 if pp.healthCheck.auto() { 154 go pp.healthCheck.check() 155 } 156} 157 158func stopProxyProvider(pd *ProxySetProvider) { 159 pd.healthCheck.close() 160 pd.fetcher.Destroy() 161} 162 163func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { 164 if hc.auto() { 165 go hc.process() 166 } 167 168 pd := &proxySetProvider{ 169 proxies: []C.Proxy{}, 170 healthCheck: hc, 171 } 172 173 onUpdate := func(elm interface{}) { 174 ret := elm.([]C.Proxy) 175 pd.setProxies(ret) 176 } 177 178 fetcher := newFetcher(name, interval, vehicle, proxiesParse, onUpdate) 179 pd.fetcher = fetcher 180 181 wrapper := &ProxySetProvider{pd} 182 runtime.SetFinalizer(wrapper, stopProxyProvider) 183 return wrapper 184} 185 186// for auto gc 187type CompatibleProvider struct { 188 *compatibleProvider 189} 190 191type compatibleProvider struct { 192 name string 193 healthCheck *HealthCheck 194 proxies []C.Proxy 195} 196 197func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { 198 return json.Marshal(map[string]interface{}{ 199 "name": cp.Name(), 200 "type": cp.Type().String(), 201 "vehicleType": cp.VehicleType().String(), 202 "proxies": cp.Proxies(), 203 }) 204} 205 206func (cp *compatibleProvider) Name() string { 207 return cp.name 208} 209 210func (cp *compatibleProvider) HealthCheck() { 211 cp.healthCheck.check() 212} 213 214func (cp *compatibleProvider) Update() error { 215 return nil 216} 217 218func (cp *compatibleProvider) Initial() error { 219 return nil 220} 221 222func (cp *compatibleProvider) VehicleType() VehicleType { 223 return Compatible 224} 225 226func (cp *compatibleProvider) Type() ProviderType { 227 return Proxy 228} 229 230func (cp *compatibleProvider) Proxies() []C.Proxy { 231 return cp.proxies 232} 233 234func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy { 235 cp.healthCheck.touch() 236 return cp.Proxies() 237} 238 239func stopCompatibleProvider(pd *CompatibleProvider) { 240 pd.healthCheck.close() 241} 242 243func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { 244 if len(proxies) == 0 { 245 return nil, errors.New("Provider need one proxy at least") 246 } 247 248 if hc.auto() { 249 go hc.process() 250 } 251 252 pd := &compatibleProvider{ 253 name: name, 254 proxies: proxies, 255 healthCheck: hc, 256 } 257 258 wrapper := &CompatibleProvider{pd} 259 runtime.SetFinalizer(wrapper, stopCompatibleProvider) 260 return wrapper, nil 261} 262