Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
d3b14c325f | |||
4859b158b4 | |||
d65b51c62b | |||
a6444bb449 | |||
e09931dcf7 | |||
5bd189f2d0 | |||
8766287e72 | |||
10f9571c9e | |||
96a8259c42 | |||
68dd0622b8 | |||
558ac6b965 | |||
e773f95f21 | |||
314ce1c249 | |||
13275b1aa6 | |||
02d9169b5d | |||
7631bcc99e | |||
a32ee13fc9 | |||
b8ed738238 | |||
687c2a21cf | |||
ad18064e6b | |||
c9735ef75b | |||
b70882f01a | |||
5805334ccd | |||
c1b4382fe8 | |||
008743f20b |
5
.github/workflows/docker.yml
vendored
5
.github/workflows/docker.yml
vendored
@ -19,10 +19,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up docker buildx
|
- name: Set up docker buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: crazy-max/ghaction-docker-buildx@v2
|
uses: crazy-max/ghaction-docker-buildx@v3
|
||||||
with:
|
with:
|
||||||
buildx-version: latest
|
buildx-version: latest
|
||||||
skip-cache: false
|
|
||||||
qemu-version: latest
|
qemu-version: latest
|
||||||
|
|
||||||
- name: Docker login
|
- name: Docker login
|
||||||
@ -39,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Replace tag without `v`
|
- name: Replace tag without `v`
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: actions/github-script@v1
|
uses: actions/github-script@v3
|
||||||
id: version
|
id: version
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
5
.github/workflows/go.yml
vendored
5
.github/workflows/go.yml
vendored
@ -22,9 +22,12 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Get dependencies and run test
|
- name: Get dependencies, run test and static check
|
||||||
run: |
|
run: |
|
||||||
go test ./...
|
go test ./...
|
||||||
|
go vet ./...
|
||||||
|
go get -u honnef.co/go/tools/cmd/staticcheck
|
||||||
|
staticcheck -- $(go list ./...)
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v1
|
- uses: actions/stale@v3
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||||
|
8
Makefile
8
Makefile
@ -4,7 +4,7 @@ VERSION=$(shell git describe --tags || echo "unknown version")
|
|||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
-w -s'
|
-w -s -buildid='
|
||||||
|
|
||||||
PLATFORM_LIST = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
@ -25,7 +25,8 @@ PLATFORM_LIST = \
|
|||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64
|
windows-amd64 \
|
||||||
|
windows-arm32v7
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
|
|
||||||
@ -83,6 +84,9 @@ windows-386:
|
|||||||
windows-amd64:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
|
windows-arm32v7:
|
||||||
|
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||||
|
|
||||||
|
@ -13,10 +13,6 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
defaultURLTestTimeout = time.Second * 5
|
|
||||||
)
|
|
||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
name string
|
name string
|
||||||
addr string
|
addr string
|
||||||
|
@ -31,6 +31,7 @@ type HttpOption struct {
|
|||||||
UserName string `proxy:"username,omitempty"`
|
UserName string `proxy:"username,omitempty"`
|
||||||
Password string `proxy:"password,omitempty"`
|
Password string `proxy:"password,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,10 +115,14 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
|||||||
func NewHttp(option HttpOption) *Http {
|
func NewHttp(option HttpOption) *Http {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
|
sni := option.Server
|
||||||
|
if option.SNI != "" {
|
||||||
|
sni = option.SNI
|
||||||
|
}
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
ClientSessionCache: getClientSessionCache(),
|
||||||
ServerName: option.Server,
|
ServerName: sni,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||||
proxyType, existType := mapping["type"].(string)
|
proxyType, existType := mapping["type"].(string)
|
||||||
if !existType {
|
if !existType {
|
||||||
return nil, fmt.Errorf("Missing type")
|
return nil, fmt.Errorf("missing type")
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy C.ProxyAdapter
|
var (
|
||||||
err := fmt.Errorf("Cannot parse")
|
proxy C.ProxyAdapter
|
||||||
|
err error
|
||||||
|
)
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &ShadowSocksOption{}
|
ssOption := &ShadowSocksOption{}
|
||||||
@ -72,7 +74,7 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
}
|
}
|
||||||
proxy, err = NewTrojan(*trojanOption)
|
proxy, err = NewTrojan(*trojanOption)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,7 +16,9 @@ import (
|
|||||||
type Snell struct {
|
type Snell struct {
|
||||||
*Base
|
*Base
|
||||||
psk []byte
|
psk []byte
|
||||||
|
pool *snell.Pool
|
||||||
obfsOption *simpleObfsOption
|
obfsOption *simpleObfsOption
|
||||||
|
version int
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnellOption struct {
|
type SnellOption struct {
|
||||||
@ -24,24 +26,47 @@ type SnellOption struct {
|
|||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Psk string `proxy:"psk"`
|
Psk string `proxy:"psk"`
|
||||||
|
Version int `proxy:"version,omitempty"`
|
||||||
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
type streamOption struct {
|
||||||
switch s.obfsOption.Mode {
|
psk []byte
|
||||||
|
version int
|
||||||
|
addr string
|
||||||
|
obfsOption *simpleObfsOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||||
|
switch option.obfsOption.Mode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
||||||
case "http":
|
case "http":
|
||||||
_, port, _ := net.SplitHostPort(s.addr)
|
_, port, _ := net.SplitHostPort(option.addr)
|
||||||
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
|
||||||
}
|
}
|
||||||
c = snell.StreamConn(c, s.psk)
|
return snell.StreamConn(c, option.psk, option.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
err := snell.WriteHeader(c, metadata.String(), uint(port))
|
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
if s.version == snell.Version2 {
|
||||||
|
c, err := s.pool.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
|
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||||
|
return NewConn(c, s), err
|
||||||
|
}
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||||
@ -66,7 +91,15 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Snell{
|
// backward compatible
|
||||||
|
if option.Version == 0 {
|
||||||
|
option.Version = snell.DefaultSnellVersion
|
||||||
|
}
|
||||||
|
if option.Version != snell.Version1 && option.Version != snell.Version2 {
|
||||||
|
return nil, fmt.Errorf("snell version error: %d", option.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Snell{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
@ -74,5 +107,19 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
},
|
},
|
||||||
psk: psk,
|
psk: psk,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
}, nil
|
version: option.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.Version == snell.Version2 {
|
||||||
|
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ type VmessOption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
@ -44,6 +45,11 @@ type HTTPOptions struct {
|
|||||||
Headers map[string][]string `proxy:"headers,omitempty"`
|
Headers map[string][]string `proxy:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTTP2Options struct {
|
||||||
|
Host []string `proxy:"host,omitempty"`
|
||||||
|
Path string `proxy:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
@ -71,6 +77,25 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
case "http":
|
case "http":
|
||||||
|
// readability first, so just copy default TLS logic
|
||||||
|
if v.option.TLS {
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
tlsOpts := &vmess.TLSConfig{
|
||||||
|
Host: host,
|
||||||
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
|
SessionCache: getClientSessionCache(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
tlsOpts.Host = v.option.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
httpOpts := &vmess.HTTPConfig{
|
httpOpts := &vmess.HTTPConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -80,6 +105,30 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c = vmess.StreamHTTPConn(c, httpOpts)
|
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||||
|
case "h2":
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
tlsOpts := vmess.TLSConfig{
|
||||||
|
Host: host,
|
||||||
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
|
SessionCache: getClientSessionCache(),
|
||||||
|
NextProtos: []string{"h2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
tlsOpts.Host = v.option.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = vmess.StreamTLSConn(c, &tlsOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h2Opts := &vmess.H2Config{
|
||||||
|
Hosts: v.option.HTTP2Opts.Host,
|
||||||
|
Path: v.option.HTTP2Opts.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
@ -152,13 +201,16 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if option.Network == "h2" && !option.TLS {
|
||||||
|
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||||
|
}
|
||||||
|
|
||||||
return &Vmess{
|
return &Vmess{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Vmess,
|
tp: C.Vmess,
|
||||||
udp: true,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
option: &option,
|
option: &option,
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
errFormat = errors.New("format error")
|
errFormat = errors.New("format error")
|
||||||
errType = errors.New("unsupport type")
|
errType = errors.New("unsupport type")
|
||||||
errMissUse = errors.New("`use` field should not be empty")
|
|
||||||
errMissProxy = errors.New("`use` or `proxies` missing")
|
errMissProxy = errors.New("`use` or `proxies` missing")
|
||||||
errMissHealthCheck = errors.New("`url` or `interval` missing")
|
errMissHealthCheck = errors.New("`url` or `interval` missing")
|
||||||
errDuplicateProvider = errors.New("`duplicate provider name")
|
errDuplicateProvider = errors.New("`duplicate provider name")
|
||||||
@ -63,6 +62,10 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
|
|
||||||
providers = append(providers, pd)
|
providers = append(providers, pd)
|
||||||
} else {
|
} else {
|
||||||
|
if _, ok := providersMap[groupName]; ok {
|
||||||
|
return nil, errDuplicateProvider
|
||||||
|
}
|
||||||
|
|
||||||
// select don't need health check
|
// select don't need health check
|
||||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||||
hc := provider.NewHealthCheck(ps, "", 0)
|
hc := provider.NewHealthCheck(ps, "", 0)
|
||||||
|
@ -22,7 +22,7 @@ type Relay struct {
|
|||||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
proxies := r.proxies(metadata)
|
proxies := r.proxies(metadata)
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return nil, errors.New("Proxy does not exist")
|
return nil, errors.New("proxy does not exist")
|
||||||
}
|
}
|
||||||
first := proxies[0]
|
first := proxies[0]
|
||||||
last := proxies[len(proxies)-1]
|
last := proxies[len(proxies)-1]
|
||||||
|
@ -64,7 +64,7 @@ func (s *Selector) Set(name string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("Proxy does not exist")
|
return errors.New("proxy not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
@ -78,7 +78,7 @@ func (u *URLTest) fast() C.Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tolerance
|
// tolerance
|
||||||
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay() + u.tolerance {
|
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||||
u.fastNode = fast
|
u.fastNode = fast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,20 +120,20 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if schema.Proxies == nil {
|
if schema.Proxies == nil {
|
||||||
return nil, errors.New("File must have a `proxies` field")
|
return nil, errors.New("file must have a `proxies` field")
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies := []C.Proxy{}
|
proxies := []C.Proxy{}
|
||||||
for idx, mapping := range schema.Proxies {
|
for idx, mapping := range schema.Proxies {
|
||||||
proxy, err := outbound.ParseProxy(mapping)
|
proxy, err := outbound.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Proxy %d error: %w", idx, err)
|
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||||
}
|
}
|
||||||
proxies = append(proxies, proxy)
|
proxies = append(proxies, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return nil, errors.New("File doesn't have any valid proxy")
|
return nil, errors.New("file doesn't have any valid proxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxies, nil
|
return proxies, nil
|
||||||
|
19
common/cache/lrucache.go
vendored
19
common/cache/lrucache.go
vendored
@ -146,6 +146,23 @@ func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires tim
|
|||||||
c.maybeDeleteOldest()
|
c.maybeDeleteOldest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloneTo clone and overwrite elements to another LruCache
|
||||||
|
func (c *LruCache) CloneTo(n *LruCache) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
n.mu.Lock()
|
||||||
|
defer n.mu.Unlock()
|
||||||
|
|
||||||
|
n.lru = list.New()
|
||||||
|
n.cache = make(map[interface{}]*list.Element)
|
||||||
|
|
||||||
|
for e := c.lru.Front(); e != nil; e = e.Next() {
|
||||||
|
elm := e.Value.(*entry)
|
||||||
|
n.cache[elm.key] = n.lru.PushBack(elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *LruCache) get(key interface{}) *entry {
|
func (c *LruCache) get(key interface{}) *entry {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
@ -171,7 +188,7 @@ func (c *LruCache) get(key interface{}) *entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the value associated with a key.
|
// Delete removes the value associated with a key.
|
||||||
func (c *LruCache) Delete(key string) {
|
func (c *LruCache) Delete(key interface{}) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|
||||||
if le, ok := c.cache[key]; ok {
|
if le, ok := c.cache[key]; ok {
|
||||||
|
18
common/cache/lrucache_test.go
vendored
18
common/cache/lrucache_test.go
vendored
@ -164,3 +164,21 @@ func TestStale(t *testing.T) {
|
|||||||
assert.Equal(t, tenSecBefore, expires)
|
assert.Equal(t, tenSecBefore, expires)
|
||||||
assert.Equal(t, true, exist)
|
assert.Equal(t, true, exist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCloneTo(t *testing.T) {
|
||||||
|
o := NewLRUCache(WithSize(10))
|
||||||
|
o.Set("1", 1)
|
||||||
|
o.Set("2", 2)
|
||||||
|
|
||||||
|
n := NewLRUCache(WithSize(2))
|
||||||
|
n.Set("3", 3)
|
||||||
|
n.Set("4", 4)
|
||||||
|
|
||||||
|
o.CloneTo(n)
|
||||||
|
|
||||||
|
assert.False(t, n.Exist("3"))
|
||||||
|
assert.True(t, n.Exist("1"))
|
||||||
|
|
||||||
|
n.Set("5", 5)
|
||||||
|
assert.False(t, n.Exist("1"))
|
||||||
|
}
|
||||||
|
@ -55,6 +55,8 @@ func (alloc *Allocator) Put(buf []byte) error {
|
|||||||
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
||||||
return errors.New("allocator Put() incorrect buffer size")
|
return errors.New("allocator Put() incorrect buffer size")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//lint:ignore SA6002 ignore temporarily
|
||||||
alloc.buffers[bits].Put(buf)
|
alloc.buffers[bits].Put(buf)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,11 @@ func TestAllocGet(t *testing.T) {
|
|||||||
func TestAllocPut(t *testing.T) {
|
func TestAllocPut(t *testing.T) {
|
||||||
alloc := NewAllocator()
|
alloc := NewAllocator()
|
||||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||||
assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior")
|
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
|
||||||
assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior")
|
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
|
||||||
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
|
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
|
||||||
assert.Nil(t, alloc.Put(make([]byte, 65536, 65536)), "put elem:65536 []bytes misbehavior")
|
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
|
||||||
assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior")
|
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllocPutThenGet(t *testing.T) {
|
func TestAllocPutThenGet(t *testing.T) {
|
||||||
|
@ -24,6 +24,8 @@ type Result struct {
|
|||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do single.Do likes sync.singleFilght
|
||||||
|
//lint:ignore ST1008 it likes sync.singleFilght
|
||||||
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
|
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -147,28 +147,27 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
|
|||||||
go startRacer(ctx, network+"4", host, false)
|
go startRacer(ctx, network+"4", host, false)
|
||||||
go startRacer(ctx, network+"6", host, true)
|
go startRacer(ctx, network+"6", host, true)
|
||||||
|
|
||||||
for {
|
for res := range results {
|
||||||
select {
|
if res.error == nil {
|
||||||
case res := <-results:
|
return res.Conn, nil
|
||||||
if res.error == nil {
|
}
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !res.ipv6 {
|
if !res.ipv6 {
|
||||||
primary = res
|
primary = res
|
||||||
|
} else {
|
||||||
|
fallback = res
|
||||||
|
}
|
||||||
|
|
||||||
|
if primary.done && fallback.done {
|
||||||
|
if primary.resolved {
|
||||||
|
return nil, primary.error
|
||||||
|
} else if fallback.resolved {
|
||||||
|
return nil, fallback.error
|
||||||
} else {
|
} else {
|
||||||
fallback = res
|
return nil, primary.error
|
||||||
}
|
|
||||||
|
|
||||||
if primary.done && fallback.done {
|
|
||||||
if primary.resolved {
|
|
||||||
return nil, primary.error
|
|
||||||
} else if fallback.resolved {
|
|
||||||
return nil, fallback.error
|
|
||||||
} else {
|
|
||||||
return nil, primary.error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("never touched")
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,11 @@ func (p *Pool) Gateway() net.IP {
|
|||||||
return uintToIP(p.gateway)
|
return uintToIP(p.gateway)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PatchFrom clone cache from old pool
|
||||||
|
func (p *Pool) PatchFrom(o *Pool) {
|
||||||
|
o.cache.CloneTo(p.cache)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Pool) get(host string) net.IP {
|
func (p *Pool) get(host string) net.IP {
|
||||||
current := p.offset
|
current := p.offset
|
||||||
for {
|
for {
|
||||||
@ -116,7 +121,7 @@ func ipToUint(ip net.IP) uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func uintToIP(v uint32) net.IP {
|
func uintToIP(v uint32) net.IP {
|
||||||
return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return Pool instance
|
// New return Pool instance
|
||||||
|
114
component/pool/pool.go
Normal file
114
component/pool/pool.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Factory = func(context.Context) (interface{}, error)
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
elm interface{}
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*pool)
|
||||||
|
|
||||||
|
// WithEvict set the evict callback
|
||||||
|
func WithEvict(cb func(interface{})) Option {
|
||||||
|
return func(p *pool) {
|
||||||
|
p.evict = cb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAge defined element max age (millisecond)
|
||||||
|
func WithAge(maxAge int64) Option {
|
||||||
|
return func(p *pool) {
|
||||||
|
p.maxAge = maxAge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSize defined max size of Pool
|
||||||
|
func WithSize(maxSize int) Option {
|
||||||
|
return func(p *pool) {
|
||||||
|
p.ch = make(chan interface{}, maxSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool is for GC, see New for detail
|
||||||
|
type Pool struct {
|
||||||
|
*pool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pool struct {
|
||||||
|
ch chan interface{}
|
||||||
|
factory Factory
|
||||||
|
evict func(interface{})
|
||||||
|
maxAge int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pool) GetContext(ctx context.Context) (interface{}, error) {
|
||||||
|
now := time.Now()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case item := <-p.ch:
|
||||||
|
elm := item.(*entry)
|
||||||
|
if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge {
|
||||||
|
if p.evict != nil {
|
||||||
|
p.evict(elm.elm)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return elm.elm, nil
|
||||||
|
default:
|
||||||
|
return p.factory(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pool) Get() (interface{}, error) {
|
||||||
|
return p.GetContext(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pool) Put(item interface{}) {
|
||||||
|
e := &entry{
|
||||||
|
elm: item,
|
||||||
|
time: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p.ch <- e:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// pool is full
|
||||||
|
if p.evict != nil {
|
||||||
|
p.evict(item)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recycle(p *Pool) {
|
||||||
|
for item := range p.pool.ch {
|
||||||
|
if p.pool.evict != nil {
|
||||||
|
p.pool.evict(item.(*entry).elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(factory Factory, options ...Option) *Pool {
|
||||||
|
p := &pool{
|
||||||
|
ch: make(chan interface{}, 10),
|
||||||
|
factory: factory,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
P := &Pool{p}
|
||||||
|
runtime.SetFinalizer(P, recycle)
|
||||||
|
return P
|
||||||
|
}
|
73
component/pool/pool_test.go
Normal file
73
component/pool/pool_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lg() Factory {
|
||||||
|
initial := -1
|
||||||
|
return func(context.Context) (interface{}, error) {
|
||||||
|
initial++
|
||||||
|
return initial, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool_Basic(t *testing.T) {
|
||||||
|
g := lg()
|
||||||
|
pool := New(g)
|
||||||
|
|
||||||
|
elm, _ := pool.Get()
|
||||||
|
assert.Equal(t, 0, elm.(int))
|
||||||
|
pool.Put(elm)
|
||||||
|
elm, _ = pool.Get()
|
||||||
|
assert.Equal(t, 0, elm.(int))
|
||||||
|
elm, _ = pool.Get()
|
||||||
|
assert.Equal(t, 1, elm.(int))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool_MaxSize(t *testing.T) {
|
||||||
|
g := lg()
|
||||||
|
size := 5
|
||||||
|
pool := New(g, WithSize(size))
|
||||||
|
|
||||||
|
items := []interface{}{}
|
||||||
|
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
item, _ := pool.Get()
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
extra, _ := pool.Get()
|
||||||
|
assert.Equal(t, size, extra.(int))
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
pool.Put(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.Put(extra)
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
elm, _ := pool.Get()
|
||||||
|
assert.Equal(t, item.(int), elm.(int))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool_MaxAge(t *testing.T) {
|
||||||
|
g := lg()
|
||||||
|
pool := New(g, WithAge(20))
|
||||||
|
|
||||||
|
elm, _ := pool.Get()
|
||||||
|
pool.Put(elm)
|
||||||
|
|
||||||
|
elm, _ = pool.Get()
|
||||||
|
assert.Equal(t, 0, elm.(int))
|
||||||
|
pool.Put(elm)
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 22)
|
||||||
|
elm, _ = pool.Get()
|
||||||
|
assert.Equal(t, 1, elm.(int))
|
||||||
|
}
|
46
component/resolver/enhancer.go
Normal file
46
component/resolver/enhancer.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultHostMapper Enhancer
|
||||||
|
|
||||||
|
type Enhancer interface {
|
||||||
|
FakeIPEnabled() bool
|
||||||
|
MappingEnabled() bool
|
||||||
|
IsFakeIP(net.IP) bool
|
||||||
|
FindHostByIP(net.IP) (string, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FakeIPEnabled() bool {
|
||||||
|
if mapper := DefaultHostMapper; mapper != nil {
|
||||||
|
return mapper.FakeIPEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func MappingEnabled() bool {
|
||||||
|
if mapper := DefaultHostMapper; mapper != nil {
|
||||||
|
return mapper.MappingEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFakeIP(ip net.IP) bool {
|
||||||
|
if mapper := DefaultHostMapper; mapper != nil {
|
||||||
|
return mapper.IsFakeIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindHostByIP(ip net.IP) (string, bool) {
|
||||||
|
if mapper := DefaultHostMapper; mapper != nil {
|
||||||
|
return mapper.FindHostByIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
@ -1,21 +1,54 @@
|
|||||||
package snell
|
package snell
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
type snellCipher struct {
|
type snellCipher struct {
|
||||||
psk []byte
|
psk []byte
|
||||||
|
keySize int
|
||||||
makeAEAD func(key []byte) (cipher.AEAD, error)
|
makeAEAD func(key []byte) (cipher.AEAD, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *snellCipher) KeySize() int { return 32 }
|
func (sc *snellCipher) KeySize() int { return sc.keySize }
|
||||||
func (sc *snellCipher) SaltSize() int { return 16 }
|
func (sc *snellCipher) SaltSize() int { return 16 }
|
||||||
func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
||||||
return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize())))
|
return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize()))
|
||||||
}
|
}
|
||||||
func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
||||||
return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize())))
|
return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func snellKDF(psk, salt []byte, keySize int) []byte {
|
||||||
|
// snell use a special kdf function
|
||||||
|
return argon2.IDKey(psk, salt, 3, 8, 1, 32)[:keySize]
|
||||||
|
}
|
||||||
|
|
||||||
|
func aesGCM(key []byte) (cipher.AEAD, error) {
|
||||||
|
blk, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cipher.NewGCM(blk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAES128GCM(psk []byte) shadowaead.Cipher {
|
||||||
|
return &snellCipher{
|
||||||
|
psk: psk,
|
||||||
|
keySize: 16,
|
||||||
|
makeAEAD: aesGCM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChacha20Poly1305(psk []byte) shadowaead.Cipher {
|
||||||
|
return &snellCipher{
|
||||||
|
psk: psk,
|
||||||
|
keySize: 32,
|
||||||
|
makeAEAD: chacha20poly1305.New,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
80
component/snell/pool.go
Normal file
80
component/snell/pool.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package snell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/pool"
|
||||||
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pool struct {
|
||||||
|
pool *pool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) Get() (net.Conn, error) {
|
||||||
|
return p.GetContext(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) {
|
||||||
|
elm, err := p.pool.GetContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PoolConn{elm.(*Snell), p}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) Put(conn net.Conn) {
|
||||||
|
if err := HalfClose(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pool.Put(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PoolConn struct {
|
||||||
|
*Snell
|
||||||
|
pool *Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *PoolConn) Read(b []byte) (int, error) {
|
||||||
|
// save old status of reply (it mutable by Read)
|
||||||
|
reply := pc.Snell.reply
|
||||||
|
|
||||||
|
n, err := pc.Snell.Read(b)
|
||||||
|
if err == shadowaead.ErrZeroChunk {
|
||||||
|
// if reply is false, it should be client halfclose.
|
||||||
|
// ignore error and read data again.
|
||||||
|
if !reply {
|
||||||
|
pc.Snell.reply = false
|
||||||
|
return pc.Snell.Read(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *PoolConn) Write(b []byte) (int, error) {
|
||||||
|
return pc.Snell.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *PoolConn) Close() error {
|
||||||
|
pc.pool.Put(pc.Snell)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPool(factory func(context.Context) (*Snell, error)) *Pool {
|
||||||
|
p := pool.New(
|
||||||
|
func(ctx context.Context) (interface{}, error) {
|
||||||
|
return factory(ctx)
|
||||||
|
},
|
||||||
|
pool.WithAge(15000),
|
||||||
|
pool.WithSize(10),
|
||||||
|
pool.WithEvict(func(item interface{}) {
|
||||||
|
item.(*Snell).Close()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &Pool{p}
|
||||||
|
}
|
@ -10,14 +10,21 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CommandPing byte = 0
|
Version1 = 1
|
||||||
CommandConnect byte = 1
|
Version2 = 2
|
||||||
|
DefaultSnellVersion = Version1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommandPing byte = 0
|
||||||
|
CommandConnect byte = 1
|
||||||
|
CommandConnectV2 byte = 5
|
||||||
|
|
||||||
CommandTunnel byte = 0
|
CommandTunnel byte = 0
|
||||||
|
CommandPong byte = 1
|
||||||
CommandError byte = 2
|
CommandError byte = 2
|
||||||
|
|
||||||
Version byte = 1
|
Version byte = 1
|
||||||
@ -25,6 +32,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||||
|
endSignal = []byte{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type Snell struct {
|
type Snell struct {
|
||||||
@ -46,7 +54,7 @@ func (s *Snell) Read(b []byte) (int, error) {
|
|||||||
if s.buffer[0] == CommandTunnel {
|
if s.buffer[0] == CommandTunnel {
|
||||||
return s.Conn.Read(b)
|
return s.Conn.Read(b)
|
||||||
} else if s.buffer[0] != CommandError {
|
} else if s.buffer[0] != CommandError {
|
||||||
return 0, errors.New("Command not support")
|
return 0, errors.New("command not support")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandError
|
// CommandError
|
||||||
@ -70,12 +78,16 @@ func (s *Snell) Read(b []byte) (int, error) {
|
|||||||
return 0, fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg))
|
return 0, fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteHeader(conn net.Conn, host string, port uint) error {
|
func WriteHeader(conn net.Conn, host string, port uint, version int) error {
|
||||||
buf := bufferPool.Get().(*bytes.Buffer)
|
buf := bufferPool.Get().(*bytes.Buffer)
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
defer bufferPool.Put(buf)
|
defer bufferPool.Put(buf)
|
||||||
buf.WriteByte(Version)
|
buf.WriteByte(Version)
|
||||||
buf.WriteByte(CommandConnect)
|
if version == Version2 {
|
||||||
|
buf.WriteByte(CommandConnectV2)
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(CommandConnect)
|
||||||
|
}
|
||||||
|
|
||||||
// clientID length & id
|
// clientID length & id
|
||||||
buf.WriteByte(0)
|
buf.WriteByte(0)
|
||||||
@ -92,7 +104,24 @@ func WriteHeader(conn net.Conn, host string, port uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamConn(conn net.Conn, psk []byte) net.Conn {
|
// HalfClose works only on version2
|
||||||
cipher := &snellCipher{psk, chacha20poly1305.New}
|
func HalfClose(conn net.Conn) error {
|
||||||
|
if _, err := conn.Write(endSignal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := conn.(*Snell); ok {
|
||||||
|
s.reply = false
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
|
||||||
|
var cipher shadowaead.Cipher
|
||||||
|
if version == Version2 {
|
||||||
|
cipher = NewAES128GCM(psk)
|
||||||
|
} else {
|
||||||
|
cipher = NewChacha20Poly1305(psk)
|
||||||
|
}
|
||||||
return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
|
return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,13 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tlsAuthData struct {
|
type tlsAuthData struct {
|
||||||
@ -63,7 +63,7 @@ func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) {
|
|||||||
var h [5]byte
|
var h [5]byte
|
||||||
t.recvBuffer.Read(h[:])
|
t.recvBuffer.Read(h[:])
|
||||||
if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) {
|
if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) {
|
||||||
log.Println("incorrect magic number", h[:3], ", 0x170303 is expected")
|
log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3])
|
||||||
return nil, false, errTLS12TicketAuthIncorrectMagicNumber
|
return nil, false, errTLS12TicketAuthIncorrectMagicNumber
|
||||||
}
|
}
|
||||||
size := int(binary.BigEndian.Uint16(h[3:5]))
|
size := int(binary.BigEndian.Uint16(h[3:5]))
|
||||||
@ -287,5 +287,4 @@ func packData(buffer *bytes.Buffer, suffix []byte) {
|
|||||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF))
|
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF))
|
||||||
buffer.Write(d)
|
buffer.Write(d)
|
||||||
buffer.Write(suffix)
|
buffer.Write(suffix)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,6 @@ func (a *authChain) packData(outData []byte, data []byte, randLength int) {
|
|||||||
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
||||||
a.lastClientHash = a.hmac(key, outData[:outLength])
|
a.lastClientHash = a.hmac(key, outData[:outLength])
|
||||||
copy(outData[outLength:], a.lastClientHash[:2])
|
copy(outData[outLength:], a.lastClientHash[:2])
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeadLength = 4 + 8 + 4 + 16 + 4
|
const authHeadLength = 4 + 8 + 4 + 16 + 4
|
||||||
|
@ -86,7 +86,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
|
|||||||
|
|
||||||
size := int(binary.BigEndian.Uint16(r.sizeBuf))
|
size := int(binary.BigEndian.Uint16(r.sizeBuf))
|
||||||
if size > maxSize {
|
if size > maxSize {
|
||||||
return 0, errors.New("Buffer is larger than standard")
|
return 0, errors.New("buffer is larger than standard")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.Get(size)
|
buf := pool.Get(size)
|
||||||
|
@ -47,7 +47,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
|
|||||||
|
|
||||||
size := int(binary.BigEndian.Uint16(cr.sizeBuf))
|
size := int(binary.BigEndian.Uint16(cr.sizeBuf))
|
||||||
if size > maxSize {
|
if size > maxSize {
|
||||||
return 0, errors.New("Buffer is larger than standard")
|
return 0, errors.New("buffer is larger than standard")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b) >= size {
|
if len(b) >= size {
|
||||||
|
111
component/vmess/h2.go
Normal file
111
component/vmess/h2.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type h2Conn struct {
|
||||||
|
net.Conn
|
||||||
|
*http2.ClientConn
|
||||||
|
pwriter *io.PipeWriter
|
||||||
|
res *http.Response
|
||||||
|
cfg *H2Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type H2Config struct {
|
||||||
|
Hosts []string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *h2Conn) establishConn() error {
|
||||||
|
preader, pwriter := io.Pipe()
|
||||||
|
|
||||||
|
host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))]
|
||||||
|
path := hc.cfg.Path
|
||||||
|
// TODO: connect use VMess Host instead of H2 Host
|
||||||
|
req := http.Request{
|
||||||
|
Method: "PUT",
|
||||||
|
Host: host,
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
|
},
|
||||||
|
Proto: "HTTP/2",
|
||||||
|
ProtoMajor: 2,
|
||||||
|
ProtoMinor: 0,
|
||||||
|
Body: preader,
|
||||||
|
Header: map[string][]string{
|
||||||
|
"Accept-Encoding": {"identity"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := hc.ClientConn.RoundTrip(&req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hc.pwriter = pwriter
|
||||||
|
hc.res = res
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements net.Conn.Read()
|
||||||
|
func (hc *h2Conn) Read(b []byte) (int, error) {
|
||||||
|
if hc.res != nil && !hc.res.Close {
|
||||||
|
n, err := hc.res.Body.Read(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hc.establishConn(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return hc.res.Body.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (hc *h2Conn) Write(b []byte) (int, error) {
|
||||||
|
if hc.pwriter != nil {
|
||||||
|
return hc.pwriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hc.establishConn(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return hc.pwriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *h2Conn) Close() error {
|
||||||
|
if err := hc.pwriter.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := hc.Conn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) {
|
||||||
|
transport := &http2.Transport{}
|
||||||
|
|
||||||
|
cconn, err := transport.NewClientConn(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &h2Conn{
|
||||||
|
Conn: conn,
|
||||||
|
ClientConn: cconn,
|
||||||
|
cfg: cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -9,6 +9,7 @@ type TLSConfig struct {
|
|||||||
Host string
|
Host string
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
SessionCache tls.ClientSessionCache
|
SessionCache tls.ClientSessionCache
|
||||||
|
NextProtos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
@ -16,6 +17,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
ServerName: cfg.Host,
|
ServerName: cfg.Host,
|
||||||
InsecureSkipVerify: cfg.SkipCertVerify,
|
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||||
ClientSessionCache: cfg.SessionCache,
|
ClientSessionCache: cfg.SessionCache,
|
||||||
|
NextProtos: cfg.NextProtos,
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package vmess
|
package vmess
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
@ -37,11 +35,6 @@ var CipherMapping = map[string]byte{
|
|||||||
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
clientSessionCache tls.ClientSessionCache
|
|
||||||
once sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// Command types
|
// Command types
|
||||||
const (
|
const (
|
||||||
CommandTCP byte = 1
|
CommandTCP byte = 1
|
||||||
@ -106,7 +99,7 @@ func NewClient(config Config) (*Client, error) {
|
|||||||
security = SecurityAES128GCM
|
security = SecurityAES128GCM
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
return nil, fmt.Errorf("unknown security type: %s", config.Security)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
|
@ -73,7 +73,7 @@ func (wsc *websocketConn) Close() error {
|
|||||||
errors = append(errors, err.Error())
|
errors = append(errors, err.Error())
|
||||||
}
|
}
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ","))
|
return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ","))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
|||||||
if resp != nil {
|
if resp != nil {
|
||||||
reason = resp.Status
|
reason = resp.Status
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason)
|
return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &websocketConn{
|
return &websocketConn{
|
||||||
|
@ -69,6 +69,7 @@ type DNS struct {
|
|||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||||
|
Domain []string `yaml:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
@ -103,6 +104,7 @@ type RawDNS struct {
|
|||||||
type RawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
IPCIDR []string `yaml:"ipcidr"`
|
IPCIDR []string `yaml:"ipcidr"`
|
||||||
|
Domain []string `yaml:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
@ -264,11 +266,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
proxy, err := outbound.ParseProxy(mapping)
|
proxy, err := outbound.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("Proxy %d: %w", idx, err)
|
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exist := proxies[proxy.Name()]; exist {
|
if _, exist := proxies[proxy.Name()]; exist {
|
||||||
return nil, nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
return nil, nil, fmt.Errorf("proxy %s is the duplicate name", proxy.Name())
|
||||||
}
|
}
|
||||||
proxies[proxy.Name()] = proxy
|
proxies[proxy.Name()] = proxy
|
||||||
proxyList = append(proxyList, proxy.Name())
|
proxyList = append(proxyList, proxy.Name())
|
||||||
@ -278,7 +280,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
for idx, mapping := range groupsConfig {
|
for idx, mapping := range groupsConfig {
|
||||||
groupName, existName := mapping["name"].(string)
|
groupName, existName := mapping["name"].(string)
|
||||||
if !existName {
|
if !existName {
|
||||||
return nil, nil, fmt.Errorf("ProxyGroup %d: missing name", idx)
|
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
|
||||||
}
|
}
|
||||||
proxyList = append(proxyList, groupName)
|
proxyList = append(proxyList, groupName)
|
||||||
}
|
}
|
||||||
@ -313,12 +315,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
for idx, mapping := range groupsConfig {
|
for idx, mapping := range groupsConfig {
|
||||||
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("ProxyGroup[%d]: %w", idx, err)
|
return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
groupName := group.Name()
|
groupName := group.Name()
|
||||||
if _, exist := proxies[groupName]; exist {
|
if _, exist := proxies[groupName]; exist {
|
||||||
return nil, nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
|
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies[groupName] = outbound.NewProxy(group)
|
proxies[groupName] = outbound.NewProxy(group)
|
||||||
@ -373,11 +375,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
target = rule[2]
|
target = rule[2]
|
||||||
params = rule[3:]
|
params = rule[3:]
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
|
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := proxies[target]; !ok {
|
if _, ok := proxies[target]; !ok {
|
||||||
return nil, fmt.Errorf("Rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule = trimArr(rule)
|
rule = trimArr(rule)
|
||||||
@ -389,7 +391,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line)
|
log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
rules = append(rules, parsed)
|
rules = append(rules, parsed)
|
||||||
@ -403,7 +405,7 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
|||||||
|
|
||||||
// add default hosts
|
// add default hosts
|
||||||
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
|
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
|
||||||
println(err.Error())
|
log.Errorln("insert localhost to host error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Hosts) != 0 {
|
if len(cfg.Hosts) != 0 {
|
||||||
@ -499,7 +501,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
|||||||
|
|
||||||
func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||||
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
|
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsCfg := &DNS{
|
dnsCfg := &DNS{
|
||||||
@ -561,6 +563,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
|||||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||||
}
|
}
|
||||||
|
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
|
||||||
|
|
||||||
if cfg.UseHosts {
|
if cfg.UseHosts {
|
||||||
dnsCfg.Hosts = hosts
|
dnsCfg.Hosts = hosts
|
||||||
|
@ -32,18 +32,18 @@ func initMMDB() error {
|
|||||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find MMDB, start download")
|
log.Infoln("Can't find MMDB, start download")
|
||||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||||
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mmdb.Verify() {
|
if !mmdb.Verify() {
|
||||||
log.Warnln("MMDB invalid, remove and download")
|
log.Warnln("MMDB invalid, remove and download")
|
||||||
if err := os.Remove(C.Path.MMDB()); err != nil {
|
if err := os.Remove(C.Path.MMDB()); err != nil {
|
||||||
return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error())
|
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||||
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ func Init(dir string) error {
|
|||||||
// initial homedir
|
// initial homedir
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||||
return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error())
|
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ func Init(dir string) error {
|
|||||||
log.Infoln("Can't find config, create a initial config file")
|
log.Infoln("Can't find config, create a initial config file")
|
||||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error())
|
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
||||||
}
|
}
|
||||||
f.Write([]byte(`port: 7890`))
|
f.Write([]byte(`port: 7890`))
|
||||||
f.Close()
|
f.Close()
|
||||||
@ -72,7 +72,7 @@ func Init(dir string) error {
|
|||||||
|
|
||||||
// initial mmdb
|
// initial mmdb
|
||||||
if err := initMMDB(); err != nil {
|
if err := initMMDB(); err != nil {
|
||||||
return fmt.Errorf("Can't initial MMDB: %w", err)
|
return fmt.Errorf("can't initial MMDB: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,6 @@ func trimArr(arr []string) (r []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func or(pointers ...*int) *int {
|
|
||||||
for _, p := range pointers {
|
|
||||||
if p != nil {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pointers[len(pointers)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
|
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
|
||||||
// Meanwhile, record the original index in the config file.
|
// Meanwhile, record the original index in the config file.
|
||||||
// If loop is detected, return an error with location of loop.
|
// If loop is detected, return an error with location of loop.
|
||||||
@ -153,5 +144,5 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
|
|||||||
loopElements = append(loopElements, name)
|
loopElements = append(loopElements, name)
|
||||||
delete(graph, name)
|
delete(graph, name)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements)
|
return fmt.Errorf("loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements)
|
||||||
}
|
}
|
||||||
|
76
dns/enhancer.go
Normal file
76
dns/enhancer.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResolverEnhancer struct {
|
||||||
|
mode EnhancedMode
|
||||||
|
fakePool *fakeip.Pool
|
||||||
|
mapping *cache.LruCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResolverEnhancer) FakeIPEnabled() bool {
|
||||||
|
return h.mode == FAKEIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResolverEnhancer) MappingEnabled() bool {
|
||||||
|
return h.mode == FAKEIP || h.mode == MAPPING
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool {
|
||||||
|
if !h.FakeIPEnabled() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pool := h.fakePool; pool != nil {
|
||||||
|
return pool.Exist(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) {
|
||||||
|
if pool := h.fakePool; pool != nil {
|
||||||
|
if host, existed := pool.LookBack(ip); existed {
|
||||||
|
return host, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mapping := h.mapping; mapping != nil {
|
||||||
|
if host, existed := h.mapping.Get(ip.String()); existed {
|
||||||
|
return host.(string), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) {
|
||||||
|
if h.mapping != nil && o.mapping != nil {
|
||||||
|
o.mapping.CloneTo(h.mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.fakePool != nil && o.fakePool != nil {
|
||||||
|
h.fakePool.PatchFrom(o.fakePool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnhancer(cfg Config) *ResolverEnhancer {
|
||||||
|
var fakePool *fakeip.Pool
|
||||||
|
var mapping *cache.LruCache
|
||||||
|
|
||||||
|
if cfg.EnhancedMode != NORMAL {
|
||||||
|
fakePool = cfg.Pool
|
||||||
|
mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ResolverEnhancer{
|
||||||
|
mode: cfg.EnhancedMode,
|
||||||
|
fakePool: fakePool,
|
||||||
|
mapping: mapping,
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/mmdb"
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fallbackFilter interface {
|
type fallbackIPFilter interface {
|
||||||
Match(net.IP) bool
|
Match(net.IP) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,3 +25,22 @@ type ipnetFilter struct {
|
|||||||
func (inf *ipnetFilter) Match(ip net.IP) bool {
|
func (inf *ipnetFilter) Match(ip net.IP) bool {
|
||||||
return inf.ipnet.Contains(ip)
|
return inf.ipnet.Contains(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fallbackDomainFilter interface {
|
||||||
|
Match(domain string) bool
|
||||||
|
}
|
||||||
|
type domainFilter struct {
|
||||||
|
tree *trie.DomainTrie
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDomainFilter(domains []string) *domainFilter {
|
||||||
|
df := domainFilter{tree: trie.New()}
|
||||||
|
for _, domain := range domains {
|
||||||
|
df.tree.Insert(domain, "")
|
||||||
|
}
|
||||||
|
return &df
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *domainFilter) Match(domain string) bool {
|
||||||
|
return df.tree.Search(domain) != nil
|
||||||
|
}
|
||||||
|
@ -3,7 +3,9 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
@ -11,23 +13,21 @@ import (
|
|||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type handler func(w D.ResponseWriter, r *D.Msg)
|
type handler func(r *D.Msg) (*D.Msg, error)
|
||||||
type middleware func(next handler) handler
|
type middleware func(next handler) handler
|
||||||
|
|
||||||
func withHosts(hosts *trie.DomainTrie) middleware {
|
func withHosts(hosts *trie.DomainTrie) middleware {
|
||||||
return func(next handler) handler {
|
return func(next handler) handler {
|
||||||
return func(w D.ResponseWriter, r *D.Msg) {
|
return func(r *D.Msg) (*D.Msg, error) {
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
if !isIPRequest(q) {
|
if !isIPRequest(q) {
|
||||||
next(w, r)
|
return next(r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
record := hosts.Search(strings.TrimRight(q.Name, "."))
|
record := hosts.Search(strings.TrimRight(q.Name, "."))
|
||||||
if record == nil {
|
if record == nil {
|
||||||
next(w, r)
|
return next(r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := record.Data.(net.IP)
|
ip := record.Data.(net.IP)
|
||||||
@ -46,25 +46,67 @@ func withHosts(hosts *trie.DomainTrie) middleware {
|
|||||||
|
|
||||||
msg.Answer = []D.RR{rr}
|
msg.Answer = []D.RR{rr}
|
||||||
} else {
|
} else {
|
||||||
next(w, r)
|
return next(r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.SetRcode(r, D.RcodeSuccess)
|
msg.SetRcode(r, D.RcodeSuccess)
|
||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
msg.RecursionAvailable = true
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
w.WriteMsg(msg)
|
return msg, nil
|
||||||
return
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withMapping(mapping *cache.LruCache) middleware {
|
||||||
|
return func(next handler) handler {
|
||||||
|
return func(r *D.Msg) (*D.Msg, error) {
|
||||||
|
q := r.Question[0]
|
||||||
|
|
||||||
|
if !isIPRequest(q) {
|
||||||
|
return next(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := next(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
host := strings.TrimRight(q.Name, ".")
|
||||||
|
|
||||||
|
for _, ans := range msg.Answer {
|
||||||
|
var ip net.IP
|
||||||
|
var ttl uint32
|
||||||
|
|
||||||
|
switch a := ans.(type) {
|
||||||
|
case *D.A:
|
||||||
|
ip = a.A
|
||||||
|
ttl = a.Hdr.Ttl
|
||||||
|
case *D.AAAA:
|
||||||
|
ip = a.AAAA
|
||||||
|
ttl = a.Hdr.Ttl
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping.SetWithExpire(ip.String(), host, time.Now().Add(time.Second*time.Duration(ttl)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withFakeIP(fakePool *fakeip.Pool) middleware {
|
func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||||
return func(next handler) handler {
|
return func(next handler) handler {
|
||||||
return func(w D.ResponseWriter, r *D.Msg) {
|
return func(r *D.Msg) (*D.Msg, error) {
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
|
host := strings.TrimRight(q.Name, ".")
|
||||||
|
if fakePool.LookupHost(host) {
|
||||||
|
return next(r)
|
||||||
|
}
|
||||||
|
|
||||||
if q.Qtype == D.TypeAAAA {
|
if q.Qtype == D.TypeAAAA {
|
||||||
msg := &D.Msg{}
|
msg := &D.Msg{}
|
||||||
msg.Answer = []D.RR{}
|
msg.Answer = []D.RR{}
|
||||||
@ -73,17 +115,9 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
msg.RecursionAvailable = true
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
w.WriteMsg(msg)
|
return msg, nil
|
||||||
return
|
|
||||||
} else if q.Qtype != D.TypeA {
|
} else if q.Qtype != D.TypeA {
|
||||||
next(w, r)
|
return next(r)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
host := strings.TrimRight(q.Name, ".")
|
|
||||||
if fakePool.LookupHost(host) {
|
|
||||||
next(w, r)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rr := &D.A{}
|
rr := &D.A{}
|
||||||
@ -98,14 +132,13 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
msg.RecursionAvailable = true
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
w.WriteMsg(msg)
|
return msg, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withResolver(resolver *Resolver) handler {
|
func withResolver(resolver *Resolver) handler {
|
||||||
return func(w D.ResponseWriter, r *D.Msg) {
|
return func(r *D.Msg) (*D.Msg, error) {
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
// return a empty AAAA msg when ipv6 disabled
|
// return a empty AAAA msg when ipv6 disabled
|
||||||
@ -117,20 +150,18 @@ func withResolver(resolver *Resolver) handler {
|
|||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
msg.RecursionAvailable = true
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
w.WriteMsg(msg)
|
return msg, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := resolver.Exchange(r)
|
msg, err := resolver.Exchange(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
||||||
D.HandleFailed(w, r)
|
return msg, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
msg.SetRcode(r, msg.Rcode)
|
msg.SetRcode(r, msg.Rcode)
|
||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
w.WriteMsg(msg)
|
|
||||||
return
|
return msg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,15 +176,19 @@ func compose(middlewares []middleware, endpoint handler) handler {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHandler(resolver *Resolver) handler {
|
func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
||||||
middlewares := []middleware{}
|
middlewares := []middleware{}
|
||||||
|
|
||||||
if resolver.hosts != nil {
|
if resolver.hosts != nil {
|
||||||
middlewares = append(middlewares, withHosts(resolver.hosts))
|
middlewares = append(middlewares, withHosts(resolver.hosts))
|
||||||
}
|
}
|
||||||
|
|
||||||
if resolver.FakeIPEnabled() {
|
if mapper.mode == FAKEIP {
|
||||||
middlewares = append(middlewares, withFakeIP(resolver.pool))
|
middlewares = append(middlewares, withFakeIP(mapper.fakePool))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mapper.mode != NORMAL {
|
||||||
|
middlewares = append(middlewares, withMapping(mapper.mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
return compose(middlewares, withResolver(resolver))
|
return compose(middlewares, withResolver(resolver))
|
||||||
|
133
dns/resolver.go
133
dns/resolver.go
@ -35,16 +35,14 @@ type result struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
ipv6 bool
|
ipv6 bool
|
||||||
mapping bool
|
hosts *trie.DomainTrie
|
||||||
fakeip bool
|
main []dnsClient
|
||||||
hosts *trie.DomainTrie
|
fallback []dnsClient
|
||||||
pool *fakeip.Pool
|
fallbackDomainFilters []fallbackDomainFilter
|
||||||
main []dnsClient
|
fallbackIPFilters []fallbackIPFilter
|
||||||
fallback []dnsClient
|
group singleflight.Group
|
||||||
fallbackFilters []fallbackFilter
|
lruCache *cache.LruCache
|
||||||
group singleflight.Group
|
|
||||||
lruCache *cache.LruCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||||
@ -82,8 +80,8 @@ func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) {
|
|||||||
return r.resolveIP(host, D.TypeAAAA)
|
return r.resolveIP(host, D.TypeAAAA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) shouldFallback(ip net.IP) bool {
|
func (r *Resolver) shouldIPFallback(ip net.IP) bool {
|
||||||
for _, filter := range r.fallbackFilters {
|
for _, filter := range r.fallbackIPFilters {
|
||||||
if filter.Match(ip) {
|
if filter.Match(ip) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -106,7 +104,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||||
go r.exchangeWithoutCache(m)
|
go r.exchangeWithoutCache(m)
|
||||||
} else {
|
} else {
|
||||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -126,17 +124,11 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
msg := result.(*D.Msg)
|
msg := result.(*D.Msg)
|
||||||
|
|
||||||
putMsgToCache(r.lruCache, q.String(), msg)
|
putMsgToCache(r.lruCache, q.String(), msg)
|
||||||
if r.mapping || r.fakeip {
|
|
||||||
ips := r.msgToIP(msg)
|
|
||||||
for _, ip := range ips {
|
|
||||||
putMsgToCache(r.lruCache, ip.String(), msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
isIPReq := isIPRequest(q)
|
isIPReq := isIPRequest(q)
|
||||||
if isIPReq {
|
if isIPReq {
|
||||||
return r.fallbackExchange(m)
|
return r.ipExchange(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.batchExchange(r.main, m)
|
return r.batchExchange(r.main, m)
|
||||||
@ -152,40 +144,6 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPToHost return fake-ip or redir-host mapping host
|
|
||||||
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
|
||||||
if r.fakeip {
|
|
||||||
record, existed := r.pool.LookBack(ip)
|
|
||||||
if existed {
|
|
||||||
return record, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache, _ := r.lruCache.Get(ip.String())
|
|
||||||
if cache == nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
fqdn := cache.(*D.Msg).Question[0].Name
|
|
||||||
return strings.TrimRight(fqdn, "."), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) IsMapping() bool {
|
|
||||||
return r.mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
// FakeIPEnabled returns if fake-ip is enabled
|
|
||||||
func (r *Resolver) FakeIPEnabled() bool {
|
|
||||||
return r.fakeip
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFakeIP determine if given ip is a fake-ip
|
|
||||||
func (r *Resolver) IsFakeIP(ip net.IP) bool {
|
|
||||||
if r.FakeIPEnabled() {
|
|
||||||
return r.pool.Exist(ip)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
@ -203,7 +161,7 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
|||||||
|
|
||||||
elm := fast.Wait()
|
elm := fast.Wait()
|
||||||
if elm == nil {
|
if elm == nil {
|
||||||
err := errors.New("All DNS requests failed")
|
err := errors.New("all DNS requests failed")
|
||||||
if fErr := fast.Error(); fErr != nil {
|
if fErr := fast.Error(); fErr != nil {
|
||||||
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
||||||
}
|
}
|
||||||
@ -214,19 +172,49 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||||
|
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := r.msgToDomain(m)
|
||||||
|
|
||||||
|
if domain == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, df := range r.fallbackDomainFilters {
|
||||||
|
if df.Match(domain) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
|
||||||
|
onlyFallback := r.shouldOnlyQueryFallback(m)
|
||||||
|
|
||||||
|
if onlyFallback {
|
||||||
|
res := <-r.asyncExchange(r.fallback, m)
|
||||||
|
return res.Msg, res.Error
|
||||||
|
}
|
||||||
|
|
||||||
msgCh := r.asyncExchange(r.main, m)
|
msgCh := r.asyncExchange(r.main, m)
|
||||||
if r.fallback == nil {
|
|
||||||
|
if r.fallback == nil { // directly return if no fallback servers are available
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
msg, err = res.Msg, res.Error
|
msg, err = res.Msg, res.Error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||||
if !r.shouldFallback(ips[0]) {
|
if !r.shouldIPFallback(ips[0]) {
|
||||||
msg = res.Msg
|
msg = res.Msg // no need to wait for fallback result
|
||||||
err = res.Error
|
err = res.Error
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
@ -284,6 +272,14 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
|||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) msgToDomain(msg *D.Msg) string {
|
||||||
|
if len(msg.Question) > 0 {
|
||||||
|
return strings.TrimRight(msg.Question[0].Name, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
||||||
ch := make(chan *result, 1)
|
ch := make(chan *result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
@ -301,6 +297,7 @@ type NameServer struct {
|
|||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool
|
GeoIP bool
|
||||||
IPCIDR []*net.IPNet
|
IPCIDR []*net.IPNet
|
||||||
|
Domain []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -313,7 +310,7 @@ type Config struct {
|
|||||||
Hosts *trie.DomainTrie
|
Hosts *trie.DomainTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config Config) *Resolver {
|
func NewResolver(config Config) *Resolver {
|
||||||
defaultResolver := &Resolver{
|
defaultResolver := &Resolver{
|
||||||
main: transform(config.Default, nil),
|
main: transform(config.Default, nil),
|
||||||
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||||
@ -323,9 +320,6 @@ func New(config Config) *Resolver {
|
|||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: transform(config.Main, defaultResolver),
|
main: transform(config.Main, defaultResolver),
|
||||||
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||||
mapping: config.EnhancedMode == MAPPING,
|
|
||||||
fakeip: config.EnhancedMode == FAKEIP,
|
|
||||||
pool: config.Pool,
|
|
||||||
hosts: config.Hosts,
|
hosts: config.Hosts,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,14 +327,19 @@ func New(config Config) *Resolver {
|
|||||||
r.fallback = transform(config.Fallback, defaultResolver)
|
r.fallback = transform(config.Fallback, defaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
fallbackFilters := []fallbackFilter{}
|
fallbackIPFilters := []fallbackIPFilter{}
|
||||||
if config.FallbackFilter.GeoIP {
|
if config.FallbackFilter.GeoIP {
|
||||||
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
||||||
}
|
}
|
||||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||||
fallbackFilters = append(fallbackFilters, &ipnetFilter{ipnet: ipnet})
|
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
|
||||||
|
}
|
||||||
|
r.fallbackIPFilters = fallbackIPFilters
|
||||||
|
|
||||||
|
if len(config.FallbackFilter.Domain) != 0 {
|
||||||
|
fallbackDomainFilters := []fallbackDomainFilter{NewDomainFilter(config.FallbackFilter.Domain)}
|
||||||
|
r.fallbackDomainFilters = fallbackDomainFilters
|
||||||
}
|
}
|
||||||
r.fallbackFilters = fallbackFilters
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -27,16 +27,22 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.handler(w, r)
|
msg, err := s.handler(r)
|
||||||
|
if err != nil {
|
||||||
|
D.HandleFailed(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteMsg(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setHandler(handler handler) {
|
func (s *Server) setHandler(handler handler) {
|
||||||
s.handler = handler
|
s.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateServer(addr string, resolver *Resolver) error {
|
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) error {
|
||||||
if addr == address && resolver != nil {
|
if addr == address && resolver != nil {
|
||||||
handler := newHandler(resolver)
|
handler := newHandler(resolver, mapper)
|
||||||
server.setHandler(handler)
|
server.setHandler(handler)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -68,7 +74,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
address = addr
|
address = addr
|
||||||
handler := newHandler(resolver)
|
handler := newHandler(resolver, mapper)
|
||||||
server = &Server{handler: handler}
|
server = &Server{handler: handler}
|
||||||
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
|
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
|
||||||
|
|
||||||
|
12
go.mod
12
go.mod
@ -3,21 +3,21 @@ module github.com/Dreamacro/clash
|
|||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a
|
github.com/Dreamacro/go-shadowsocks2 v0.1.6
|
||||||
github.com/eapache/queue v1.1.0 // indirect
|
github.com/eapache/queue v1.1.0 // indirect
|
||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi v4.1.2+incompatible
|
||||||
github.com/go-chi/cors v1.1.1
|
github.com/go-chi/cors v1.1.1
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v3.3.0+incompatible
|
github.com/gofrs/uuid v3.3.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/miekg/dns v1.1.29
|
github.com/miekg/dns v1.1.31
|
||||||
github.com/oschwald/geoip2-golang v1.4.0
|
github.com/oschwald/geoip2-golang v1.4.0
|
||||||
github.com/sirupsen/logrus v1.6.0
|
github.com/sirupsen/logrus v1.6.0
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0
|
gopkg.in/eapache/channels.v1 v1.1.0
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
)
|
)
|
||||||
|
26
go.sum
26
go.sum
@ -1,5 +1,5 @@
|
|||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a h1:JhQFrFOkCpRB8qsN6PrzHFzjy/8iQpFFk5cbOiplh6s=
|
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -17,8 +17,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
|||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
|
||||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||||
@ -27,7 +27,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@ -36,24 +35,27 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||||
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
|
||||||
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -34,7 +34,7 @@ func readConfig(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil, fmt.Errorf("Configuration file %s is empty", path)
|
return nil, fmt.Errorf("configuration file %s is empty", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, err
|
return data, err
|
||||||
@ -101,13 +101,14 @@ func GetGeneral() *config.General {
|
|||||||
func updateExperimental(c *config.Config) {}
|
func updateExperimental(c *config.Config) {}
|
||||||
|
|
||||||
func updateDNS(c *config.DNS) {
|
func updateDNS(c *config.DNS) {
|
||||||
if c.Enable == false {
|
if !c.Enable {
|
||||||
resolver.DefaultResolver = nil
|
resolver.DefaultResolver = nil
|
||||||
tunnel.SetResolver(nil)
|
resolver.DefaultHostMapper = nil
|
||||||
dns.ReCreateServer("", nil)
|
dns.ReCreateServer("", nil, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r := dns.New(dns.Config{
|
|
||||||
|
cfg := dns.Config{
|
||||||
Main: c.NameServer,
|
Main: c.NameServer,
|
||||||
Fallback: c.Fallback,
|
Fallback: c.Fallback,
|
||||||
IPv6: c.IPv6,
|
IPv6: c.IPv6,
|
||||||
@ -117,12 +118,23 @@ func updateDNS(c *config.DNS) {
|
|||||||
FallbackFilter: dns.FallbackFilter{
|
FallbackFilter: dns.FallbackFilter{
|
||||||
GeoIP: c.FallbackFilter.GeoIP,
|
GeoIP: c.FallbackFilter.GeoIP,
|
||||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||||
|
Domain: c.FallbackFilter.Domain,
|
||||||
},
|
},
|
||||||
Default: c.DefaultNameserver,
|
Default: c.DefaultNameserver,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
r := dns.NewResolver(cfg)
|
||||||
|
m := dns.NewEnhancer(cfg)
|
||||||
|
|
||||||
|
// reuse cache of old host mapper
|
||||||
|
if old := resolver.DefaultHostMapper; old != nil {
|
||||||
|
m.PatchFrom(old.(*dns.ResolverEnhancer))
|
||||||
|
}
|
||||||
|
|
||||||
resolver.DefaultResolver = r
|
resolver.DefaultResolver = r
|
||||||
tunnel.SetResolver(r)
|
resolver.DefaultHostMapper = m
|
||||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
|
||||||
|
if err := dns.ReCreateServer(c.Listen, r, m); err != nil {
|
||||||
log.Errorln("Start DNS server error: %s", err.Error())
|
log.Errorln("Start DNS server error: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,6 @@ func Subscribe() observable.Subscription {
|
|||||||
|
|
||||||
func UnSubscribe(sub observable.Subscription) {
|
func UnSubscribe(sub observable.Subscription) {
|
||||||
source.UnSubscribe(sub)
|
source.UnSubscribe(sub)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Level() LogLevel {
|
func Level() LogLevel {
|
||||||
|
5
main.go
5
main.go
@ -10,7 +10,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
"github.com/Dreamacro/clash/constant"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/hub"
|
"github.com/Dreamacro/clash/hub"
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
@ -76,10 +75,10 @@ func main() {
|
|||||||
if testConfig {
|
if testConfig {
|
||||||
if _, err := executor.Parse(); err != nil {
|
if _, err := executor.Parse(); err != nil {
|
||||||
log.Errorln(err.Error())
|
log.Errorln(err.Error())
|
||||||
fmt.Printf("configuration file %s test failed\n", constant.Path.Config())
|
fmt.Printf("configuration file %s test failed\n", C.Path.Config())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf("configuration file %s test is successful\n", constant.Path.Config())
|
fmt.Printf("configuration file %s test is successful\n", C.Path.Config())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ func (l *HttpListener) Address() string {
|
|||||||
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
|
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
|
||||||
if result := cache.Get(loginStr); result != nil {
|
if result := cache.Get(loginStr); result != nil {
|
||||||
ret = result.(bool)
|
ret = result.(bool)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
loginData, err := base64.StdEncoding.DecodeString(loginStr)
|
loginData, err := base64.StdEncoding.DecodeString(loginStr)
|
||||||
login := strings.Split(string(loginData), ":")
|
login := strings.Split(string(loginData), ":")
|
||||||
@ -80,7 +81,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) {
|
|||||||
authenticator := authStore.Authenticator()
|
authenticator := authStore.Authenticator()
|
||||||
if authenticator != nil {
|
if authenticator != nil {
|
||||||
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
|
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
|
||||||
_, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
} else if !canActivate(authStrings[1], authenticator, cache) {
|
} else if !canActivate(authStrings[1], authenticator, cache) {
|
||||||
|
@ -30,14 +30,8 @@ var (
|
|||||||
httpMux sync.Mutex
|
httpMux sync.Mutex
|
||||||
redirMux sync.Mutex
|
redirMux sync.Mutex
|
||||||
mixedMux sync.Mutex
|
mixedMux sync.Mutex
|
||||||
tunMux sync.Mutex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type listener interface {
|
|
||||||
Close()
|
|
||||||
Address() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ports struct {
|
type Ports struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
|
@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
|
|||||||
|
|
||||||
func (c *packet) Drop() {
|
func (c *packet) Drop() {
|
||||||
pool.Put(c.buf)
|
pool.Put(c.buf)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
|
|||||||
|
|
||||||
func (c *packet) Drop() {
|
func (c *packet) Drop() {
|
||||||
pool.Put(c.bufRef)
|
pool.Put(c.bufRef)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
errPayload = errors.New("payload error")
|
errPayload = errors.New("payload error")
|
||||||
errParams = errors.New("params error")
|
|
||||||
ErrPlatformNotSupport = errors.New("not support on this platform")
|
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||||
ErrInvalidNetwork = errors.New("invalid network")
|
ErrInvalidNetwork = errors.New("invalid network")
|
||||||
|
|
||||||
|
@ -127,8 +127,8 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
// skip the first and last xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i < len(buf)-24; i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
inp, so := i, i+104
|
inp, so := i, i+104
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -17,7 +18,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
// store process name for when dealing with multiple PROCESS-NAME rules
|
||||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
var (
|
||||||
|
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||||
|
errNotFound = errors.New("process not found")
|
||||||
|
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
|
||||||
|
|
||||||
|
defaultSearcher *searcher
|
||||||
|
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
type Process struct {
|
type Process struct {
|
||||||
adapter string
|
adapter string
|
||||||
@ -28,7 +37,7 @@ func (ps *Process) RuleType() C.RuleType {
|
|||||||
return C.Process
|
return C.Process
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
func match(ps *Process, metadata *C.Metadata) bool {
|
||||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||||
cached, hit := processCache.Get(key)
|
cached, hit := processCache.Get(key)
|
||||||
if !hit {
|
if !hit {
|
||||||
@ -45,6 +54,10 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
|||||||
return strings.EqualFold(cached.(string), ps.process)
|
return strings.EqualFold(cached.(string), ps.process)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||||
|
return matchMeta(ps, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Process) Adapter() string {
|
func (p *Process) Adapter() string {
|
||||||
return p.adapter
|
return p.adapter
|
||||||
}
|
}
|
||||||
@ -58,6 +71,15 @@ func (p *Process) ShouldResolveIP() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewProcess(process string, adapter string) (*Process, error) {
|
func NewProcess(process string, adapter string) (*Process, error) {
|
||||||
|
once.Do(func() {
|
||||||
|
err := initSearcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||||
|
log.Warnln("All PROCESS-NAME rules will be skipped")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matchMeta = match
|
||||||
|
})
|
||||||
return &Process{
|
return &Process{
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
process: process,
|
process: process,
|
||||||
@ -85,28 +107,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
|||||||
return filepath.Base(string(buf[:size-1])), nil
|
return filepath.Base(string(buf[:size-1])), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchSocketPid(socket uint64) (uint32, error) {
|
|
||||||
value, err := syscall.Sysctl("kern.file")
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := []byte(value)
|
|
||||||
|
|
||||||
// struct xfile
|
|
||||||
itemSize := 128
|
|
||||||
for i := 0; i < len(buf); i += itemSize {
|
|
||||||
// xfile.xf_data
|
|
||||||
data := binary.BigEndian.Uint64(buf[i+56 : i+64])
|
|
||||||
if data == socket {
|
|
||||||
// xfile.xf_pid
|
|
||||||
pid := readNativeUint32(buf[i+8 : i+12])
|
|
||||||
return pid, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, errors.New("pid not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||||
ip := metadata.SrcIP
|
ip := metadata.SrcIP
|
||||||
port, err := strconv.Atoi(metadata.SrcPort)
|
port, err := strconv.Atoi(metadata.SrcPort)
|
||||||
@ -115,25 +115,18 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var spath string
|
var spath string
|
||||||
var itemSize int
|
var isTCP bool
|
||||||
var inpOffset int
|
|
||||||
switch metadata.NetWork {
|
switch metadata.NetWork {
|
||||||
case C.TCP:
|
case C.TCP:
|
||||||
spath = "net.inet.tcp.pcblist"
|
spath = "net.inet.tcp.pcblist"
|
||||||
// struct xtcpcb
|
isTCP = true
|
||||||
itemSize = 744
|
|
||||||
inpOffset = 8
|
|
||||||
case C.UDP:
|
case C.UDP:
|
||||||
spath = "net.inet.udp.pcblist"
|
spath = "net.inet.udp.pcblist"
|
||||||
// struct xinpcb
|
isTCP = false
|
||||||
itemSize = 400
|
|
||||||
inpOffset = 0
|
|
||||||
default:
|
default:
|
||||||
return "", ErrInvalidNetwork
|
return "", ErrInvalidNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
isIPv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
value, err := syscall.Sysctl(spath)
|
value, err := syscall.Sysctl(spath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -141,27 +134,73 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|||||||
|
|
||||||
buf := []byte(value)
|
buf := []byte(value)
|
||||||
|
|
||||||
// skip the first and last xinpgen(64 bytes) block
|
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
|
||||||
for i := 64; i < len(buf)-64; i += itemSize {
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getExecPathFromPID(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNativeUint32(b []byte) uint32 {
|
||||||
|
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
type searcher struct {
|
||||||
|
// sizeof(struct xinpgen)
|
||||||
|
headSize int
|
||||||
|
// sizeof(struct xtcpcb)
|
||||||
|
tcpItemSize int
|
||||||
|
// sizeof(struct xinpcb)
|
||||||
|
udpItemSize int
|
||||||
|
udpInpOffset int
|
||||||
|
port int
|
||||||
|
ip int
|
||||||
|
vflag int
|
||||||
|
socket int
|
||||||
|
|
||||||
|
// sizeof(struct xfile)
|
||||||
|
fileItemSize int
|
||||||
|
data int
|
||||||
|
pid int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) {
|
||||||
|
var itemSize int
|
||||||
|
var inpOffset int
|
||||||
|
|
||||||
|
if isTCP {
|
||||||
|
// struct xtcpcb
|
||||||
|
itemSize = s.tcpItemSize
|
||||||
|
inpOffset = 8
|
||||||
|
} else {
|
||||||
|
// struct xinpcb
|
||||||
|
itemSize = s.udpItemSize
|
||||||
|
inpOffset = s.udpInpOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
isIPv4 := ip.To4() != nil
|
||||||
|
// skip the first xinpgen block
|
||||||
|
for i := s.headSize; i+itemSize <= len(buf); i += itemSize {
|
||||||
inp := i + inpOffset
|
inp := i + inpOffset
|
||||||
|
|
||||||
srcPort := binary.BigEndian.Uint16(buf[inp+254 : inp+256])
|
srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2])
|
||||||
|
|
||||||
if uint16(port) != srcPort {
|
if port != srcPort {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// xinpcb.inp_vflag
|
// xinpcb.inp_vflag
|
||||||
flag := buf[inp+392]
|
flag := buf[inp+s.vflag]
|
||||||
|
|
||||||
var srcIP net.IP
|
var srcIP net.IP
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = net.IP(buf[inp+284 : inp+288])
|
srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4])
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = net.IP(buf[inp+272 : inp+288])
|
srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4])
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -171,17 +210,85 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison
|
// xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison
|
||||||
socket := binary.BigEndian.Uint64(buf[inp+16 : inp+24])
|
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
||||||
pid, err := searchSocketPid(socket)
|
return s.searchSocketPid(socket)
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
return 0, errNotFound
|
||||||
}
|
}
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
|
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||||
|
value, err := syscall.Sysctl("kern.file")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("process not found")
|
buf := []byte(value)
|
||||||
|
|
||||||
|
// struct xfile
|
||||||
|
itemSize := s.fileItemSize
|
||||||
|
for i := 0; i+itemSize <= len(buf); i += itemSize {
|
||||||
|
// xfile.xf_data
|
||||||
|
data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8])
|
||||||
|
if data == socket {
|
||||||
|
// xfile.xf_pid
|
||||||
|
pid := readNativeUint32(buf[i+s.pid : i+s.pid+4])
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func readNativeUint32(b []byte) uint32 {
|
func newSearcher(major int) *searcher {
|
||||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
var s *searcher = nil
|
||||||
|
switch major {
|
||||||
|
case 11:
|
||||||
|
s = &searcher{
|
||||||
|
headSize: 32,
|
||||||
|
tcpItemSize: 1304,
|
||||||
|
udpItemSize: 632,
|
||||||
|
port: 198,
|
||||||
|
ip: 228,
|
||||||
|
vflag: 116,
|
||||||
|
socket: 88,
|
||||||
|
fileItemSize: 80,
|
||||||
|
data: 56,
|
||||||
|
pid: 8,
|
||||||
|
udpInpOffset: 8,
|
||||||
|
}
|
||||||
|
case 12:
|
||||||
|
s = &searcher{
|
||||||
|
headSize: 64,
|
||||||
|
tcpItemSize: 744,
|
||||||
|
udpItemSize: 400,
|
||||||
|
port: 254,
|
||||||
|
ip: 284,
|
||||||
|
vflag: 392,
|
||||||
|
socket: 16,
|
||||||
|
fileItemSize: 128,
|
||||||
|
data: 56,
|
||||||
|
pid: 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSearcher() error {
|
||||||
|
osRelease, err := syscall.Sysctl("kern.osrelease")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dot := strings.Index(osRelease, ".")
|
||||||
|
if dot != -1 {
|
||||||
|
osRelease = osRelease[:dot]
|
||||||
|
}
|
||||||
|
major, err := strconv.Atoi(osRelease)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defaultSearcher = newSearcher(major)
|
||||||
|
if defaultSearcher == nil {
|
||||||
|
return fmt.Errorf("unsupported freebsd version %d", major)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(buffer[:n], socket) == 0 {
|
if bytes.Equal(buffer[:n], socket) {
|
||||||
cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline"))
|
cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -9,14 +9,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
|
||||||
req := request.R
|
req := request.R
|
||||||
host := req.Host
|
host := req.Host
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
|||||||
|
|
||||||
req.Header.Set("Connection", "close")
|
req.Header.Set("Connection", "close")
|
||||||
req.RequestURI = ""
|
req.RequestURI = ""
|
||||||
adapters.RemoveHopByHopHeaders(req.Header)
|
inbound.RemoveHopByHopHeaders(req.Header)
|
||||||
err := req.Write(outbound)
|
err := req.Write(outbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
@ -39,7 +38,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
adapters.RemoveHopByHopHeaders(resp.Header)
|
inbound.RemoveHopByHopHeaders(resp.Header)
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusContinue {
|
if resp.StatusCode == http.StatusContinue {
|
||||||
err = resp.Write(request)
|
err = resp.Write(request)
|
||||||
@ -121,14 +120,14 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
|
|||||||
from = fAddr
|
from = fAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = packet.WriteBack(buf[:n], from)
|
_, err = packet.WriteBack(buf[:n], from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
func handleSocket(request C.ServerAdapter, outbound net.Conn) {
|
||||||
relay(request, outbound)
|
relay(request, outbound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,23 +2,20 @@ package tunnel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DefaultManager *Manager
|
var DefaultManager *Manager
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
DefaultManager = &Manager{
|
DefaultManager = &Manager{}
|
||||||
upload: make(chan int64),
|
|
||||||
download: make(chan int64),
|
go DefaultManager.handle()
|
||||||
}
|
|
||||||
DefaultManager.handle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
connections sync.Map
|
connections sync.Map
|
||||||
upload chan int64
|
|
||||||
download chan int64
|
|
||||||
uploadTemp int64
|
uploadTemp int64
|
||||||
downloadTemp int64
|
downloadTemp int64
|
||||||
uploadBlip int64
|
uploadBlip int64
|
||||||
@ -35,16 +32,18 @@ func (m *Manager) Leave(c tracker) {
|
|||||||
m.connections.Delete(c.ID())
|
m.connections.Delete(c.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Upload() chan<- int64 {
|
func (m *Manager) PushUploaded(size int64) {
|
||||||
return m.upload
|
atomic.AddInt64(&m.uploadTemp, size)
|
||||||
|
atomic.AddInt64(&m.uploadTotal, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Download() chan<- int64 {
|
func (m *Manager) PushDownloaded(size int64) {
|
||||||
return m.download
|
atomic.AddInt64(&m.downloadTemp, size)
|
||||||
|
atomic.AddInt64(&m.downloadTotal, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Now() (up int64, down int64) {
|
func (m *Manager) Now() (up int64, down int64) {
|
||||||
return m.uploadBlip, m.downloadBlip
|
return atomic.LoadInt64(&m.uploadBlip), atomic.LoadInt64(&m.downloadBlip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Snapshot() *Snapshot {
|
func (m *Manager) Snapshot() *Snapshot {
|
||||||
@ -55,8 +54,8 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return &Snapshot{
|
return &Snapshot{
|
||||||
UploadTotal: m.uploadTotal,
|
UploadTotal: atomic.LoadInt64(&m.uploadTotal),
|
||||||
DownloadTotal: m.downloadTotal,
|
DownloadTotal: atomic.LoadInt64(&m.downloadTotal),
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,21 +70,13 @@ func (m *Manager) ResetStatistic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) handle() {
|
func (m *Manager) handle() {
|
||||||
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
|
|
||||||
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) {
|
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Second)
|
||||||
for {
|
|
||||||
select {
|
for range ticker.C {
|
||||||
case n := <-ch:
|
atomic.StoreInt64(&m.uploadBlip, atomic.LoadInt64(&m.uploadTemp))
|
||||||
*temp += n
|
atomic.StoreInt64(&m.uploadTemp, 0)
|
||||||
*total += n
|
atomic.StoreInt64(&m.downloadBlip, atomic.LoadInt64(&m.downloadTemp))
|
||||||
case <-ticker.C:
|
atomic.StoreInt64(&m.downloadTemp, 0)
|
||||||
*blip = *temp
|
|
||||||
*temp = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func (tt *tcpTracker) ID() string {
|
|||||||
func (tt *tcpTracker) Read(b []byte) (int, error) {
|
func (tt *tcpTracker) Read(b []byte) (int, error) {
|
||||||
n, err := tt.Conn.Read(b)
|
n, err := tt.Conn.Read(b)
|
||||||
download := int64(n)
|
download := int64(n)
|
||||||
tt.manager.Download() <- download
|
tt.manager.PushDownloaded(download)
|
||||||
tt.DownloadTotal += download
|
tt.DownloadTotal += download
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ func (tt *tcpTracker) Read(b []byte) (int, error) {
|
|||||||
func (tt *tcpTracker) Write(b []byte) (int, error) {
|
func (tt *tcpTracker) Write(b []byte) (int, error) {
|
||||||
n, err := tt.Conn.Write(b)
|
n, err := tt.Conn.Write(b)
|
||||||
upload := int64(n)
|
upload := int64(n)
|
||||||
tt.manager.Upload() <- upload
|
tt.manager.PushUploaded(upload)
|
||||||
tt.UploadTotal += upload
|
tt.UploadTotal += upload
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ func (ut *udpTracker) ID() string {
|
|||||||
func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
n, addr, err := ut.PacketConn.ReadFrom(b)
|
n, addr, err := ut.PacketConn.ReadFrom(b)
|
||||||
download := int64(n)
|
download := int64(n)
|
||||||
ut.manager.Download() <- download
|
ut.manager.PushDownloaded(download)
|
||||||
ut.DownloadTotal += download
|
ut.DownloadTotal += download
|
||||||
return n, addr, err
|
return n, addr, err
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
|||||||
func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
n, err := ut.PacketConn.WriteTo(b, addr)
|
n, err := ut.PacketConn.WriteTo(b, addr)
|
||||||
upload := int64(n)
|
upload := int64(n)
|
||||||
ut.manager.Upload() <- upload
|
ut.manager.PushUploaded(upload)
|
||||||
ut.UploadTotal += upload
|
ut.UploadTotal += upload
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
@ -12,21 +12,19 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/nat"
|
"github.com/Dreamacro/clash/component/nat"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
channels "gopkg.in/eapache/channels.v1"
|
channels "gopkg.in/eapache/channels.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tcpQueue = channels.NewInfiniteChannel()
|
tcpQueue = channels.NewInfiniteChannel()
|
||||||
udpQueue = channels.NewInfiniteChannel()
|
udpQueue = channels.NewInfiniteChannel()
|
||||||
natTable = nat.New()
|
natTable = nat.New()
|
||||||
rules []C.Rule
|
rules []C.Rule
|
||||||
proxies = make(map[string]C.Proxy)
|
proxies = make(map[string]C.Proxy)
|
||||||
providers map[string]provider.ProxyProvider
|
providers map[string]provider.ProxyProvider
|
||||||
configMux sync.RWMutex
|
configMux sync.RWMutex
|
||||||
enhancedMode *dns.Resolver
|
|
||||||
|
|
||||||
// Outbound Rule
|
// Outbound Rule
|
||||||
mode = Rule
|
mode = Rule
|
||||||
@ -89,11 +87,6 @@ func SetMode(m TunnelMode) {
|
|||||||
mode = m
|
mode = m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetResolver set custom dns resolver for enhanced mode
|
|
||||||
func SetResolver(r *dns.Resolver) {
|
|
||||||
enhancedMode = r
|
|
||||||
}
|
|
||||||
|
|
||||||
// processUDP starts a loop to handle udp packet
|
// processUDP starts a loop to handle udp packet
|
||||||
func processUDP() {
|
func processUDP() {
|
||||||
queue := udpQueue.Out()
|
queue := udpQueue.Out()
|
||||||
@ -120,7 +113,7 @@ func process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func needLookupIP(metadata *C.Metadata) bool {
|
func needLookupIP(metadata *C.Metadata) bool {
|
||||||
return enhancedMode != nil && (enhancedMode.IsMapping() || enhancedMode.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil
|
return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func preHandleMetadata(metadata *C.Metadata) error {
|
func preHandleMetadata(metadata *C.Metadata) error {
|
||||||
@ -131,17 +124,17 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
|||||||
|
|
||||||
// preprocess enhanced-mode metadata
|
// preprocess enhanced-mode metadata
|
||||||
if needLookupIP(metadata) {
|
if needLookupIP(metadata) {
|
||||||
host, exist := enhancedMode.IPToHost(metadata.DstIP)
|
host, exist := resolver.FindHostByIP(metadata.DstIP)
|
||||||
if exist {
|
if exist {
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
metadata.AddrType = C.AtypDomainName
|
metadata.AddrType = C.AtypDomainName
|
||||||
if enhancedMode.FakeIPEnabled() {
|
if resolver.FakeIPEnabled() {
|
||||||
metadata.DstIP = nil
|
metadata.DstIP = nil
|
||||||
} else if node := resolver.DefaultHosts.Search(host); node != nil {
|
} else if node := resolver.DefaultHosts.Search(host); node != nil {
|
||||||
// redir-host should lookup the hosts
|
// redir-host should lookup the hosts
|
||||||
metadata.DstIP = node.Data.(net.IP)
|
metadata.DstIP = node.Data.(net.IP)
|
||||||
}
|
}
|
||||||
} else if enhancedMode.IsFakeIP(metadata.DstIP) {
|
} else if resolver.IsFakeIP(metadata.DstIP) {
|
||||||
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +170,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
|||||||
|
|
||||||
// make a fAddr if requset ip is fakeip
|
// make a fAddr if requset ip is fakeip
|
||||||
var fAddr net.Addr
|
var fAddr net.Addr
|
||||||
if enhancedMode != nil && enhancedMode.IsFakeIP(metadata.DstIP) {
|
if resolver.IsFakeIP(metadata.DstIP) {
|
||||||
fAddr = metadata.UDPAddr()
|
fAddr = metadata.UDPAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user