Chore: merge branch 'with-tun' into plus-pro
This commit is contained in:
commit
dd95d335d9
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -103,11 +103,11 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
|
||||
- name: Delete workflow runs
|
||||
uses: GitRML/delete-workflow-runs@main
|
||||
with:
|
||||
retain_days: 1
|
||||
keep_minimum_runs: 2
|
||||
#- name: Delete workflow runs
|
||||
# uses: GitRML/delete-workflow-runs@main
|
||||
# with:
|
||||
# retain_days: 1
|
||||
# keep_minimum_runs: 2
|
||||
|
||||
- name: Remove old Releases
|
||||
uses: dev-drprasad/delete-older-releases@v0.2.0
|
||||
|
39
README.md
39
README.md
@ -99,7 +99,7 @@ mitm:
|
||||
```
|
||||
|
||||
### DNS configuration
|
||||
Support resolve ip with a proxy tunnel.
|
||||
Support resolve ip with a proxy tunnel or interface.
|
||||
|
||||
Support `geosite` with `fallback-filter`.
|
||||
|
||||
@ -119,8 +119,8 @@ Use `curl -X POST controllerip:port/cache/fakeip/flush` to flush persistence fak
|
||||
- https://doh.pub/dns-query
|
||||
- tls://223.5.5.5:853
|
||||
fallback:
|
||||
- 'tls://8.8.4.4:853#proxy or interface'
|
||||
- 'https://1.0.0.1/dns-query#Proxy' # append the proxy adapter name to the end of DNS URL with '#' prefix.
|
||||
- 'tls://8.8.4.4:853#Proxy'
|
||||
fallback-filter:
|
||||
geoip: false
|
||||
geosite:
|
||||
@ -325,7 +325,9 @@ Support `Trojan` with XTLS.
|
||||
|
||||
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
|
||||
proxies:
|
||||
# VLESS
|
||||
@ -381,6 +383,37 @@ proxy-groups:
|
||||
- ss1
|
||||
- ss2
|
||||
- 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
|
||||
|
@ -10,11 +10,10 @@ import (
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
type ShadowSocks struct {
|
||||
|
@ -8,12 +8,11 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
|
||||
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
||||
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||
)
|
||||
|
||||
type ShadowSocksR struct {
|
||||
|
@ -3,6 +3,7 @@ package outboundgroup
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
@ -29,6 +30,7 @@ type GroupCommonOption struct {
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,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) {
|
||||
@ -37,10 +39,23 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
groupOption := &GroupCommonOption{
|
||||
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
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
return nil, errFormat
|
||||
}
|
||||
@ -90,7 +105,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
}
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
list, err := getProviders(providersMap, groupOption, filterRegx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -130,8 +145,13 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
|
||||
var ps []types.ProxyProvider
|
||||
func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupCommonOption, filterRegx *regexp.Regexp) ([]types.ProxyProvider, error) {
|
||||
var (
|
||||
ps []types.ProxyProvider
|
||||
list = groupOption.Use
|
||||
groupName = groupOption.Name
|
||||
)
|
||||
|
||||
for _, name := range list {
|
||||
p, ok := mapping[name]
|
||||
if !ok {
|
||||
@ -141,6 +161,27 @@ func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]type
|
||||
if p.VehicleType() == types.Compatible {
|
||||
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)
|
||||
}
|
||||
return ps, nil
|
||||
|
@ -65,8 +65,13 @@ func (hc *HealthCheck) touch() {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
proxies := hc.proxies
|
||||
if len(proxies) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range hc.proxies {
|
||||
for _, proxy := range proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
|
@ -20,13 +20,15 @@ type healthCheckSchema struct {
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
ForceCertVerify bool `provider:"force-cert-verify,omitempty"`
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
ForceCertVerify bool `provider:"force-cert-verify,omitempty"`
|
||||
PrefixName string `provider:"prefix-name,omitempty"`
|
||||
Header map[string][]string `provider:"header,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify bool) (types.ProxyProvider, error) {
|
||||
@ -59,12 +61,12 @@ func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify boo
|
||||
case "file":
|
||||
vehicle = NewFileVehicle(path)
|
||||
case "http":
|
||||
vehicle = NewHTTPVehicle(schema.URL, path)
|
||||
vehicle = NewHTTPVehicle(schema.URL, path, schema.Header)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||
filter := schema.Filter
|
||||
return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify)
|
||||
return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify, schema.PrefixName)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
|
||||
@ -30,8 +31,9 @@ type ProxySetProvider struct {
|
||||
|
||||
type proxySetProvider struct {
|
||||
*fetcher[[]C.Proxy]
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
providersInUse []types.ProxyProvider
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
@ -86,17 +88,22 @@ func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = 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) {
|
||||
pd.healthCheck.close()
|
||||
_ = pd.fetcher.Destroy()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool) (*ProxySetProvider, error) {
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool, prefixName string) (*ProxySetProvider, error) {
|
||||
filterReg, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
@ -111,7 +118,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
||||
healthCheck: hc,
|
||||
}
|
||||
|
||||
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify), proxiesOnUpdate(pd))
|
||||
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify, prefixName), proxiesOnUpdate(pd))
|
||||
pd.fetcher = fetcher
|
||||
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
@ -196,18 +203,114 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||
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) {
|
||||
return func(elm []C.Proxy) {
|
||||
pd.setProxies(elm)
|
||||
}
|
||||
}
|
||||
|
||||
func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool) parser[[]C.Proxy] {
|
||||
func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool, prefixName string) parser[[]C.Proxy] {
|
||||
return func(buf []byte) ([]C.Proxy, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||
return nil, err
|
||||
proxies, err1 := convert.ConvertsV2Ray(buf)
|
||||
if err1 != nil {
|
||||
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
|
||||
}
|
||||
schema.Proxies = proxies
|
||||
}
|
||||
|
||||
if schema.Proxies == nil {
|
||||
@ -219,6 +322,11 @@ func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVer
|
||||
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefixName != "" {
|
||||
mapping["name"] = prefixName + mapping["name"].(string)
|
||||
}
|
||||
|
||||
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
|
@ -9,7 +9,9 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
@ -34,8 +36,9 @@ func NewFileVehicle(path string) *FileVehicle {
|
||||
}
|
||||
|
||||
type HTTPVehicle struct {
|
||||
url string
|
||||
path string
|
||||
url string
|
||||
path string
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Type() types.VehicleType {
|
||||
@ -60,11 +63,19 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if h.header != nil {
|
||||
req.Header = h.header
|
||||
}
|
||||
|
||||
if user := uri.User; user != nil {
|
||||
password, _ := user.Password()
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
|
||||
if req.UserAgent() == "" {
|
||||
convert.SetUserAgent(req)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
transport := &http.Transport{
|
||||
@ -73,8 +84,13 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
DialContext: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||
conn, err = dialer.DialContext(ctx, network, address) // with direct
|
||||
if err != nil {
|
||||
// fallback to tun if tun enabled
|
||||
conn, err = (&net.Dialer{Timeout: C.DefaultTCPTimeout}).Dial(network, address)
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
@ -83,7 +99,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@ -93,6 +111,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, header http.Header) *HTTPVehicle {
|
||||
return &HTTPVehicle{url, path, header}
|
||||
}
|
||||
|
344
common/convert/converter.go
Normal file
344
common/convert/converter.go
Normal file
@ -0,0 +1,344 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var enc = base64.StdEncoding
|
||||
|
||||
func DecodeBase64(buf []byte) ([]byte, error) {
|
||||
dBuf := make([]byte, enc.DecodedLen(len(buf)))
|
||||
n, err := enc.Decode(dBuf, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dBuf[:n], nil
|
||||
}
|
||||
|
||||
// DecodeBase64StringToString decode base64 string to string
|
||||
func DecodeBase64StringToString(s string) (string, error) {
|
||||
dBuf, err := enc.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(dBuf), nil
|
||||
}
|
||||
|
||||
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
|
||||
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
data, err := DecodeBase64(buf)
|
||||
if err != nil {
|
||||
data = buf
|
||||
}
|
||||
|
||||
arr := strings.Split(string(data), "\n")
|
||||
|
||||
proxies := make([]map[string]any, 0, len(arr))
|
||||
names := make(map[string]int, 200)
|
||||
|
||||
for _, line := range arr {
|
||||
line = strings.TrimRight(line, " \r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
scheme, body, found := strings.Cut(line, "://")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
scheme = strings.ToLower(scheme)
|
||||
switch scheme {
|
||||
case "trojan":
|
||||
urlTrojan, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
q := urlTrojan.Query()
|
||||
|
||||
name := uniqueName(names, urlTrojan.Fragment)
|
||||
trojan := make(map[string]any, 20)
|
||||
|
||||
trojan["name"] = name
|
||||
trojan["type"] = scheme
|
||||
trojan["server"] = urlTrojan.Hostname()
|
||||
trojan["port"] = urlTrojan.Port()
|
||||
trojan["password"] = urlTrojan.User.Username()
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"] = false
|
||||
|
||||
sni := q.Get("sni")
|
||||
if sni != "" {
|
||||
trojan["sni"] = sni
|
||||
}
|
||||
|
||||
network := strings.ToLower(q.Get("type"))
|
||||
if network != "" {
|
||||
trojan["network"] = network
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = q.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
trojan["ws-opts"] = wsOpts
|
||||
}
|
||||
|
||||
proxies = append(proxies, trojan)
|
||||
case "vmess":
|
||||
dcBuf, err := enc.DecodeString(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
jsonDc := json.NewDecoder(bytes.NewReader(dcBuf))
|
||||
values := make(map[string]any, 20)
|
||||
|
||||
if jsonDc.Decode(&values) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := uniqueName(names, values["ps"].(string))
|
||||
vmess := make(map[string]any, 20)
|
||||
|
||||
vmess["name"] = name
|
||||
vmess["type"] = scheme
|
||||
vmess["server"] = values["add"]
|
||||
vmess["port"] = values["port"]
|
||||
vmess["uuid"] = values["id"]
|
||||
vmess["alterId"] = values["aid"]
|
||||
vmess["cipher"] = "auto"
|
||||
vmess["udp"] = true
|
||||
vmess["skip-cert-verify"] = false
|
||||
|
||||
host := values["host"]
|
||||
network := strings.ToLower(values["net"].(string))
|
||||
|
||||
vmess["network"] = network
|
||||
|
||||
tls := strings.ToLower(values["tls"].(string))
|
||||
if tls != "" && tls != "0" && tls != "null" {
|
||||
if host != nil {
|
||||
vmess["servername"] = host
|
||||
}
|
||||
vmess["tls"] = true
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
if values["path"] != nil {
|
||||
wsOpts["path"] = values["path"]
|
||||
}
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
vmess["ws-opts"] = wsOpts
|
||||
}
|
||||
|
||||
proxies = append(proxies, vmess)
|
||||
case "ss":
|
||||
urlSS, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := uniqueName(names, urlSS.Fragment)
|
||||
port := urlSS.Port()
|
||||
|
||||
if port == "" {
|
||||
dcBuf, err := enc.DecodeString(urlSS.Host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
urlSS, err = url.Parse("ss://" + string(dcBuf))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
cipher = urlSS.User.Username()
|
||||
password string
|
||||
)
|
||||
|
||||
if password, found = urlSS.User.Password(); !found {
|
||||
dcBuf, err := enc.DecodeString(cipher)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ss := make(map[string]any, 20)
|
||||
|
||||
ss["name"] = name
|
||||
ss["type"] = scheme
|
||||
ss["server"] = urlSS.Hostname()
|
||||
ss["port"] = urlSS.Port()
|
||||
ss["cipher"] = cipher
|
||||
ss["password"] = password
|
||||
ss["udp"] = true
|
||||
|
||||
proxies = append(proxies, ss)
|
||||
case "ssr":
|
||||
dcBuf, err := enc.DecodeString(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
|
||||
|
||||
before, after, ok := strings.Cut(string(dcBuf), "/?")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
beforeArr := strings.Split(before, ":")
|
||||
|
||||
if len(beforeArr) != 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
host := beforeArr[0]
|
||||
port := beforeArr[1]
|
||||
protocol := beforeArr[2]
|
||||
method := beforeArr[3]
|
||||
obfs := beforeArr[4]
|
||||
password := decodeUrlSafe(urlSafe(beforeArr[5]))
|
||||
|
||||
query, err := url.ParseQuery(urlSafe(after))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
remarks := decodeUrlSafe(query.Get("remarks"))
|
||||
name := uniqueName(names, remarks)
|
||||
|
||||
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
|
||||
protocolParam := query.Get("protoparam")
|
||||
|
||||
ssr := make(map[string]any, 20)
|
||||
|
||||
ssr["name"] = name
|
||||
ssr["type"] = scheme
|
||||
ssr["server"] = host
|
||||
ssr["port"] = port
|
||||
ssr["cipher"] = method
|
||||
ssr["password"] = password
|
||||
ssr["obfs"] = obfs
|
||||
ssr["protocol"] = protocol
|
||||
ssr["udp"] = true
|
||||
|
||||
if obfsParam != "" {
|
||||
ssr["obfs-param"] = obfsParam
|
||||
}
|
||||
|
||||
if protocolParam != "" {
|
||||
ssr["protocol-param"] = protocolParam
|
||||
}
|
||||
|
||||
proxies = append(proxies, ssr)
|
||||
case "vless":
|
||||
urlVless, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
q := urlVless.Query()
|
||||
|
||||
name := uniqueName(names, urlVless.Fragment)
|
||||
vless := make(map[string]any, 20)
|
||||
|
||||
vless["name"] = name
|
||||
vless["type"] = scheme
|
||||
vless["server"] = urlVless.Hostname()
|
||||
vless["port"] = urlVless.Port()
|
||||
vless["uuid"] = urlVless.User.Username()
|
||||
vless["udp"] = true
|
||||
vless["skip-cert-verify"] = false
|
||||
|
||||
sni := q.Get("sni")
|
||||
if sni != "" {
|
||||
vless["servername"] = sni
|
||||
}
|
||||
|
||||
flow := strings.ToLower(q.Get("flow"))
|
||||
if flow != "" {
|
||||
vless["flow"] = flow
|
||||
}
|
||||
|
||||
network := strings.ToLower(q.Get("type"))
|
||||
if network != "" {
|
||||
vless["network"] = network
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = q.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
vless["ws-opts"] = wsOpts
|
||||
}
|
||||
|
||||
proxies = append(proxies, vless)
|
||||
}
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
return nil, fmt.Errorf("convert v2ray subscribe error: format invalid")
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func urlSafe(data string) string {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_")
|
||||
}
|
||||
|
||||
func decodeUrlSafe(data string) string {
|
||||
dcBuf, err := base64.URLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(dcBuf)
|
||||
}
|
||||
|
||||
func uniqueName(names map[string]int, name string) string {
|
||||
if index, ok := names[name]; ok {
|
||||
index++
|
||||
names[name] = index
|
||||
name = name + "-" + fmt.Sprintf("%02d", index)
|
||||
} else {
|
||||
index = 0
|
||||
names[name] = index
|
||||
}
|
||||
return name
|
||||
}
|
313
common/convert/util.go
Normal file
313
common/convert/util.go
Normal file
@ -0,0 +1,313 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
var hostsSuffix = []string{
|
||||
"-cdn.aliyuncs.com",
|
||||
".alicdn.com",
|
||||
".pan.baidu.com",
|
||||
".tbcache.com",
|
||||
".aliyuncdn.com",
|
||||
".vod.miguvideo.com",
|
||||
".cibntv.net",
|
||||
".myqcloud.com",
|
||||
".smtcdns.com",
|
||||
".alikunlun.com",
|
||||
".smtcdns.net",
|
||||
".apcdns.net",
|
||||
".cdn-go.cn",
|
||||
".cdntip.com",
|
||||
".cdntips.com",
|
||||
".alidayu.com",
|
||||
".alidns.com",
|
||||
".cdngslb.com",
|
||||
".mxhichina.com",
|
||||
".alibabadns.com",
|
||||
}
|
||||
|
||||
var userAgents = []string{
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
}
|
||||
|
||||
var (
|
||||
hostsLen = len(hostsSuffix)
|
||||
uaLen = len(userAgents)
|
||||
)
|
||||
|
||||
func RandHost() string {
|
||||
id, _ := uuid.NewV4()
|
||||
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
||||
base = strings.ReplaceAll(base, "-", "")
|
||||
base = strings.ReplaceAll(base, "_", "")
|
||||
buf := []byte(base)
|
||||
prefix := string(buf[:3]) + "---"
|
||||
prefix += string(buf[6:8]) + "-"
|
||||
prefix += string(buf[len(buf)-8:])
|
||||
|
||||
return prefix + hostsSuffix[rand.Intn(hostsLen)]
|
||||
}
|
||||
|
||||
func RandUserAgent() string {
|
||||
return userAgents[rand.Intn(uaLen)]
|
||||
}
|
||||
|
||||
func SetUserAgent(req *http.Request) {
|
||||
userAgent := RandUserAgent()
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
}
|
@ -456,7 +456,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", proxyProvider.Name(), err)
|
||||
}
|
||||
|
@ -6,13 +6,14 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func downloadMMDB(path string) (err error) {
|
||||
resp, err := http.Get("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb")
|
||||
resp, err := doGet("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -51,7 +52,7 @@ func initMMDB() error {
|
||||
}
|
||||
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
resp, err := http.Get("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat")
|
||||
resp, err := doGet("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -110,3 +111,16 @@ func Init(dir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doGet(url string) (resp *http.Response, err error) {
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
convert.SetUserAgent(req)
|
||||
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
return
|
||||
}
|
||||
|
@ -54,10 +54,14 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
}
|
||||
|
||||
var conn net.Conn
|
||||
if c.proxyAdapter == "" {
|
||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||
} else {
|
||||
if c.proxyAdapter != "" {
|
||||
conn, err = dialContextWithProxyAdapter(ctx, c.proxyAdapter, network, ip, c.port, options...)
|
||||
if err == errProxyNotFound {
|
||||
options = append(options[:0], dialer.WithInterface(c.proxyAdapter), dialer.WithRoutingMark(0))
|
||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||
}
|
||||
} else {
|
||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
18
dns/doh.go
18
dns/doh.go
@ -69,7 +69,9 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@ -97,11 +99,17 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyAdapter == "" {
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
} else {
|
||||
return dialContextWithProxyAdapter(ctx, proxyAdapter, "tcp", ip, port)
|
||||
if proxyAdapter != "" {
|
||||
var conn net.Conn
|
||||
conn, err = dialContextWithProxyAdapter(ctx, proxyAdapter, "tcp", ip, port)
|
||||
if err == errProxyNotFound {
|
||||
options := []dialer.Option{dialer.WithInterface(proxyAdapter), dialer.WithRoutingMark(0)}
|
||||
conn, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port), options...)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@ -19,6 +20,8 @@ import (
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var errProxyNotFound = errors.New("proxy adapter not found")
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
var ttl uint32
|
||||
switch {
|
||||
@ -135,7 +138,7 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
|
||||
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
|
||||
proxy, ok := tunnel.Proxies()[adapterName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName)
|
||||
return nil, errProxyNotFound
|
||||
}
|
||||
|
||||
networkType := C.TCP
|
||||
|
11
go.mod
11
go.mod
@ -3,7 +3,6 @@ module github.com/Dreamacro/clash
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
@ -19,17 +18,17 @@ require (
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/automaxprocs v1.5.1
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075
|
||||
)
|
||||
|
||||
require (
|
||||
|
22
go.sum
22
go.sum
@ -1,5 +1,3 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@ -82,8 +80,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM=
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
@ -99,12 +97,12 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -148,8 +146,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
@ -160,5 +158,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
|
||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E=
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
|
@ -49,14 +49,14 @@ func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] update GEO databases successful, apply config...")
|
||||
|
||||
cfg, err := executor.ParseWithPath(constant.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] update GEO databases successful, apply config...")
|
||||
|
||||
executor.ApplyConfig(cfg, false)
|
||||
}()
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@ -43,6 +44,10 @@ func findProxyByName(next http.Handler) http.Handler {
|
||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||
proxies := tunnel.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
proxy, exist = findProxyInNonCompatibleProviderByName(name)
|
||||
}
|
||||
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
@ -128,3 +133,20 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
"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
|
||||
}
|
||||
|
11
test/go.mod
11
test/go.mod
@ -8,13 +8,12 @@ require (
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/miekg/dns v1.1.49
|
||||
github.com/stretchr/testify v1.7.1
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93
|
||||
)
|
||||
|
||||
replace github.com/Dreamacro/clash => ../
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
@ -38,20 +37,20 @@ require (
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.1.0 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 // indirect
|
||||
)
|
||||
|
22
test/go.sum
22
test/go.sum
@ -1,7 +1,5 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@ -103,8 +101,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM=
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -124,14 +122,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -182,8 +180,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
@ -198,5 +196,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
|
||||
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
|
||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
|
||||
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075 h1:ucwwit0X39HmMQ1iSeNCIXw4g/B8bi+O6TSxxDbPs9E=
|
||||
gvisor.dev/gvisor v0.0.0-20220601233344-46e478629075/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
|
5
transport/shadowsocks/README.md
Normal file
5
transport/shadowsocks/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
## Embedded go-shadowsocks2
|
||||
|
||||
from https://github.com/Dreamacro/go-shadowsocks2
|
||||
|
||||
origin https://github.com/riobard/go-shadowsocks2
|
164
transport/shadowsocks/core/cipher.go
Normal file
164
transport/shadowsocks/core/cipher.go
Normal file
@ -0,0 +1,164 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
|
||||
)
|
||||
|
||||
type Cipher interface {
|
||||
StreamConnCipher
|
||||
PacketConnCipher
|
||||
}
|
||||
|
||||
type StreamConnCipher interface {
|
||||
StreamConn(net.Conn) net.Conn
|
||||
}
|
||||
|
||||
type PacketConnCipher interface {
|
||||
PacketConn(net.PacketConn) net.PacketConn
|
||||
}
|
||||
|
||||
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
|
||||
var ErrCipherNotSupported = errors.New("cipher not supported")
|
||||
|
||||
const (
|
||||
aeadAes128Gcm = "AEAD_AES_128_GCM"
|
||||
aeadAes192Gcm = "AEAD_AES_192_GCM"
|
||||
aeadAes256Gcm = "AEAD_AES_256_GCM"
|
||||
aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305"
|
||||
aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305"
|
||||
)
|
||||
|
||||
// List of AEAD ciphers: key size in bytes and constructor
|
||||
var aeadList = map[string]struct {
|
||||
KeySize int
|
||||
New func([]byte) (shadowaead.Cipher, error)
|
||||
}{
|
||||
aeadAes128Gcm: {16, shadowaead.AESGCM},
|
||||
aeadAes192Gcm: {24, shadowaead.AESGCM},
|
||||
aeadAes256Gcm: {32, shadowaead.AESGCM},
|
||||
aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305},
|
||||
aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305},
|
||||
}
|
||||
|
||||
// List of stream ciphers: key size in bytes and constructor
|
||||
var streamList = map[string]struct {
|
||||
KeySize int
|
||||
New func(key []byte) (shadowstream.Cipher, error)
|
||||
}{
|
||||
"RC4-MD5": {16, shadowstream.RC4MD5},
|
||||
"AES-128-CTR": {16, shadowstream.AESCTR},
|
||||
"AES-192-CTR": {24, shadowstream.AESCTR},
|
||||
"AES-256-CTR": {32, shadowstream.AESCTR},
|
||||
"AES-128-CFB": {16, shadowstream.AESCFB},
|
||||
"AES-192-CFB": {24, shadowstream.AESCFB},
|
||||
"AES-256-CFB": {32, shadowstream.AESCFB},
|
||||
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
|
||||
"XCHACHA20": {32, shadowstream.Xchacha20},
|
||||
}
|
||||
|
||||
// ListCipher returns a list of available cipher names sorted alphabetically.
|
||||
func ListCipher() []string {
|
||||
var l []string
|
||||
for k := range aeadList {
|
||||
l = append(l, k)
|
||||
}
|
||||
for k := range streamList {
|
||||
l = append(l, k)
|
||||
}
|
||||
sort.Strings(l)
|
||||
return l
|
||||
}
|
||||
|
||||
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
|
||||
func PickCipher(name string, key []byte, password string) (Cipher, error) {
|
||||
name = strings.ToUpper(name)
|
||||
|
||||
switch name {
|
||||
case "DUMMY":
|
||||
return &dummy{}, nil
|
||||
case "CHACHA20-IETF-POLY1305":
|
||||
name = aeadChacha20Poly1305
|
||||
case "XCHACHA20-IETF-POLY1305":
|
||||
name = aeadXChacha20Poly1305
|
||||
case "AES-128-GCM":
|
||||
name = aeadAes128Gcm
|
||||
case "AES-192-GCM":
|
||||
name = aeadAes192Gcm
|
||||
case "AES-256-GCM":
|
||||
name = aeadAes256Gcm
|
||||
}
|
||||
|
||||
if choice, ok := aeadList[name]; ok {
|
||||
if len(key) == 0 {
|
||||
key = Kdf(password, choice.KeySize)
|
||||
}
|
||||
if len(key) != choice.KeySize {
|
||||
return nil, shadowaead.KeySizeError(choice.KeySize)
|
||||
}
|
||||
aead, err := choice.New(key)
|
||||
return &AeadCipher{Cipher: aead, Key: key}, err
|
||||
}
|
||||
|
||||
if choice, ok := streamList[name]; ok {
|
||||
if len(key) == 0 {
|
||||
key = Kdf(password, choice.KeySize)
|
||||
}
|
||||
if len(key) != choice.KeySize {
|
||||
return nil, shadowstream.KeySizeError(choice.KeySize)
|
||||
}
|
||||
ciph, err := choice.New(key)
|
||||
return &StreamCipher{Cipher: ciph, Key: key}, err
|
||||
}
|
||||
|
||||
return nil, ErrCipherNotSupported
|
||||
}
|
||||
|
||||
type AeadCipher struct {
|
||||
shadowaead.Cipher
|
||||
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func (aead *AeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
|
||||
func (aead *AeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
return shadowaead.NewPacketConn(c, aead)
|
||||
}
|
||||
|
||||
type StreamCipher struct {
|
||||
shadowstream.Cipher
|
||||
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
|
||||
func (ciph *StreamCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
return shadowstream.NewPacketConn(c, ciph)
|
||||
}
|
||||
|
||||
// dummy cipher does not encrypt
|
||||
|
||||
type dummy struct{}
|
||||
|
||||
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
|
||||
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
|
||||
|
||||
// key-derivation function from original Shadowsocks
|
||||
func Kdf(password string, keyLen int) []byte {
|
||||
var b, prev []byte
|
||||
h := md5.New()
|
||||
for len(b) < keyLen {
|
||||
h.Write(prev)
|
||||
h.Write([]byte(password))
|
||||
b = h.Sum(b)
|
||||
prev = b[len(b)-h.Size():]
|
||||
h.Reset()
|
||||
}
|
||||
return b[:keyLen]
|
||||
}
|
94
transport/shadowsocks/shadowaead/cipher.go
Normal file
94
transport/shadowsocks/shadowaead/cipher.go
Normal file
@ -0,0 +1,94 @@
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
type Cipher interface {
|
||||
KeySize() int
|
||||
SaltSize() int
|
||||
Encrypter(salt []byte) (cipher.AEAD, error)
|
||||
Decrypter(salt []byte) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
type KeySizeError int
|
||||
|
||||
func (e KeySizeError) Error() string {
|
||||
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
||||
}
|
||||
|
||||
func hkdfSHA1(secret, salt, info, outkey []byte) {
|
||||
r := hkdf.New(sha1.New, secret, salt, info)
|
||||
if _, err := io.ReadFull(r, outkey); err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
}
|
||||
|
||||
type metaCipher struct {
|
||||
psk []byte
|
||||
makeAEAD func(key []byte) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
func (a *metaCipher) KeySize() int { return len(a.psk) }
|
||||
func (a *metaCipher) SaltSize() int {
|
||||
if ks := a.KeySize(); ks > 16 {
|
||||
return ks
|
||||
}
|
||||
return 16
|
||||
}
|
||||
|
||||
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
||||
subkey := make([]byte, a.KeySize())
|
||||
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
||||
return a.makeAEAD(subkey)
|
||||
}
|
||||
|
||||
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
||||
subkey := make([]byte, a.KeySize())
|
||||
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
||||
return a.makeAEAD(subkey)
|
||||
}
|
||||
|
||||
func aesGCM(key []byte) (cipher.AEAD, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewGCM(blk)
|
||||
}
|
||||
|
||||
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
|
||||
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
|
||||
func AESGCM(psk []byte) (Cipher, error) {
|
||||
switch l := len(psk); l {
|
||||
case 16, 24, 32: // AES 128/196/256
|
||||
default:
|
||||
return nil, aes.KeySizeError(l)
|
||||
}
|
||||
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
|
||||
}
|
||||
|
||||
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
||||
// must be 32.
|
||||
func Chacha20Poly1305(psk []byte) (Cipher, error) {
|
||||
if len(psk) != chacha20poly1305.KeySize {
|
||||
return nil, KeySizeError(chacha20poly1305.KeySize)
|
||||
}
|
||||
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
|
||||
}
|
||||
|
||||
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
||||
// must be 32.
|
||||
func XChacha20Poly1305(psk []byte) (Cipher, error) {
|
||||
if len(psk) != chacha20poly1305.KeySize {
|
||||
return nil, KeySizeError(chacha20poly1305.KeySize)
|
||||
}
|
||||
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
|
||||
}
|
95
transport/shadowsocks/shadowaead/packet.go
Normal file
95
transport/shadowsocks/shadowaead/packet.go
Normal file
@ -0,0 +1,95 @@
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
|
||||
var ErrShortPacket = errors.New("short packet")
|
||||
|
||||
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
|
||||
|
||||
// Pack encrypts plaintext using Cipher with a randomly generated salt and
|
||||
// returns a slice of dst containing the encrypted packet and any error occurred.
|
||||
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
|
||||
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
|
||||
saltSize := ciph.SaltSize()
|
||||
salt := dst[:saltSize]
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aead, err := ciph.Encrypter(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
|
||||
return dst[:saltSize+len(b)], nil
|
||||
}
|
||||
|
||||
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
|
||||
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
|
||||
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
|
||||
saltSize := ciph.SaltSize()
|
||||
if len(pkt) < saltSize {
|
||||
return nil, ErrShortPacket
|
||||
}
|
||||
salt := pkt[:saltSize]
|
||||
aead, err := ciph.Decrypter(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pkt) < saltSize+aead.Overhead() {
|
||||
return nil, ErrShortPacket
|
||||
}
|
||||
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
|
||||
return b, err
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
Cipher
|
||||
}
|
||||
|
||||
const maxPacketSize = 64 * 1024
|
||||
|
||||
// NewPacketConn wraps a net.PacketConn with cipher
|
||||
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
|
||||
return &PacketConn{PacketConn: c, Cipher: ciph}
|
||||
}
|
||||
|
||||
// WriteTo encrypts b and write to addr using the embedded PacketConn.
|
||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
buf := pool.Get(maxPacketSize)
|
||||
defer pool.Put(buf)
|
||||
buf, err := Pack(buf, b, c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
// ReadFrom reads from the embedded PacketConn and decrypts into b.
|
||||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
copy(b, bb)
|
||||
return len(bb), addr, err
|
||||
}
|
285
transport/shadowsocks/shadowaead/stream.go
Normal file
285
transport/shadowsocks/shadowaead/stream.go
Normal file
@ -0,0 +1,285 @@
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
// payloadSizeMask is the maximum size of payload in bytes.
|
||||
payloadSizeMask = 0x3FFF // 16*1024 - 1
|
||||
bufSize = 17 * 1024 // >= 2+aead.Overhead()+payloadSizeMask+aead.Overhead()
|
||||
)
|
||||
|
||||
var ErrZeroChunk = errors.New("zero chunk")
|
||||
|
||||
type Writer struct {
|
||||
io.Writer
|
||||
cipher.AEAD
|
||||
nonce [32]byte // should be sufficient for most nonce sizes
|
||||
}
|
||||
|
||||
// NewWriter wraps an io.Writer with authenticated encryption.
|
||||
func NewWriter(w io.Writer, aead cipher.AEAD) *Writer { return &Writer{Writer: w, AEAD: aead} }
|
||||
|
||||
// Write encrypts p and writes to the embedded io.Writer.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
buf := pool.Get(bufSize)
|
||||
defer pool.Put(buf)
|
||||
nonce := w.nonce[:w.NonceSize()]
|
||||
tag := w.Overhead()
|
||||
off := 2 + tag
|
||||
|
||||
// compatible with snell
|
||||
if len(p) == 0 {
|
||||
buf = buf[:off]
|
||||
buf[0], buf[1] = byte(0), byte(0)
|
||||
w.Seal(buf[:0], nonce, buf[:2], nil)
|
||||
increment(nonce)
|
||||
_, err = w.Writer.Write(buf)
|
||||
return
|
||||
}
|
||||
|
||||
for nr := 0; n < len(p) && err == nil; n += nr {
|
||||
nr = payloadSizeMask
|
||||
if n+nr > len(p) {
|
||||
nr = len(p) - n
|
||||
}
|
||||
buf = buf[:off+nr+tag]
|
||||
buf[0], buf[1] = byte(nr>>8), byte(nr) // big-endian payload size
|
||||
w.Seal(buf[:0], nonce, buf[:2], nil)
|
||||
increment(nonce)
|
||||
w.Seal(buf[:off], nonce, p[n:n+nr], nil)
|
||||
increment(nonce)
|
||||
_, err = w.Writer.Write(buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadFrom reads from the given io.Reader until EOF or error, encrypts and
|
||||
// writes to the embedded io.Writer. Returns number of bytes read from r and
|
||||
// any error encountered.
|
||||
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
buf := pool.Get(bufSize)
|
||||
defer pool.Put(buf)
|
||||
nonce := w.nonce[:w.NonceSize()]
|
||||
tag := w.Overhead()
|
||||
off := 2 + tag
|
||||
for {
|
||||
nr, er := r.Read(buf[off : off+payloadSizeMask])
|
||||
n += int64(nr)
|
||||
buf[0], buf[1] = byte(nr>>8), byte(nr)
|
||||
w.Seal(buf[:0], nonce, buf[:2], nil)
|
||||
increment(nonce)
|
||||
w.Seal(buf[:off], nonce, buf[off:off+nr], nil)
|
||||
increment(nonce)
|
||||
if _, ew := w.Writer.Write(buf[:off+nr+tag]); ew != nil {
|
||||
err = ew
|
||||
return
|
||||
}
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||
err = er
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
io.Reader
|
||||
cipher.AEAD
|
||||
nonce [32]byte // should be sufficient for most nonce sizes
|
||||
buf []byte // to be put back into bufPool
|
||||
off int // offset to unconsumed part of buf
|
||||
}
|
||||
|
||||
// NewReader wraps an io.Reader with authenticated decryption.
|
||||
func NewReader(r io.Reader, aead cipher.AEAD) *Reader { return &Reader{Reader: r, AEAD: aead} }
|
||||
|
||||
// Read and decrypt a record into p. len(p) >= max payload size + AEAD overhead.
|
||||
func (r *Reader) read(p []byte) (int, error) {
|
||||
nonce := r.nonce[:r.NonceSize()]
|
||||
tag := r.Overhead()
|
||||
|
||||
// decrypt payload size
|
||||
p = p[:2+tag]
|
||||
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err := r.Open(p[:0], nonce, p, nil)
|
||||
increment(nonce)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// decrypt payload
|
||||
size := (int(p[0])<<8 + int(p[1])) & payloadSizeMask
|
||||
if size == 0 {
|
||||
return 0, ErrZeroChunk
|
||||
}
|
||||
|
||||
p = p[:size+tag]
|
||||
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = r.Open(p[:0], nonce, p, nil)
|
||||
increment(nonce)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// Read reads from the embedded io.Reader, decrypts and writes to p.
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
if r.buf == nil {
|
||||
if len(p) >= payloadSizeMask+r.Overhead() {
|
||||
return r.read(p)
|
||||
}
|
||||
b := pool.Get(bufSize)
|
||||
n, err := r.read(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.buf = b[:n]
|
||||
r.off = 0
|
||||
}
|
||||
|
||||
n := copy(p, r.buf[r.off:])
|
||||
r.off += n
|
||||
if r.off == len(r.buf) {
|
||||
pool.Put(r.buf[:cap(r.buf)])
|
||||
r.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteTo reads from the embedded io.Reader, decrypts and writes to w until
|
||||
// there's no more data to write or when an error occurs. Return number of
|
||||
// bytes written to w and any error encountered.
|
||||
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if r.buf == nil {
|
||||
r.buf = pool.Get(bufSize)
|
||||
r.off = len(r.buf)
|
||||
}
|
||||
|
||||
for {
|
||||
for r.off < len(r.buf) {
|
||||
nw, ew := w.Write(r.buf[r.off:])
|
||||
r.off += nw
|
||||
n += int64(nw)
|
||||
if ew != nil {
|
||||
if r.off == len(r.buf) {
|
||||
pool.Put(r.buf[:cap(r.buf)])
|
||||
r.buf = nil
|
||||
}
|
||||
err = ew
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nr, er := r.read(r.buf)
|
||||
if er != nil {
|
||||
if er != io.EOF {
|
||||
err = er
|
||||
}
|
||||
return
|
||||
}
|
||||
r.buf = r.buf[:nr]
|
||||
r.off = 0
|
||||
}
|
||||
}
|
||||
|
||||
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
|
||||
func increment(b []byte) {
|
||||
for i := range b {
|
||||
b[i]++
|
||||
if b[i] != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
Cipher
|
||||
r *Reader
|
||||
w *Writer
|
||||
}
|
||||
|
||||
// NewConn wraps a stream-oriented net.Conn with cipher.
|
||||
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
|
||||
|
||||
func (c *Conn) initReader() error {
|
||||
salt := make([]byte, c.SaltSize())
|
||||
if _, err := io.ReadFull(c.Conn, salt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aead, err := c.Decrypter(salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.r = NewReader(c.Conn, aead)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
if c.r == nil {
|
||||
if err := c.initReader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
|
||||
if c.r == nil {
|
||||
if err := c.initReader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.r.WriteTo(w)
|
||||
}
|
||||
|
||||
func (c *Conn) initWriter() error {
|
||||
salt := make([]byte, c.SaltSize())
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return err
|
||||
}
|
||||
aead, err := c.Encrypter(salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.Conn.Write(salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.w = NewWriter(c.Conn, aead)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (int, error) {
|
||||
if c.w == nil {
|
||||
if err := c.initWriter(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.w.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
|
||||
if c.w == nil {
|
||||
if err := c.initWriter(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.w.ReadFrom(r)
|
||||
}
|
116
transport/shadowsocks/shadowstream/cipher.go
Normal file
116
transport/shadowsocks/shadowstream/cipher.go
Normal file
@ -0,0 +1,116 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rc4"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
)
|
||||
|
||||
// Cipher generates a pair of stream ciphers for encryption and decryption.
|
||||
type Cipher interface {
|
||||
IVSize() int
|
||||
Encrypter(iv []byte) cipher.Stream
|
||||
Decrypter(iv []byte) cipher.Stream
|
||||
}
|
||||
|
||||
type KeySizeError int
|
||||
|
||||
func (e KeySizeError) Error() string {
|
||||
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
||||
}
|
||||
|
||||
// CTR mode
|
||||
type ctrStream struct{ cipher.Block }
|
||||
|
||||
func (b *ctrStream) IVSize() int { return b.BlockSize() }
|
||||
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
|
||||
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
|
||||
|
||||
func AESCTR(key []byte) (Cipher, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctrStream{blk}, nil
|
||||
}
|
||||
|
||||
// CFB mode
|
||||
type cfbStream struct{ cipher.Block }
|
||||
|
||||
func (b *cfbStream) IVSize() int { return b.BlockSize() }
|
||||
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
|
||||
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
|
||||
|
||||
func AESCFB(key []byte) (Cipher, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfbStream{blk}, nil
|
||||
}
|
||||
|
||||
// IETF-variant of chacha20
|
||||
type chacha20ietfkey []byte
|
||||
|
||||
func (k chacha20ietfkey) IVSize() int { return chacha20.NonceSize }
|
||||
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
|
||||
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
return ciph
|
||||
}
|
||||
|
||||
func Chacha20IETF(key []byte) (Cipher, error) {
|
||||
if len(key) != chacha20.KeySize {
|
||||
return nil, KeySizeError(chacha20.KeySize)
|
||||
}
|
||||
return chacha20ietfkey(key), nil
|
||||
}
|
||||
|
||||
type xchacha20key []byte
|
||||
|
||||
func (k xchacha20key) IVSize() int { return chacha20.NonceSizeX }
|
||||
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
|
||||
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
return ciph
|
||||
}
|
||||
|
||||
func Xchacha20(key []byte) (Cipher, error) {
|
||||
if len(key) != chacha20.KeySize {
|
||||
return nil, KeySizeError(chacha20.KeySize)
|
||||
}
|
||||
return xchacha20key(key), nil
|
||||
}
|
||||
|
||||
type rc4Md5Key []byte
|
||||
|
||||
func (k rc4Md5Key) IVSize() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
|
||||
h := md5.New()
|
||||
h.Write([]byte(k))
|
||||
h.Write(iv)
|
||||
rc4key := h.Sum(nil)
|
||||
c, _ := rc4.NewCipher(rc4key)
|
||||
return c
|
||||
}
|
||||
|
||||
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream {
|
||||
return k.Encrypter(iv)
|
||||
}
|
||||
|
||||
func RC4MD5(key []byte) (Cipher, error) {
|
||||
return rc4Md5Key(key), nil
|
||||
}
|
79
transport/shadowsocks/shadowstream/packet.go
Normal file
79
transport/shadowsocks/shadowstream/packet.go
Normal file
@ -0,0 +1,79 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
|
||||
var ErrShortPacket = errors.New("short packet")
|
||||
|
||||
// Pack encrypts plaintext using stream cipher s and a random IV.
|
||||
// Returns a slice of dst containing random IV and ciphertext.
|
||||
// Ensure len(dst) >= s.IVSize() + len(plaintext).
|
||||
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
|
||||
if len(dst) < s.IVSize()+len(plaintext) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
iv := dst[:s.IVSize()]
|
||||
_, err := rand.Read(iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
|
||||
return dst[:len(iv)+len(plaintext)], nil
|
||||
}
|
||||
|
||||
// Unpack decrypts pkt using stream cipher s.
|
||||
// Returns a slice of dst containing decrypted plaintext.
|
||||
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
|
||||
if len(pkt) < s.IVSize() {
|
||||
return nil, ErrShortPacket
|
||||
}
|
||||
if len(dst) < len(pkt)-s.IVSize() {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
iv := pkt[:s.IVSize()]
|
||||
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
|
||||
return dst[:len(pkt)-len(iv)], nil
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
Cipher
|
||||
}
|
||||
|
||||
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
|
||||
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
|
||||
return &PacketConn{PacketConn: c, Cipher: ciph}
|
||||
}
|
||||
|
||||
const maxPacketSize = 64 * 1024
|
||||
|
||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
buf := pool.Get(maxPacketSize)
|
||||
defer pool.Put(buf)
|
||||
buf, err := Pack(buf, b, c.Cipher)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
copy(b, bb)
|
||||
return len(bb), addr, err
|
||||
}
|
197
transport/shadowsocks/shadowstream/stream.go
Normal file
197
transport/shadowsocks/shadowstream/stream.go
Normal file
@ -0,0 +1,197 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
const bufSize = 2048
|
||||
|
||||
type Writer struct {
|
||||
io.Writer
|
||||
cipher.Stream
|
||||
buf [bufSize]byte
|
||||
}
|
||||
|
||||
// NewWriter wraps an io.Writer with stream cipher encryption.
|
||||
func NewWriter(w io.Writer, s cipher.Stream) *Writer { return &Writer{Writer: w, Stream: s} }
|
||||
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
buf := w.buf[:]
|
||||
for nw := 0; n < len(p) && err == nil; n += nw {
|
||||
end := n + len(buf)
|
||||
if end > len(p) {
|
||||
end = len(p)
|
||||
}
|
||||
w.XORKeyStream(buf, p[n:end])
|
||||
nw, err = w.Writer.Write(buf[:end-n])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
buf := w.buf[:]
|
||||
for {
|
||||
nr, er := r.Read(buf)
|
||||
n += int64(nr)
|
||||
b := buf[:nr]
|
||||
w.XORKeyStream(b, b)
|
||||
if _, err = w.Writer.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||
err = er
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
io.Reader
|
||||
cipher.Stream
|
||||
buf [bufSize]byte
|
||||
}
|
||||
|
||||
// NewReader wraps an io.Reader with stream cipher decryption.
|
||||
func NewReader(r io.Reader, s cipher.Stream) *Reader { return &Reader{Reader: r, Stream: s} }
|
||||
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.XORKeyStream(p, p[:n])
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
|
||||
buf := r.buf[:]
|
||||
for {
|
||||
nr, er := r.Reader.Read(buf)
|
||||
if nr > 0 {
|
||||
r.XORKeyStream(buf, buf[:nr])
|
||||
nw, ew := w.Write(buf[:nr])
|
||||
n += int64(nw)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
return
|
||||
}
|
||||
}
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut)
|
||||
err = er
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A Conn represents a Shadowsocks connection. It implements the net.Conn interface.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
Cipher
|
||||
r *Reader
|
||||
w *Writer
|
||||
readIV []byte
|
||||
writeIV []byte
|
||||
}
|
||||
|
||||
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
|
||||
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
|
||||
|
||||
func (c *Conn) initReader() error {
|
||||
if c.r == nil {
|
||||
iv, err := c.ObtainReadIV()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.r = NewReader(c.Conn, c.Decrypter(iv))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
if c.r == nil {
|
||||
if err := c.initReader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
|
||||
if c.r == nil {
|
||||
if err := c.initReader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.r.WriteTo(w)
|
||||
}
|
||||
|
||||
func (c *Conn) initWriter() error {
|
||||
if c.w == nil {
|
||||
iv, err := c.ObtainWriteIV()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.Conn.Write(iv); err != nil {
|
||||
return err
|
||||
}
|
||||
c.w = NewWriter(c.Conn, c.Encrypter(iv))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (int, error) {
|
||||
if c.w == nil {
|
||||
if err := c.initWriter(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.w.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
|
||||
if c.w == nil {
|
||||
if err := c.initWriter(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.w.ReadFrom(r)
|
||||
}
|
||||
|
||||
func (c *Conn) ObtainWriteIV() ([]byte, error) {
|
||||
if len(c.writeIV) == c.IVSize() {
|
||||
return c.writeIV, nil
|
||||
}
|
||||
|
||||
iv := make([]byte, c.IVSize())
|
||||
|
||||
if _, err := rand.Read(iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.writeIV = iv
|
||||
|
||||
return iv, nil
|
||||
}
|
||||
|
||||
func (c *Conn) ObtainReadIV() ([]byte, error) {
|
||||
if len(c.readIV) == c.IVSize() {
|
||||
return c.readIV, nil
|
||||
}
|
||||
|
||||
iv := make([]byte, c.IVSize())
|
||||
|
||||
if _, err := io.ReadFull(c.Conn, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.readIV = iv
|
||||
|
||||
return iv, nil
|
||||
}
|
@ -4,7 +4,8 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
@ -6,8 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/pool"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
|
@ -9,9 +9,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -13,9 +13,8 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -12,8 +12,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
|
@ -192,13 +192,13 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
||||
}
|
||||
|
||||
// pre resolve process name
|
||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
||||
if err == nil && P.ShouldFindProcess(metadata) {
|
||||
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
|
||||
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
|
||||
if err != nil {
|
||||
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
||||
} else {
|
||||
// log.Debugln("[Process] %s from process %s", metadata.String(), path)
|
||||
log.Debugln("[Process] %s from process %s", metadata.String(), path)
|
||||
metadata.Process = filepath.Base(path)
|
||||
metadata.ProcessPath = path
|
||||
}
|
||||
|
Reference in New Issue
Block a user