Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
1e5593f1a9 | |||
34febc4579 | |||
97581148b5 | |||
0402878daa | |||
4735f61fd1 | |||
16ae107e70 | |||
83efe2ae57 | |||
87e4d94290 | |||
b98e9ea202 | |||
9a62b1081d | |||
2cd1b890ce | |||
ba060bd0ee | |||
b1795b1e3d | |||
76c9820065 | |||
2db4ce57ef | |||
50b3d497f6 | |||
2321e9139d | |||
baabf21340 | |||
d3bb4c65a8 | |||
8c3e2a7559 | |||
bc52f8e4fd | |||
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 |
65
.github/workflows/docker.yml
vendored
65
.github/workflows/docker.yml
vendored
@ -17,37 +17,60 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up docker buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
buildx-version: latest
|
||||
skip-cache: false
|
||||
qemu-version: latest
|
||||
version: latest
|
||||
|
||||
- name: Docker login
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Docker buildx image and push on dev branch
|
||||
- name: Login to Github Package
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: Dreamacro
|
||||
password: ${{ secrets.PACKAGE_TOKEN }}
|
||||
|
||||
- name: Build dev branch and push
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev .
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
push: true
|
||||
tags: 'dreamacro/clash:dev,ghcr.io/dreamacro/clash:dev'
|
||||
|
||||
- name: Replace tag without `v`
|
||||
- name: Get all docker tags
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/github-script@v1
|
||||
id: version
|
||||
uses: actions/github-script@v3
|
||||
id: tags
|
||||
with:
|
||||
script: |
|
||||
return context.payload.ref.replace(/\/?refs\/tags\/v/, '')
|
||||
const ref = `${context.payload.ref.replace(/\/?refs\/tags\//, '')}`
|
||||
const tags = [
|
||||
'dreamacro/clash:latest',
|
||||
`dreamacro/clash:${ref}`,
|
||||
'ghcr.io/dreamacro/clash:latest',
|
||||
`ghcr.io/dreamacro/clash:${ref}`
|
||||
]
|
||||
return tags.join(',')
|
||||
result-encoding: string
|
||||
|
||||
- name: Docker buildx image and push on release
|
||||
- name: Build release and push
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} .
|
||||
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest .
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
push: true
|
||||
tags: ${{steps.tags.outputs.result}}
|
||||
|
5
.github/workflows/go.yml
vendored
5
.github/workflows/go.yml
vendored
@ -22,9 +22,12 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Get dependencies and run test
|
||||
- name: Get dependencies, run test and static check
|
||||
run: |
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go get -u honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck -- $(go list ./...)
|
||||
|
||||
- name: Build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
6
.github/workflows/stale.yml
vendored
6
.github/workflows/stale.yml
vendored
@ -11,9 +11,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v1
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
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'
|
||||
days-before-stale: 120
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
days-before-stale: 60
|
||||
days-before-close: 5
|
||||
|
@ -10,6 +10,7 @@ RUN go mod download && \
|
||||
mv ./bin/clash-docker /clash
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||
|
8
Makefile
8
Makefile
@ -4,7 +4,7 @@ VERSION=$(shell git describe --tags || echo "unknown version")
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s'
|
||||
-w -s -buildid='
|
||||
|
||||
PLATFORM_LIST = \
|
||||
darwin-amd64 \
|
||||
@ -25,7 +25,8 @@ PLATFORM_LIST = \
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64
|
||||
windows-amd64 \
|
||||
windows-arm32v7
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
|
||||
@ -83,6 +84,9 @@ windows-386:
|
||||
windows-amd64:
|
||||
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))
|
||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||
|
||||
|
@ -6,15 +6,12 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultURLTestTimeout = time.Second * 5
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
@ -99,11 +96,11 @@ func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue
|
||||
alive uint32
|
||||
alive *atomic.Bool
|
||||
}
|
||||
|
||||
func (p *Proxy) Alive() bool {
|
||||
return atomic.LoadUint32(&p.alive) > 0
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
@ -115,7 +112,7 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
||||
if err != nil {
|
||||
atomic.StoreUint32(&p.alive, 0)
|
||||
p.alive.Store(false)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
@ -132,7 +129,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||
func (p *Proxy) LastDelay() (delay uint16) {
|
||||
var max uint16 = 0xffff
|
||||
if atomic.LoadUint32(&p.alive) == 0 {
|
||||
if !p.alive.Load() {
|
||||
return max
|
||||
}
|
||||
|
||||
@ -163,11 +160,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
// URLTest get the delay for the specified URL
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
atomic.StoreUint32(&p.alive, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&p.alive, 0)
|
||||
}
|
||||
p.alive.Store(err == nil)
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if err == nil {
|
||||
record.Delay = t
|
||||
@ -223,5 +216,5 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
}
|
||||
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{adapter, queue.New(10), 1}
|
||||
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ type HttpOption struct {
|
||||
UserName string `proxy:"username,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SNI string `proxy:"sni,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 {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
sni := option.Server
|
||||
if option.SNI != "" {
|
||||
sni = option.SNI
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
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})
|
||||
proxyType, existType := mapping["type"].(string)
|
||||
if !existType {
|
||||
return nil, fmt.Errorf("Missing type")
|
||||
return nil, fmt.Errorf("missing type")
|
||||
}
|
||||
|
||||
var proxy C.ProxyAdapter
|
||||
err := fmt.Errorf("Cannot parse")
|
||||
var (
|
||||
proxy C.ProxyAdapter
|
||||
err error
|
||||
)
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &ShadowSocksOption{}
|
||||
@ -72,7 +74,7 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
}
|
||||
proxy, err = NewTrojan(*trojanOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -16,7 +16,9 @@ import (
|
||||
type Snell struct {
|
||||
*Base
|
||||
psk []byte
|
||||
pool *snell.Pool
|
||||
obfsOption *simpleObfsOption
|
||||
version int
|
||||
}
|
||||
|
||||
type SnellOption struct {
|
||||
@ -24,24 +26,47 @@ type SnellOption struct {
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Psk string `proxy:"psk"`
|
||||
Version int `proxy:"version,omitempty"`
|
||||
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
switch s.obfsOption.Mode {
|
||||
type streamOption struct {
|
||||
psk []byte
|
||||
version int
|
||||
addr string
|
||||
obfsOption *simpleObfsOption
|
||||
}
|
||||
|
||||
func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||
switch option.obfsOption.Mode {
|
||||
case "tls":
|
||||
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
||||
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
||||
case "http":
|
||||
_, port, _ := net.SplitHostPort(s.addr)
|
||||
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
||||
_, port, _ := net.SplitHostPort(option.addr)
|
||||
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)
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(port))
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
return c, err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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 &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{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
@ -74,5 +107,19 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
},
|
||||
psk: psk,
|
||||
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"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
@ -44,6 +45,11 @@ type HTTPOptions struct {
|
||||
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) {
|
||||
var err error
|
||||
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)
|
||||
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)
|
||||
httpOpts := &vmess.HTTPConfig{
|
||||
Host: host,
|
||||
@ -80,6 +105,30 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
}
|
||||
|
||||
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:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
@ -152,13 +201,16 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.Network == "h2" && !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||
}
|
||||
|
||||
return &Vmess{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Vmess,
|
||||
udp: true,
|
||||
udp: option.UDP,
|
||||
},
|
||||
client: client,
|
||||
option: &option,
|
||||
|
@ -11,10 +11,14 @@ const (
|
||||
defaultGetProxiesDuration = time.Second * 5
|
||||
)
|
||||
|
||||
func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy {
|
||||
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
|
||||
proxies := []C.Proxy{}
|
||||
for _, provider := range providers {
|
||||
proxies = append(proxies, provider.Proxies()...)
|
||||
if touch {
|
||||
proxies = append(proxies, provider.ProxiesWithTouch()...)
|
||||
} else {
|
||||
proxies = append(proxies, provider.Proxies()...)
|
||||
}
|
||||
}
|
||||
return proxies
|
||||
}
|
||||
|
@ -12,17 +12,18 @@ import (
|
||||
|
||||
type Fallback struct {
|
||||
*outbound.Base
|
||||
single *singledo.Single
|
||||
providers []provider.ProxyProvider
|
||||
disableUDP bool
|
||||
single *singledo.Single
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
func (f *Fallback) Now() string {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(false)
|
||||
return proxy.Name()
|
||||
}
|
||||
|
||||
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(true)
|
||||
c, err := proxy.DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
c.AppendToChains(f)
|
||||
@ -31,7 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
}
|
||||
|
||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(true)
|
||||
pc, err := proxy.DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(f)
|
||||
@ -40,13 +41,17 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
}
|
||||
|
||||
func (f *Fallback) SupportUDP() bool {
|
||||
proxy := f.findAliveProxy()
|
||||
if f.disableUDP {
|
||||
return false
|
||||
}
|
||||
|
||||
proxy := f.findAliveProxy(false)
|
||||
return proxy.SupportUDP()
|
||||
}
|
||||
|
||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range f.proxies() {
|
||||
for _, proxy := range f.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
@ -57,33 +62,34 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(true)
|
||||
return proxy
|
||||
}
|
||||
|
||||
func (f *Fallback) proxies() []C.Proxy {
|
||||
func (f *Fallback) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := f.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(f.providers), nil
|
||||
return getProvidersProxies(f.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (f *Fallback) findAliveProxy() C.Proxy {
|
||||
proxies := f.proxies()
|
||||
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||
proxies := f.proxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Alive() {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return f.proxies()[0]
|
||||
return proxies[0]
|
||||
}
|
||||
|
||||
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
|
||||
func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
||||
return &Fallback{
|
||||
Base: outbound.NewBase(name, "", C.Fallback, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
Base: outbound.NewBase(options.Name, "", C.Fallback, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
disableUDP: options.DisableUDP,
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package outboundgroup
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
@ -14,11 +16,25 @@ import (
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
|
||||
|
||||
type LoadBalance struct {
|
||||
*outbound.Base
|
||||
single *singledo.Single
|
||||
maxRetry int
|
||||
providers []provider.ProxyProvider
|
||||
disableUDP bool
|
||||
single *singledo.Single
|
||||
providers []provider.ProxyProvider
|
||||
strategyFn strategyFn
|
||||
}
|
||||
|
||||
var errStrategy = errors.New("unsupported strategy")
|
||||
|
||||
func parseStrategy(config map[string]interface{}) string {
|
||||
if elm, ok := config["strategy"]; ok {
|
||||
if strategy, ok := elm.(string); ok {
|
||||
return strategy
|
||||
}
|
||||
}
|
||||
return "consistent-hashing"
|
||||
}
|
||||
|
||||
func getKey(metadata *C.Metadata) string {
|
||||
@ -78,27 +94,50 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) SupportUDP() bool {
|
||||
return true
|
||||
return !lb.disableUDP
|
||||
}
|
||||
|
||||
func strategyRoundRobin() strategyFn {
|
||||
idx := 0
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
length := len(proxies)
|
||||
for i := 0; i < length; i++ {
|
||||
idx = (idx + 1) % length
|
||||
proxy := proxies[idx]
|
||||
if proxy.Alive() {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return proxies[0]
|
||||
}
|
||||
}
|
||||
|
||||
func strategyConsistentHashing() strategyFn {
|
||||
maxRetry := 5
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||
buckets := int32(len(proxies))
|
||||
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||
idx := jumpHash(key, buckets)
|
||||
proxy := proxies[idx]
|
||||
if proxy.Alive() {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return proxies[0]
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||
proxies := lb.proxies()
|
||||
buckets := int32(len(proxies))
|
||||
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
|
||||
idx := jumpHash(key, buckets)
|
||||
proxy := proxies[idx]
|
||||
if proxy.Alive() {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return proxies[0]
|
||||
proxies := lb.proxies(true)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) proxies() []C.Proxy {
|
||||
func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := lb.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(lb.providers), nil
|
||||
return getProvidersProxies(lb.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
@ -106,7 +145,7 @@ func (lb *LoadBalance) proxies() []C.Proxy {
|
||||
|
||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range lb.proxies() {
|
||||
for _, proxy := range lb.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
@ -115,11 +154,21 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
|
||||
return &LoadBalance{
|
||||
Base: outbound.NewBase(name, "", C.LoadBalance, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
maxRetry: 3,
|
||||
providers: providers,
|
||||
func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
|
||||
var strategyFn strategyFn
|
||||
switch strategy {
|
||||
case "consistent-hashing":
|
||||
strategyFn = strategyConsistentHashing()
|
||||
case "round-robin":
|
||||
strategyFn = strategyRoundRobin()
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
||||
}
|
||||
return &LoadBalance{
|
||||
Base: outbound.NewBase(options.Name, "", C.LoadBalance, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
strategyFn: strategyFn,
|
||||
disableUDP: options.DisableUDP,
|
||||
}, nil
|
||||
}
|
||||
|
@ -12,25 +12,28 @@ import (
|
||||
var (
|
||||
errFormat = errors.New("format error")
|
||||
errType = errors.New("unsupport type")
|
||||
errMissUse = errors.New("`use` field should not be empty")
|
||||
errMissProxy = errors.New("`use` or `proxies` missing")
|
||||
errMissHealthCheck = errors.New("`url` or `interval` missing")
|
||||
errDuplicateProvider = errors.New("`duplicate provider name")
|
||||
)
|
||||
|
||||
type GroupCommonOption struct {
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
|
||||
|
||||
groupOption := &GroupCommonOption{}
|
||||
groupOption := &GroupCommonOption{
|
||||
Lazy: true,
|
||||
}
|
||||
if err := decoder.Decode(config, groupOption); err != nil {
|
||||
return nil, errFormat
|
||||
}
|
||||
@ -55,7 +58,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
|
||||
// if Use not empty, drop health check options
|
||||
if len(groupOption.Use) != 0 {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -63,9 +66,13 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
|
||||
providers = append(providers, pd)
|
||||
} else {
|
||||
if _, ok := providersMap[groupName]; ok {
|
||||
return nil, errDuplicateProvider
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -78,7 +85,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
return nil, errMissHealthCheck
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -102,15 +109,16 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
switch groupOption.Type {
|
||||
case "url-test":
|
||||
opts := parseURLTestOption(config)
|
||||
group = NewURLTest(groupName, providers, opts...)
|
||||
group = NewURLTest(groupOption, providers, opts...)
|
||||
case "select":
|
||||
group = NewSelector(groupName, providers)
|
||||
group = NewSelector(groupOption, providers)
|
||||
case "fallback":
|
||||
group = NewFallback(groupName, providers)
|
||||
group = NewFallback(groupOption, providers)
|
||||
case "load-balance":
|
||||
group = NewLoadBalance(groupName, providers)
|
||||
strategy := parseStrategy(config)
|
||||
return NewLoadBalance(groupOption, providers, strategy)
|
||||
case "relay":
|
||||
group = NewRelay(groupName, providers)
|
||||
group = NewRelay(groupOption, providers)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ type Relay struct {
|
||||
}
|
||||
|
||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxies := r.proxies(metadata)
|
||||
proxies := r.proxies(metadata, true)
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("Proxy does not exist")
|
||||
return nil, errors.New("proxy does not exist")
|
||||
}
|
||||
first := proxies[0]
|
||||
last := proxies[len(proxies)-1]
|
||||
@ -58,7 +58,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range r.rawProxies() {
|
||||
for _, proxy := range r.rawProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
@ -67,16 +67,16 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Relay) rawProxies() []C.Proxy {
|
||||
func (r *Relay) rawProxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := r.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(r.providers), nil
|
||||
return getProvidersProxies(r.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
|
||||
proxies := r.rawProxies()
|
||||
func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
||||
proxies := r.rawProxies(touch)
|
||||
|
||||
for n, proxy := range proxies {
|
||||
subproxy := proxy.Unwrap(metadata)
|
||||
@ -89,9 +89,9 @@ func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
|
||||
return proxies
|
||||
}
|
||||
|
||||
func NewRelay(name string, providers []provider.ProxyProvider) *Relay {
|
||||
func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||
return &Relay{
|
||||
Base: outbound.NewBase(name, "", C.Relay, false),
|
||||
Base: outbound.NewBase(options.Name, "", C.Relay, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
}
|
||||
|
@ -13,13 +13,14 @@ import (
|
||||
|
||||
type Selector struct {
|
||||
*outbound.Base
|
||||
single *singledo.Single
|
||||
selected string
|
||||
providers []provider.ProxyProvider
|
||||
disableUDP bool
|
||||
single *singledo.Single
|
||||
selected string
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := s.selectedProxy().DialContext(ctx, metadata)
|
||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
c.AppendToChains(s)
|
||||
}
|
||||
@ -27,7 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
}
|
||||
|
||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := s.selectedProxy().DialUDP(metadata)
|
||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(s)
|
||||
}
|
||||
@ -35,12 +36,16 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
}
|
||||
|
||||
func (s *Selector) SupportUDP() bool {
|
||||
return s.selectedProxy().SupportUDP()
|
||||
if s.disableUDP {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.selectedProxy(false).SupportUDP()
|
||||
}
|
||||
|
||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range getProvidersProxies(s.providers) {
|
||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
@ -52,11 +57,11 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *Selector) Now() string {
|
||||
return s.selectedProxy().Name()
|
||||
return s.selectedProxy(false).Name()
|
||||
}
|
||||
|
||||
func (s *Selector) Set(name string) error {
|
||||
for _, proxy := range getProvidersProxies(s.providers) {
|
||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||
if proxy.Name() == name {
|
||||
s.selected = name
|
||||
s.single.Reset()
|
||||
@ -64,16 +69,16 @@ 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 {
|
||||
return s.selectedProxy()
|
||||
return s.selectedProxy(true)
|
||||
}
|
||||
|
||||
func (s *Selector) selectedProxy() C.Proxy {
|
||||
func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||
elm, _, _ := s.single.Do(func() (interface{}, error) {
|
||||
proxies := getProvidersProxies(s.providers)
|
||||
proxies := getProvidersProxies(s.providers, touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Name() == s.selected {
|
||||
return proxy, nil
|
||||
@ -86,12 +91,13 @@ func (s *Selector) selectedProxy() C.Proxy {
|
||||
return elm.(C.Proxy)
|
||||
}
|
||||
|
||||
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
|
||||
func NewSelector(options *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||
selected := providers[0].Proxies()[0].Name()
|
||||
return &Selector{
|
||||
Base: outbound.NewBase(name, "", C.Selector, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
selected: selected,
|
||||
Base: outbound.NewBase(options.Name, "", C.Selector, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
selected: selected,
|
||||
disableUDP: options.DisableUDP,
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
|
||||
type URLTest struct {
|
||||
*outbound.Base
|
||||
tolerance uint16
|
||||
disableUDP bool
|
||||
fastNode C.Proxy
|
||||
single *singledo.Single
|
||||
fastSingle *singledo.Single
|
||||
@ -29,11 +30,11 @@ type URLTest struct {
|
||||
}
|
||||
|
||||
func (u *URLTest) Now() string {
|
||||
return u.fast().Name()
|
||||
return u.fast(false).Name()
|
||||
}
|
||||
|
||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||
c, err = u.fast().DialContext(ctx, metadata)
|
||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
c.AppendToChains(u)
|
||||
}
|
||||
@ -41,7 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
||||
}
|
||||
|
||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := u.fast().DialUDP(metadata)
|
||||
pc, err := u.fast(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(u)
|
||||
}
|
||||
@ -49,20 +50,20 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
}
|
||||
|
||||
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return u.fast()
|
||||
return u.fast(true)
|
||||
}
|
||||
|
||||
func (u *URLTest) proxies() []C.Proxy {
|
||||
func (u *URLTest) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := u.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(u.providers), nil
|
||||
return getProvidersProxies(u.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (u *URLTest) fast() C.Proxy {
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
|
||||
proxies := u.proxies()
|
||||
proxies := u.proxies(touch)
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
for _, proxy := range proxies[1:] {
|
||||
@ -78,7 +79,7 @@ func (u *URLTest) fast() C.Proxy {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -89,12 +90,16 @@ func (u *URLTest) fast() C.Proxy {
|
||||
}
|
||||
|
||||
func (u *URLTest) SupportUDP() bool {
|
||||
return u.fast().SupportUDP()
|
||||
if u.disableUDP {
|
||||
return false
|
||||
}
|
||||
|
||||
return u.fast(false).SupportUDP()
|
||||
}
|
||||
|
||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range u.proxies() {
|
||||
for _, proxy := range u.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
@ -117,12 +122,13 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption {
|
||||
return opts
|
||||
}
|
||||
|
||||
func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
|
||||
func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
|
||||
urlTest := &URLTest{
|
||||
Base: outbound.NewBase(name, "", C.URLTest, false),
|
||||
Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||
providers: providers,
|
||||
disableUDP: commonOptions.DisableUDP,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -17,10 +19,12 @@ type HealthCheckOption struct {
|
||||
}
|
||||
|
||||
type HealthCheck struct {
|
||||
url string
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
done chan struct{}
|
||||
url string
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
lazy bool
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
@ -30,7 +34,10 @@ func (hc *HealthCheck) process() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
hc.check()
|
||||
now := time.Now().Unix()
|
||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||
hc.check()
|
||||
}
|
||||
case <-hc.done:
|
||||
ticker.Stop()
|
||||
return
|
||||
@ -46,6 +53,10 @@ func (hc *HealthCheck) auto() bool {
|
||||
return hc.interval != 0
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
for _, proxy := range hc.proxies {
|
||||
@ -60,11 +71,13 @@ func (hc *HealthCheck) close() {
|
||||
hc.done <- struct{}{}
|
||||
}
|
||||
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck {
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
|
||||
return &HealthCheck{
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
interval: interval,
|
||||
done: make(chan struct{}, 1),
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ type healthCheckSchema struct {
|
||||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
@ -30,7 +31,11 @@ type proxyProviderSchema struct {
|
||||
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
|
||||
schema := &proxyProviderSchema{}
|
||||
schema := &proxyProviderSchema{
|
||||
HealthCheck: healthCheckSchema{
|
||||
Lazy: true,
|
||||
},
|
||||
}
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -39,7 +44,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
||||
if schema.HealthCheck.Enable {
|
||||
hcInterval = uint(schema.HealthCheck.Interval)
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
|
||||
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
|
||||
|
@ -50,6 +50,9 @@ type Provider interface {
|
||||
type ProxyProvider interface {
|
||||
Provider
|
||||
Proxies() []C.Proxy
|
||||
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
||||
// Commonly used in Dial and DialUDP
|
||||
ProxiesWithTouch() []C.Proxy
|
||||
HealthCheck()
|
||||
}
|
||||
|
||||
@ -112,6 +115,11 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||
return pp.proxies
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
|
||||
pp.healthCheck.touch()
|
||||
return pp.Proxies()
|
||||
}
|
||||
|
||||
func proxiesParse(buf []byte) (interface{}, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
@ -120,20 +128,20 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
||||
}
|
||||
|
||||
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{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
@ -223,6 +231,11 @@ func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||
return cp.proxies
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy {
|
||||
cp.healthCheck.touch()
|
||||
return cp.Proxies()
|
||||
}
|
||||
|
||||
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||
pd.healthCheck.close()
|
||||
}
|
||||
|
21
common/cache/lrucache.go
vendored
21
common/cache/lrucache.go
vendored
@ -121,7 +121,7 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
|
||||
c.SetWithExpire(key, value, time.Unix(expires, 0))
|
||||
}
|
||||
|
||||
// SetWithExpire stores the interface{} representation of a response for a given key and given exires.
|
||||
// SetWithExpire stores the interface{} representation of a response for a given key and given expires.
|
||||
// The expires time will round to second.
|
||||
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
|
||||
c.mu.Lock()
|
||||
@ -146,6 +146,23 @@ func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires tim
|
||||
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 {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@ -171,7 +188,7 @@ func (c *LruCache) get(key interface{}) *entry {
|
||||
}
|
||||
|
||||
// Delete removes the value associated with a key.
|
||||
func (c *LruCache) Delete(key string) {
|
||||
func (c *LruCache) Delete(key interface{}) {
|
||||
c.mu.Lock()
|
||||
|
||||
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, 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"))
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package observable
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
func iterator(item []interface{}) chan interface{} {
|
||||
@ -33,25 +33,25 @@ func TestObservable(t *testing.T) {
|
||||
assert.Equal(t, count, 5)
|
||||
}
|
||||
|
||||
func TestObservable_MutilSubscribe(t *testing.T) {
|
||||
func TestObservable_MultiSubscribe(t *testing.T) {
|
||||
iter := iterator([]interface{}{1, 2, 3, 4, 5})
|
||||
src := NewObservable(iter)
|
||||
ch1, _ := src.Subscribe()
|
||||
ch2, _ := src.Subscribe()
|
||||
var count int32
|
||||
var count = atomic.NewInt32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
waitCh := func(ch <-chan interface{}) {
|
||||
for range ch {
|
||||
atomic.AddInt32(&count, 1)
|
||||
count.Inc()
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
go waitCh(ch1)
|
||||
go waitCh(ch2)
|
||||
wg.Wait()
|
||||
assert.Equal(t, int32(10), count)
|
||||
assert.Equal(t, int32(10), count.Load())
|
||||
}
|
||||
|
||||
func TestObservable_UnSubscribe(t *testing.T) {
|
||||
@ -113,3 +113,34 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
|
||||
_, more := <-list[0]
|
||||
assert.False(t, more)
|
||||
}
|
||||
|
||||
func Benchmark_Observable_1000(b *testing.B) {
|
||||
ch := make(chan interface{})
|
||||
o := NewObservable(ch)
|
||||
num := 1000
|
||||
|
||||
subs := []Subscription{}
|
||||
for i := 0; i < num; i++ {
|
||||
sub, _ := o.Subscribe()
|
||||
subs = append(subs, sub)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(num)
|
||||
|
||||
b.ResetTimer()
|
||||
for _, sub := range subs {
|
||||
go func(s Subscription) {
|
||||
for range s {
|
||||
}
|
||||
wg.Done()
|
||||
}(sub)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ch <- i
|
||||
}
|
||||
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
}
|
||||
|
@ -2,34 +2,32 @@ package observable
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gopkg.in/eapache/channels.v1"
|
||||
)
|
||||
|
||||
type Subscription <-chan interface{}
|
||||
|
||||
type Subscriber struct {
|
||||
buffer *channels.InfiniteChannel
|
||||
buffer chan interface{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (s *Subscriber) Emit(item interface{}) {
|
||||
s.buffer.In() <- item
|
||||
s.buffer <- item
|
||||
}
|
||||
|
||||
func (s *Subscriber) Out() Subscription {
|
||||
return s.buffer.Out()
|
||||
return s.buffer
|
||||
}
|
||||
|
||||
func (s *Subscriber) Close() {
|
||||
s.once.Do(func() {
|
||||
s.buffer.Close()
|
||||
close(s.buffer)
|
||||
})
|
||||
}
|
||||
|
||||
func newSubscriber() *Subscriber {
|
||||
sub := &Subscriber{
|
||||
buffer: channels.NewInfiniteChannel(),
|
||||
buffer: make(chan interface{}, 200),
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
@ -55,11 +55,13 @@ func (alloc *Allocator) Put(buf []byte) error {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
||||
return errors.New("allocator Put() incorrect buffer size")
|
||||
}
|
||||
|
||||
//lint:ignore SA6002 ignore temporarily
|
||||
alloc.buffers[bits].Put(buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// msb return the pos of most significiant bit
|
||||
// msb return the pos of most significant bit
|
||||
func msb(size int) uint16 {
|
||||
return uint16(bits.Len32(uint32(size)) - 1)
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ func TestAllocGet(t *testing.T) {
|
||||
func TestAllocPut(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []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, 65536, 65536)), "put elem:65536 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
}
|
||||
|
||||
func TestAllocPutThenGet(t *testing.T) {
|
||||
|
@ -24,6 +24,8 @@ type Result struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Do single.Do likes sync.singleFlight
|
||||
//lint:ignore ST1008 it likes sync.singleFlight
|
||||
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
|
||||
s.mux.Lock()
|
||||
now := time.Now()
|
||||
|
@ -2,17 +2,17 @@ package singledo
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
single := NewSingle(time.Millisecond * 30)
|
||||
foo := 0
|
||||
var shardCount int32 = 0
|
||||
var shardCount = atomic.NewInt32(0)
|
||||
call := func() (interface{}, error) {
|
||||
foo++
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
@ -26,7 +26,7 @@ func TestBasic(t *testing.T) {
|
||||
go func() {
|
||||
_, _, shard := single.Do(call)
|
||||
if shard {
|
||||
atomic.AddInt32(&shardCount, 1)
|
||||
shardCount.Inc()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
@ -34,7 +34,7 @@ func TestBasic(t *testing.T) {
|
||||
|
||||
wg.Wait()
|
||||
assert.Equal(t, 1, foo)
|
||||
assert.Equal(t, int32(4), shardCount)
|
||||
assert.Equal(t, int32(4), shardCount.Load())
|
||||
}
|
||||
|
||||
func TestTimer(t *testing.T) {
|
||||
|
104
component/dialer/bind.go
Normal file
104
component/dialer/bind.go
Normal file
@ -0,0 +1,104 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
errPlatformNotSupport = errors.New("unsupport platform")
|
||||
)
|
||||
|
||||
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
||||
ipv4 := ip.To4() != nil
|
||||
|
||||
for _, elm := range addrs {
|
||||
addr, ok := elm.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addrV4 := addr.IP.To4() != nil
|
||||
|
||||
if addrV4 && ipv4 {
|
||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||
} else if !addrV4 && !ipv4 {
|
||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrAddrNotFound
|
||||
}
|
||||
|
||||
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
||||
ipv4 := ip.To4() != nil
|
||||
|
||||
for _, elm := range addrs {
|
||||
addr, ok := elm.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addrV4 := addr.IP.To4() != nil
|
||||
|
||||
if addrV4 && ipv4 {
|
||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||
} else if !addrV4 && !ipv4 {
|
||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrAddrNotFound
|
||||
}
|
||||
|
||||
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
||||
dialer.LocalAddr = addr
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
case "udp", "udp4", "udp6":
|
||||
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
||||
dialer.LocalAddr = addr
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fallbackBindToListenConfig(name string) (string, error) {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, elm := range addrs {
|
||||
addr, ok := elm.(*net.IPNet)
|
||||
if !ok || addr.IP.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return net.JoinHostPort(addr.IP.String(), "0"), nil
|
||||
}
|
||||
|
||||
return "", ErrAddrNotFound
|
||||
}
|
51
component/dialer/bind_darwin.go
Normal file
51
component/dialer/bind_darwin.go
Normal file
@ -0,0 +1,51 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||
|
||||
func bindControl(ifaceIdx int) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
ipStr, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil && !ip.IsGlobalUnicast() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx)
|
||||
case "tcp6", "udp6":
|
||||
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Control = bindControl(iface.Index)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lc.Control = bindControl(iface.Index)
|
||||
|
||||
return nil
|
||||
}
|
36
component/dialer/bind_linux.go
Normal file
36
component/dialer/bind_linux.go
Normal file
@ -0,0 +1,36 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||
|
||||
func bindControl(ifaceName string) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
ipStr, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil && !ip.IsGlobalUnicast() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
syscall.BindToDevice(int(fd), ifaceName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||
dialer.Control = bindControl(ifaceName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||
lc.Control = bindControl(ifaceName)
|
||||
|
||||
return nil
|
||||
}
|
13
component/dialer/bind_others.go
Normal file
13
component/dialer/bind_others.go
Normal file
@ -0,0 +1,13 @@
|
||||
// +build !linux,!darwin
|
||||
|
||||
package dialer
|
||||
|
||||
import "net"
|
||||
|
||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||
return errPlatformNotSupport
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||
return errPlatformNotSupport
|
||||
}
|
@ -19,17 +19,6 @@ func Dialer() (*net.Dialer, error) {
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func ListenConfig() (*net.ListenConfig, error) {
|
||||
cfg := &net.ListenConfig{}
|
||||
if ListenConfigHook != nil {
|
||||
if err := ListenConfigHook(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func Dial(network, address string) (net.Conn, error) {
|
||||
return DialContext(context.Background(), network, address)
|
||||
}
|
||||
@ -73,19 +62,16 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||
lc, err := ListenConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ListenPacketHook != nil && address == "" {
|
||||
ip, err := ListenPacketHook()
|
||||
cfg := &net.ListenConfig{}
|
||||
if ListenPacketHook != nil {
|
||||
var err error
|
||||
address, err = ListenPacketHook(cfg, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = net.JoinHostPort(ip.String(), "0")
|
||||
}
|
||||
return lc.ListenPacket(context.Background(), network, address)
|
||||
|
||||
return cfg.ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
@ -147,28 +133,27 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
|
||||
go startRacer(ctx, network+"4", host, false)
|
||||
go startRacer(ctx, network+"6", host, true)
|
||||
|
||||
for {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
for res := range results {
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
if !res.ipv6 {
|
||||
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 {
|
||||
fallback = res
|
||||
}
|
||||
|
||||
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, primary.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("never touched")
|
||||
}
|
||||
|
@ -3,20 +3,15 @@ package dialer
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
)
|
||||
|
||||
type DialerHookFunc = func(dialer *net.Dialer) error
|
||||
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
|
||||
type ListenConfigHookFunc = func(*net.ListenConfig) error
|
||||
type ListenPacketHookFunc = func() (net.IP, error)
|
||||
type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error)
|
||||
|
||||
var (
|
||||
DialerHook DialerHookFunc
|
||||
DialHook DialHookFunc
|
||||
ListenConfigHook ListenConfigHookFunc
|
||||
ListenPacketHook ListenPacketHookFunc
|
||||
)
|
||||
|
||||
@ -25,124 +20,24 @@ var (
|
||||
ErrNetworkNotSupport = errors.New("network not support")
|
||||
)
|
||||
|
||||
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
||||
ipv4 := ip.To4() != nil
|
||||
|
||||
for _, elm := range addrs {
|
||||
addr, ok := elm.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addrV4 := addr.IP.To4() != nil
|
||||
|
||||
if addrV4 && ipv4 {
|
||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||
} else if !addrV4 && !ipv4 {
|
||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrAddrNotFound
|
||||
}
|
||||
|
||||
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
||||
ipv4 := ip.To4() != nil
|
||||
|
||||
for _, elm := range addrs {
|
||||
addr, ok := elm.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addrV4 := addr.IP.To4() != nil
|
||||
|
||||
if addrV4 && ipv4 {
|
||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||
} else if !addrV4 && !ipv4 {
|
||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrAddrNotFound
|
||||
}
|
||||
|
||||
func ListenPacketWithInterface(name string) ListenPacketHookFunc {
|
||||
single := singledo.NewSingle(5 * time.Second)
|
||||
|
||||
return func() (net.IP, error) {
|
||||
elm, err, _ := single.Do(func() (interface{}, error) {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return func(lc *net.ListenConfig, address string) (string, error) {
|
||||
err := bindIfaceToListenConfig(lc, name)
|
||||
if err == errPlatformNotSupport {
|
||||
address, err = fallbackBindToListenConfig(name)
|
||||
}
|
||||
|
||||
addrs := elm.([]net.Addr)
|
||||
|
||||
for _, elm := range addrs {
|
||||
addr, ok := elm.(*net.IPNet)
|
||||
if !ok || addr.IP.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return addr.IP, nil
|
||||
}
|
||||
|
||||
return nil, ErrAddrNotFound
|
||||
return address, err
|
||||
}
|
||||
}
|
||||
|
||||
func DialerWithInterface(name string) DialHookFunc {
|
||||
single := singledo.NewSingle(5 * time.Second)
|
||||
|
||||
return func(dialer *net.Dialer, network string, ip net.IP) error {
|
||||
elm, err, _ := single.Do(func() (interface{}, error) {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
err := bindIfaceToDialer(dialer, name)
|
||||
if err == errPlatformNotSupport {
|
||||
err = fallbackBindToDialer(dialer, network, ip, name)
|
||||
}
|
||||
|
||||
addrs := elm.([]net.Addr)
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
||||
dialer.LocalAddr = addr
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
case "udp", "udp4", "udp6":
|
||||
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
||||
dialer.LocalAddr = addr
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ type Pool struct {
|
||||
offset uint32
|
||||
mux sync.Mutex
|
||||
host *trie.DomainTrie
|
||||
ipnet *net.IPNet
|
||||
cache *cache.LruCache
|
||||
}
|
||||
|
||||
@ -89,6 +90,16 @@ func (p *Pool) Gateway() net.IP {
|
||||
return uintToIP(p.gateway)
|
||||
}
|
||||
|
||||
// IPNet return raw ipnet
|
||||
func (p *Pool) IPNet() *net.IPNet {
|
||||
return p.ipnet
|
||||
}
|
||||
|
||||
// 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 {
|
||||
current := p.offset
|
||||
for {
|
||||
@ -116,7 +127,7 @@ func ipToUint(ip net.IP) uint32 {
|
||||
}
|
||||
|
||||
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
|
||||
@ -136,6 +147,7 @@ func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
|
||||
max: max,
|
||||
gateway: min - 1,
|
||||
host: host,
|
||||
ipnet: ipnet,
|
||||
cache: cache.NewLRUCache(cache.WithSize(size * 2)),
|
||||
}, nil
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ func (t *Table) Get(key string) C.PacketConn {
|
||||
return item.(C.PacketConn)
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {
|
||||
item, loaded := t.mapping.LoadOrStore(key, &sync.WaitGroup{})
|
||||
return item.(*sync.WaitGroup), loaded
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
||||
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||
return item.(*sync.Cond), loaded
|
||||
}
|
||||
|
||||
func (t *Table) Delete(key string) {
|
||||
|
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))
|
||||
}
|
55
component/resolver/enhancer.go
Normal file
55
component/resolver/enhancer.go
Normal file
@ -0,0 +1,55 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
var DefaultHostMapper Enhancer
|
||||
|
||||
type Enhancer interface {
|
||||
FakeIPEnabled() bool
|
||||
MappingEnabled() bool
|
||||
IsFakeIP(net.IP) bool
|
||||
IsExistFakeIP(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 IsExistFakeIP(ip net.IP) bool {
|
||||
if mapper := DefaultHostMapper; mapper != nil {
|
||||
return mapper.IsExistFakeIP(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
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type snellCipher struct {
|
||||
psk []byte
|
||||
keySize int
|
||||
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) 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) {
|
||||
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"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
const (
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
Version1 = 1
|
||||
Version2 = 2
|
||||
DefaultSnellVersion = Version1
|
||||
)
|
||||
|
||||
const (
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
CommandConnectV2 byte = 5
|
||||
|
||||
CommandTunnel byte = 0
|
||||
CommandPong byte = 1
|
||||
CommandError byte = 2
|
||||
|
||||
Version byte = 1
|
||||
@ -25,6 +32,7 @@ const (
|
||||
|
||||
var (
|
||||
bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||
endSignal = []byte{}
|
||||
)
|
||||
|
||||
type Snell struct {
|
||||
@ -46,7 +54,7 @@ func (s *Snell) Read(b []byte) (int, error) {
|
||||
if s.buffer[0] == CommandTunnel {
|
||||
return s.Conn.Read(b)
|
||||
} else if s.buffer[0] != CommandError {
|
||||
return 0, errors.New("Command not support")
|
||||
return 0, errors.New("command not support")
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
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.Reset()
|
||||
defer bufferPool.Put(buf)
|
||||
buf.WriteByte(Version)
|
||||
buf.WriteByte(CommandConnect)
|
||||
if version == Version2 {
|
||||
buf.WriteByte(CommandConnectV2)
|
||||
} else {
|
||||
buf.WriteByte(CommandConnect)
|
||||
}
|
||||
|
||||
// clientID length & id
|
||||
buf.WriteByte(0)
|
||||
@ -92,7 +104,24 @@ func WriteHeader(conn net.Conn, host string, port uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StreamConn(conn net.Conn, psk []byte) net.Conn {
|
||||
cipher := &snellCipher{psk, chacha20poly1305.New}
|
||||
// HalfClose works only on version2
|
||||
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)}
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type tlsAuthData struct {
|
||||
@ -63,7 +63,7 @@ func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) {
|
||||
var h [5]byte
|
||||
t.recvBuffer.Read(h[:])
|
||||
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
|
||||
}
|
||||
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))
|
||||
buffer.Write(d)
|
||||
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)
|
||||
a.lastClientHash = a.hmac(key, outData[:outLength])
|
||||
copy(outData[outLength:], a.lastClientHash[:2])
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
|
@ -47,7 +47,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
|
||||
|
||||
size := int(binary.BigEndian.Uint16(cr.sizeBuf))
|
||||
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 {
|
||||
|
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
|
||||
SkipCertVerify bool
|
||||
SessionCache tls.ClientSessionCache
|
||||
NextProtos []string
|
||||
}
|
||||
|
||||
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,
|
||||
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||
ClientSessionCache: cfg.SessionCache,
|
||||
NextProtos: cfg.NextProtos,
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
@ -1,12 +1,10 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
@ -37,11 +35,6 @@ var CipherMapping = map[string]byte{
|
||||
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
||||
}
|
||||
|
||||
var (
|
||||
clientSessionCache tls.ClientSessionCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Command types
|
||||
const (
|
||||
CommandTCP byte = 1
|
||||
@ -106,7 +99,7 @@ func NewClient(config Config) (*Client, error) {
|
||||
security = SecurityAES128GCM
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
||||
return nil, fmt.Errorf("unknown security type: %s", config.Security)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
|
@ -73,7 +73,7 @@ func (wsc *websocketConn) Close() error {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -159,7 +159,7 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||
if resp != nil {
|
||||
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{
|
||||
|
@ -38,6 +38,7 @@ type Inbound struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
@ -69,6 +70,7 @@ type DNS struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
@ -103,12 +105,14 @@ type RawDNS struct {
|
||||
type RawFallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
TProxyPort int `yaml:"tproxy-port"`
|
||||
MixedPort int `yaml:"mixed-port"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
@ -232,6 +236,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
Port: cfg.Port,
|
||||
SocksPort: cfg.SocksPort,
|
||||
RedirPort: cfg.RedirPort,
|
||||
TProxyPort: cfg.TProxyPort,
|
||||
MixedPort: cfg.MixedPort,
|
||||
AllowLan: cfg.AllowLan,
|
||||
BindAddress: cfg.BindAddress,
|
||||
@ -264,11 +269,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for idx, mapping := range proxiesConfig {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
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 {
|
||||
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
|
||||
proxyList = append(proxyList, proxy.Name())
|
||||
@ -278,7 +283,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for idx, mapping := range groupsConfig {
|
||||
groupName, existName := mapping["name"].(string)
|
||||
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)
|
||||
}
|
||||
@ -313,12 +318,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for idx, mapping := range groupsConfig {
|
||||
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
||||
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()
|
||||
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)
|
||||
@ -340,11 +345,16 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for _, v := range proxyList {
|
||||
ps = append(ps, proxies[v])
|
||||
}
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||
providersMap[provider.ReservedName] = pd
|
||||
|
||||
global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd})
|
||||
global := outboundgroup.NewSelector(
|
||||
&outboundgroup.GroupCommonOption{
|
||||
Name: "GLOBAL",
|
||||
},
|
||||
[]provider.ProxyProvider{pd},
|
||||
)
|
||||
proxies["GLOBAL"] = outbound.NewProxy(global)
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
@ -373,11 +383,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
target = rule[2]
|
||||
params = rule[3:]
|
||||
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 {
|
||||
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)
|
||||
@ -389,7 +399,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)
|
||||
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)
|
||||
@ -403,7 +413,7 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
||||
|
||||
// add default hosts
|
||||
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 {
|
||||
@ -499,7 +509,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||
|
||||
func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
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{
|
||||
@ -561,6 +571,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
}
|
||||
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
|
||||
|
||||
if cfg.UseHosts {
|
||||
dnsCfg.Hosts = hosts
|
||||
|
@ -32,18 +32,18 @@ func initMMDB() error {
|
||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find MMDB, start download")
|
||||
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() {
|
||||
log.Warnln("MMDB invalid, remove and download")
|
||||
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 {
|
||||
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
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
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")
|
||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||
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.Close()
|
||||
@ -72,7 +72,7 @@ func Init(dir string) error {
|
||||
|
||||
// initial mmdb
|
||||
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
|
||||
}
|
||||
|
@ -15,15 +15,6 @@ func trimArr(arr []string) (r []string) {
|
||||
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.
|
||||
// Meanwhile, record the original index in the config file.
|
||||
// If loop is detected, return an error with location of loop.
|
||||
@ -32,7 +23,7 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
|
||||
indegree int
|
||||
// topological order
|
||||
topo int
|
||||
// the origional data in `groupsConfig`
|
||||
// the original data in `groupsConfig`
|
||||
data map[string]interface{}
|
||||
// `outdegree` and `from` are used in loop locating
|
||||
outdegree int
|
||||
@ -74,7 +65,7 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
|
||||
index := 0
|
||||
queue := make([]string, 0)
|
||||
for name, node := range graph {
|
||||
// in the begning, put nodes that have `node.indegree == 0` into queue.
|
||||
// in the beginning, put nodes that have `node.indegree == 0` into queue.
|
||||
if node.indegree == 0 {
|
||||
queue = append(queue, name)
|
||||
}
|
||||
@ -153,5 +144,5 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
|
||||
loopElements = append(loopElements, 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)
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ type UDPPacket interface {
|
||||
|
||||
// WriteBack writes the payload with source IP/Port equals addr
|
||||
// - variable source IP/Port is important to STUN
|
||||
// - if addr is not provided, WriteBack will wirte out UDP packet with SourceIP/Prot equals to origional Target,
|
||||
// - if addr is not provided, WriteBack will write out UDP packet with SourceIP/Port equals to original Target,
|
||||
// this is important when using Fake-IP.
|
||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||
|
||||
|
@ -19,6 +19,7 @@ const (
|
||||
HTTPCONNECT
|
||||
SOCKS
|
||||
REDIR
|
||||
TPROXY
|
||||
)
|
||||
|
||||
type NetWork int
|
||||
@ -46,6 +47,8 @@ func (t Type) String() string {
|
||||
return "Socks5"
|
||||
case REDIR:
|
||||
return "Redir"
|
||||
case TPROXY:
|
||||
return "TProxy"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
88
dns/enhancer.go
Normal file
88
dns/enhancer.go
Normal file
@ -0,0 +1,88 @@
|
||||
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) IsExistFakeIP(ip net.IP) bool {
|
||||
if !h.FakeIPEnabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
if pool := h.fakePool; pool != nil {
|
||||
return pool.Exist(ip)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool {
|
||||
if !h.FakeIPEnabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
if pool := h.fakePool; pool != nil {
|
||||
return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(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"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
|
||||
type fallbackFilter interface {
|
||||
type fallbackIPFilter interface {
|
||||
Match(net.IP) bool
|
||||
}
|
||||
|
||||
@ -24,3 +25,22 @@ type ipnetFilter struct {
|
||||
func (inf *ipnetFilter) Match(ip net.IP) bool {
|
||||
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 (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -11,23 +13,21 @@ import (
|
||||
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
|
||||
|
||||
func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
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]
|
||||
|
||||
if !isIPRequest(q) {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
record := hosts.Search(strings.TrimRight(q.Name, "."))
|
||||
if record == nil {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
ip := record.Data.(net.IP)
|
||||
@ -46,44 +46,74 @@ func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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]
|
||||
|
||||
if q.Qtype == D.TypeAAAA {
|
||||
msg := &D.Msg{}
|
||||
msg.Answer = []D.RR{}
|
||||
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
} else if q.Qtype != D.TypeA {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
if fakePool.LookupHost(host) {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
switch q.Qtype {
|
||||
case D.TypeAAAA, D.TypeSVCB, D.TypeHTTPS:
|
||||
return handleMsgWithEmptyAnswer(r), nil
|
||||
}
|
||||
|
||||
if q.Qtype != D.TypeA {
|
||||
return next(r)
|
||||
}
|
||||
|
||||
rr := &D.A{}
|
||||
@ -98,39 +128,29 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
// return a empty AAAA msg when ipv6 disabled
|
||||
if !resolver.ipv6 && q.Qtype == D.TypeAAAA {
|
||||
msg := &D.Msg{}
|
||||
msg.Answer = []D.RR{}
|
||||
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return handleMsgWithEmptyAnswer(r), nil
|
||||
}
|
||||
|
||||
msg, err := resolver.Exchange(r)
|
||||
if err != nil {
|
||||
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
return msg, err
|
||||
}
|
||||
msg.SetRcode(r, msg.Rcode)
|
||||
msg.Authoritative = true
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,15 +165,19 @@ func compose(middlewares []middleware, endpoint handler) handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func newHandler(resolver *Resolver) handler {
|
||||
func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
||||
middlewares := []middleware{}
|
||||
|
||||
if resolver.hosts != nil {
|
||||
middlewares = append(middlewares, withHosts(resolver.hosts))
|
||||
}
|
||||
|
||||
if resolver.FakeIPEnabled() {
|
||||
middlewares = append(middlewares, withFakeIP(resolver.pool))
|
||||
if mapper.mode == FAKEIP {
|
||||
middlewares = append(middlewares, withFakeIP(mapper.fakePool))
|
||||
}
|
||||
|
||||
if mapper.mode != NORMAL {
|
||||
middlewares = append(middlewares, withMapping(mapper.mapping))
|
||||
}
|
||||
|
||||
return compose(middlewares, withResolver(resolver))
|
||||
|
133
dns/resolver.go
133
dns/resolver.go
@ -35,16 +35,14 @@ type result struct {
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
ipv6 bool
|
||||
mapping bool
|
||||
fakeip bool
|
||||
hosts *trie.DomainTrie
|
||||
pool *fakeip.Pool
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackFilters []fallbackFilter
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
ipv6 bool
|
||||
hosts *trie.DomainTrie
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackDomainFilters []fallbackDomainFilter
|
||||
fallbackIPFilters []fallbackIPFilter
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (r *Resolver) shouldFallback(ip net.IP) bool {
|
||||
for _, filter := range r.fallbackFilters {
|
||||
func (r *Resolver) shouldIPFallback(ip net.IP) bool {
|
||||
for _, filter := range r.fallbackIPFilters {
|
||||
if filter.Match(ip) {
|
||||
return true
|
||||
}
|
||||
@ -106,7 +104,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||
go r.exchangeWithoutCache(m)
|
||||
} else {
|
||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -126,17 +124,11 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
msg := result.(*D.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)
|
||||
if isIPReq {
|
||||
return r.fallbackExchange(m)
|
||||
return r.ipExchange(m)
|
||||
}
|
||||
|
||||
return r.batchExchange(r.main, m)
|
||||
@ -152,40 +144,6 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
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) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||
for _, client := range clients {
|
||||
@ -203,7 +161,7 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
||||
|
||||
elm := fast.Wait()
|
||||
if elm == nil {
|
||||
err := errors.New("All DNS requests failed")
|
||||
err := errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if r.fallback == nil {
|
||||
|
||||
if r.fallback == nil { // directly return if no fallback servers are available
|
||||
res := <-msgCh
|
||||
msg, err = res.Msg, res.Error
|
||||
return
|
||||
}
|
||||
|
||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||
if !r.shouldFallback(ips[0]) {
|
||||
msg = res.Msg
|
||||
if !r.shouldIPFallback(ips[0]) {
|
||||
msg = res.Msg // no need to wait for fallback result
|
||||
err = res.Error
|
||||
return msg, err
|
||||
}
|
||||
@ -284,6 +272,14 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||
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 {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
@ -301,6 +297,7 @@ type NameServer struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool
|
||||
IPCIDR []*net.IPNet
|
||||
Domain []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -313,7 +310,7 @@ type Config struct {
|
||||
Hosts *trie.DomainTrie
|
||||
}
|
||||
|
||||
func New(config Config) *Resolver {
|
||||
func NewResolver(config Config) *Resolver {
|
||||
defaultResolver := &Resolver{
|
||||
main: transform(config.Default, nil),
|
||||
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||
@ -323,9 +320,6 @@ func New(config Config) *Resolver {
|
||||
ipv6: config.IPv6,
|
||||
main: transform(config.Main, defaultResolver),
|
||||
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||
mapping: config.EnhancedMode == MAPPING,
|
||||
fakeip: config.EnhancedMode == FAKEIP,
|
||||
pool: config.Pool,
|
||||
hosts: config.Hosts,
|
||||
}
|
||||
|
||||
@ -333,14 +327,19 @@ func New(config Config) *Resolver {
|
||||
r.fallback = transform(config.Fallback, defaultResolver)
|
||||
}
|
||||
|
||||
fallbackFilters := []fallbackFilter{}
|
||||
fallbackIPFilters := []fallbackIPFilter{}
|
||||
if config.FallbackFilter.GeoIP {
|
||||
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -27,16 +27,22 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
||||
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) {
|
||||
s.handler = handler
|
||||
}
|
||||
|
||||
func ReCreateServer(addr string, resolver *Resolver) error {
|
||||
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) error {
|
||||
if addr == address && resolver != nil {
|
||||
handler := newHandler(resolver)
|
||||
handler := newHandler(resolver, mapper)
|
||||
server.setHandler(handler)
|
||||
return nil
|
||||
}
|
||||
@ -68,7 +74,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
||||
}
|
||||
|
||||
address = addr
|
||||
handler := newHandler(resolver)
|
||||
handler := newHandler(resolver, mapper)
|
||||
server = &Server{handler: handler}
|
||||
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
|
||||
|
||||
|
11
dns/util.go
11
dns/util.go
@ -142,3 +142,14 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
||||
msg := &D.Msg{}
|
||||
msg.Answer = []D.RR{}
|
||||
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
return msg
|
||||
}
|
||||
|
19
go.mod
19
go.mod
@ -1,23 +1,22 @@
|
||||
module github.com/Dreamacro/clash
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.1.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
||||
gopkg.in/eapache/channels.v1 v1.1.0
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
47
go.sum
47
go.sum
@ -1,10 +1,8 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a h1:JhQFrFOkCpRB8qsN6PrzHFzjy/8iQpFFk5cbOiplh6s=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||
@ -15,52 +13,57 @@ github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
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/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.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/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/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
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-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
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-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-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-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-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -34,7 +34,7 @@ func readConfig(path string) ([]byte, error) {
|
||||
}
|
||||
|
||||
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
|
||||
@ -86,6 +86,7 @@ func GetGeneral() *config.General {
|
||||
Port: ports.Port,
|
||||
SocksPort: ports.SocksPort,
|
||||
RedirPort: ports.RedirPort,
|
||||
TProxyPort: ports.TProxyPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
@ -101,13 +102,14 @@ func GetGeneral() *config.General {
|
||||
func updateExperimental(c *config.Config) {}
|
||||
|
||||
func updateDNS(c *config.DNS) {
|
||||
if c.Enable == false {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
tunnel.SetResolver(nil)
|
||||
dns.ReCreateServer("", nil)
|
||||
resolver.DefaultHostMapper = nil
|
||||
dns.ReCreateServer("", nil, nil)
|
||||
return
|
||||
}
|
||||
r := dns.New(dns.Config{
|
||||
|
||||
cfg := dns.Config{
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
IPv6: c.IPv6,
|
||||
@ -117,12 +119,23 @@ func updateDNS(c *config.DNS) {
|
||||
FallbackFilter: dns.FallbackFilter{
|
||||
GeoIP: c.FallbackFilter.GeoIP,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
},
|
||||
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
|
||||
tunnel.SetResolver(r)
|
||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||
resolver.DefaultHostMapper = m
|
||||
|
||||
if err := dns.ReCreateServer(c.Listen, r, m); err != nil {
|
||||
log.Errorln("Start DNS server error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
@ -179,6 +192,10 @@ func updateGeneral(general *config.General, force bool) {
|
||||
log.Errorln("Start Redir server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateTProxy(general.TProxyPort); err != nil {
|
||||
log.Errorln("Start TProxy server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ type configSchema struct {
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
@ -66,6 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||
|
||||
if general.Mode != nil {
|
||||
@ -106,7 +108,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
if !filepath.IsAbs(req.Path) {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("path is not a absoluted path"))
|
||||
render.JSON(w, r, newError("path is not a absolute path"))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,6 @@ func Subscribe() observable.Subscription {
|
||||
|
||||
func UnSubscribe(sub observable.Subscription) {
|
||||
source.UnSubscribe(sub)
|
||||
return
|
||||
}
|
||||
|
||||
func Level() LogLevel {
|
||||
|
5
main.go
5
main.go
@ -10,7 +10,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/hub"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
@ -76,10 +75,10 @@ func main() {
|
||||
if testConfig {
|
||||
if _, err := executor.Parse(); err != nil {
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ func (l *HttpListener) Address() string {
|
||||
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
|
||||
if result := cache.Get(loginStr); result != nil {
|
||||
ret = result.(bool)
|
||||
return
|
||||
}
|
||||
loginData, err := base64.StdEncoding.DecodeString(loginStr)
|
||||
login := strings.Split(string(loginData), ":")
|
||||
@ -80,7 +81,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) {
|
||||
authenticator := authStore.Authenticator()
|
||||
if authenticator != nil {
|
||||
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()
|
||||
return
|
||||
} else if !canActivate(authStrings[1], authenticator, cache) {
|
||||
|
@ -17,32 +17,30 @@ var (
|
||||
allowLan = false
|
||||
bindAddress = "*"
|
||||
|
||||
socksListener *socks.SockListener
|
||||
socksUDPListener *socks.SockUDPListener
|
||||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
socksListener *socks.SockListener
|
||||
socksUDPListener *socks.SockUDPListener
|
||||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
tproxyListener *redir.TProxyListener
|
||||
tproxyUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
httpMux sync.Mutex
|
||||
redirMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
tunMux sync.Mutex
|
||||
socksMux sync.Mutex
|
||||
httpMux sync.Mutex
|
||||
redirMux sync.Mutex
|
||||
tproxyMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
)
|
||||
|
||||
type listener interface {
|
||||
Close()
|
||||
Address() string
|
||||
}
|
||||
|
||||
type Ports struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
}
|
||||
|
||||
func AllowLan() bool {
|
||||
@ -180,6 +178,46 @@ func ReCreateRedir(port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateTProxy(port int) error {
|
||||
tproxyMux.Lock()
|
||||
defer tproxyMux.Unlock()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
if tproxyListener != nil {
|
||||
if tproxyListener.Address() == addr {
|
||||
return nil
|
||||
}
|
||||
tproxyListener.Close()
|
||||
tproxyListener = nil
|
||||
}
|
||||
|
||||
if tproxyUDPListener != nil {
|
||||
if tproxyUDPListener.Address() == addr {
|
||||
return nil
|
||||
}
|
||||
tproxyUDPListener.Close()
|
||||
tproxyUDPListener = nil
|
||||
}
|
||||
|
||||
if portIsZero(addr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
tproxyListener, err = redir.NewTProxy(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tproxyUDPListener, err = redir.NewRedirUDPProxy(addr)
|
||||
if err != nil {
|
||||
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateMixed(port int) error {
|
||||
mixedMux.Lock()
|
||||
defer mixedMux.Unlock()
|
||||
@ -251,6 +289,12 @@ func GetPorts() *Ports {
|
||||
ports.RedirPort = port
|
||||
}
|
||||
|
||||
if tproxyListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(tproxyListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
ports.TProxyPort = port
|
||||
}
|
||||
|
||||
if mixedListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
|
71
proxy/redir/tproxy.go
Normal file
71
proxy/redir/tproxy.go
Normal file
@ -0,0 +1,71 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type TProxyListener struct {
|
||||
net.Listener
|
||||
address string
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewTProxy(addr string) (*TProxyListener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tl := l.(*net.TCPListener)
|
||||
rc, err := tl.SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setsockopt(rc, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl := &TProxyListener{
|
||||
Listener: l,
|
||||
address: addr,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Infoln("TProxy server listening at: %s", addr)
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if rl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go rl.handleRedir(c)
|
||||
}
|
||||
}()
|
||||
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
func (l *TProxyListener) Close() {
|
||||
l.closed = true
|
||||
l.Listener.Close()
|
||||
}
|
||||
|
||||
func (l *TProxyListener) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
func (l *TProxyListener) handleRedir(conn net.Conn) {
|
||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))
|
||||
}
|
40
proxy/redir/tproxy_linux.go
Normal file
40
proxy/redir/tproxy_linux.go
Normal file
@ -0,0 +1,40 @@
|
||||
// +build linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setsockopt(rc syscall.RawConn, addr string) error {
|
||||
isIPv6 := true
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil && ip.To4() != nil {
|
||||
isIPv6 = false
|
||||
}
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
12
proxy/redir/tproxy_other.go
Normal file
12
proxy/redir/tproxy_other.go
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setsockopt(rc syscall.RawConn, addr string) error {
|
||||
return errors.New("not supported on current platform")
|
||||
}
|
@ -26,7 +26,12 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
||||
|
||||
c := l.(*net.UDPConn)
|
||||
|
||||
err = setsockopt(c, addr)
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setsockopt(rc, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,43 +14,6 @@ const (
|
||||
IPV6_RECVORIGDSTADDR = 0x4a
|
||||
)
|
||||
|
||||
func setsockopt(c *net.UDPConn, addr string) error {
|
||||
isIPv6 := true
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil && ip.To4() != nil {
|
||||
isIPv6 = false
|
||||
}
|
||||
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
|
@ -7,10 +7,6 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func setsockopt(c *net.UDPConn, addr string) error {
|
||||
return errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
return nil, errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ func (c *packet) Data() []byte {
|
||||
return c.buf
|
||||
}
|
||||
|
||||
// WriteBack opens a new socket binding `addr` to wirte UDP packet back
|
||||
// WriteBack opens a new socket binding `addr` to write UDP packet back
|
||||
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr)
|
||||
if err != nil {
|
||||
@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.buf)
|
||||
return
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func (c *packet) Data() []byte {
|
||||
return c.payload
|
||||
}
|
||||
|
||||
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||
// WriteBack write UDP packet with source(ip, port) = `addr`
|
||||
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||
if err != nil {
|
||||
@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.bufRef)
|
||||
return
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
var (
|
||||
errPayload = errors.New("payload error")
|
||||
errParams = errors.New("params error")
|
||||
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||
ErrInvalidNetwork = errors.New("invalid network")
|
||||
|
||||
|
@ -127,8 +127,8 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
}
|
||||
// skip the first and last xinpgen(24 bytes) block
|
||||
for i := 24; i < len(buf)-24; i += itemSize {
|
||||
// skip the first xinpgen(24 bytes) block
|
||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||
// offset of xinpcb_n and xsocket_n
|
||||
inp, so := i, i+104
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@ -17,7 +18,15 @@ import (
|
||||
)
|
||||
|
||||
// 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 {
|
||||
adapter string
|
||||
@ -28,7 +37,7 @@ func (ps *Process) RuleType() C.RuleType {
|
||||
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)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
@ -45,6 +54,10 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
return strings.EqualFold(cached.(string), ps.process)
|
||||
}
|
||||
|
||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
return matchMeta(ps, metadata)
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
@ -58,6 +71,15 @@ func (p *Process) ShouldResolveIP() bool {
|
||||
}
|
||||
|
||||
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{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
@ -85,28 +107,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
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) {
|
||||
ip := metadata.SrcIP
|
||||
port, err := strconv.Atoi(metadata.SrcPort)
|
||||
@ -115,25 +115,18 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
}
|
||||
|
||||
var spath string
|
||||
var itemSize int
|
||||
var inpOffset int
|
||||
var isTCP bool
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
spath = "net.inet.tcp.pcblist"
|
||||
// struct xtcpcb
|
||||
itemSize = 744
|
||||
inpOffset = 8
|
||||
isTCP = true
|
||||
case C.UDP:
|
||||
spath = "net.inet.udp.pcblist"
|
||||
// struct xinpcb
|
||||
itemSize = 400
|
||||
inpOffset = 0
|
||||
isTCP = false
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
isIPv4 := ip.To4() != nil
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -141,27 +134,73 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
|
||||
buf := []byte(value)
|
||||
|
||||
// skip the first and last xinpgen(64 bytes) block
|
||||
for i := 64; i < len(buf)-64; i += itemSize {
|
||||
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// xinpcb.inp_vflag
|
||||
flag := buf[inp+392]
|
||||
flag := buf[inp+s.vflag]
|
||||
|
||||
var srcIP net.IP
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// 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:
|
||||
// ipv6
|
||||
srcIP = net.IP(buf[inp+272 : inp+288])
|
||||
srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4])
|
||||
default:
|
||||
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
|
||||
socket := binary.BigEndian.Uint64(buf[inp+16 : inp+24])
|
||||
pid, err := searchSocketPid(socket)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getExecPathFromPID(pid)
|
||||
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
||||
return s.searchSocketPid(socket)
|
||||
}
|
||||
return 0, errNotFound
|
||||
}
|
||||
|
||||
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 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
func newSearcher(major int) *searcher {
|
||||
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
|
||||
}
|
||||
|
||||
if bytes.Compare(buffer[:n], socket) == 0 {
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -9,14 +9,13 @@ import (
|
||||
"strings"
|
||||
"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"
|
||||
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
|
||||
host := req.Host
|
||||
|
||||
@ -28,7 +27,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
|
||||
req.Header.Set("Connection", "close")
|
||||
req.RequestURI = ""
|
||||
adapters.RemoveHopByHopHeaders(req.Header)
|
||||
inbound.RemoveHopByHopHeaders(req.Header)
|
||||
err := req.Write(outbound)
|
||||
if err != nil {
|
||||
break
|
||||
@ -39,7 +38,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
adapters.RemoveHopByHopHeaders(resp.Header)
|
||||
inbound.RemoveHopByHopHeaders(resp.Header)
|
||||
|
||||
if resp.StatusCode == http.StatusContinue {
|
||||
err = resp.Write(request)
|
||||
@ -121,14 +120,14 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
|
||||
from = fAddr
|
||||
}
|
||||
|
||||
n, err = packet.WriteBack(buf[:n], from)
|
||||
_, err = packet.WriteBack(buf[:n], from)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
||||
func handleSocket(request C.ServerAdapter, outbound net.Conn) {
|
||||
relay(request, outbound)
|
||||
}
|
||||
|
||||
|
@ -3,28 +3,33 @@ package tunnel
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
var DefaultManager *Manager
|
||||
|
||||
func init() {
|
||||
DefaultManager = &Manager{
|
||||
upload: make(chan int64),
|
||||
download: make(chan int64),
|
||||
uploadTemp: atomic.NewInt64(0),
|
||||
downloadTemp: atomic.NewInt64(0),
|
||||
uploadBlip: atomic.NewInt64(0),
|
||||
downloadBlip: atomic.NewInt64(0),
|
||||
uploadTotal: atomic.NewInt64(0),
|
||||
downloadTotal: atomic.NewInt64(0),
|
||||
}
|
||||
DefaultManager.handle()
|
||||
|
||||
go DefaultManager.handle()
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
connections sync.Map
|
||||
upload chan int64
|
||||
download chan int64
|
||||
uploadTemp int64
|
||||
downloadTemp int64
|
||||
uploadBlip int64
|
||||
downloadBlip int64
|
||||
uploadTotal int64
|
||||
downloadTotal int64
|
||||
uploadTemp *atomic.Int64
|
||||
downloadTemp *atomic.Int64
|
||||
uploadBlip *atomic.Int64
|
||||
downloadBlip *atomic.Int64
|
||||
uploadTotal *atomic.Int64
|
||||
downloadTotal *atomic.Int64
|
||||
}
|
||||
|
||||
func (m *Manager) Join(c tracker) {
|
||||
@ -35,16 +40,18 @@ func (m *Manager) Leave(c tracker) {
|
||||
m.connections.Delete(c.ID())
|
||||
}
|
||||
|
||||
func (m *Manager) Upload() chan<- int64 {
|
||||
return m.upload
|
||||
func (m *Manager) PushUploaded(size int64) {
|
||||
m.uploadTemp.Add(size)
|
||||
m.uploadTotal.Add(size)
|
||||
}
|
||||
|
||||
func (m *Manager) Download() chan<- int64 {
|
||||
return m.download
|
||||
func (m *Manager) PushDownloaded(size int64) {
|
||||
m.downloadTemp.Add(size)
|
||||
m.downloadTotal.Add(size)
|
||||
}
|
||||
|
||||
func (m *Manager) Now() (up int64, down int64) {
|
||||
return m.uploadBlip, m.downloadBlip
|
||||
return m.uploadBlip.Load(), m.downloadBlip.Load()
|
||||
}
|
||||
|
||||
func (m *Manager) Snapshot() *Snapshot {
|
||||
@ -55,37 +62,29 @@ func (m *Manager) Snapshot() *Snapshot {
|
||||
})
|
||||
|
||||
return &Snapshot{
|
||||
UploadTotal: m.uploadTotal,
|
||||
DownloadTotal: m.downloadTotal,
|
||||
UploadTotal: m.uploadTotal.Load(),
|
||||
DownloadTotal: m.downloadTotal.Load(),
|
||||
Connections: connections,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ResetStatistic() {
|
||||
m.uploadTemp = 0
|
||||
m.uploadBlip = 0
|
||||
m.uploadTotal = 0
|
||||
m.downloadTemp = 0
|
||||
m.downloadBlip = 0
|
||||
m.downloadTotal = 0
|
||||
m.uploadTemp.Store(0)
|
||||
m.uploadBlip.Store(0)
|
||||
m.uploadTotal.Store(0)
|
||||
m.downloadTemp.Store(0)
|
||||
m.downloadBlip.Store(0)
|
||||
m.downloadTotal.Store(0)
|
||||
}
|
||||
|
||||
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)
|
||||
for {
|
||||
select {
|
||||
case n := <-ch:
|
||||
*temp += n
|
||||
*total += n
|
||||
case <-ticker.C:
|
||||
*blip = *temp
|
||||
*temp = 0
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
m.uploadBlip.Store(m.uploadTemp.Load())
|
||||
m.uploadTemp.Store(0)
|
||||
m.downloadBlip.Store(m.downloadTemp.Load())
|
||||
m.downloadTemp.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,9 @@ import (
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type tracker interface {
|
||||
@ -14,14 +16,14 @@ type tracker interface {
|
||||
}
|
||||
|
||||
type trackerInfo struct {
|
||||
UUID uuid.UUID `json:"id"`
|
||||
Metadata *C.Metadata `json:"metadata"`
|
||||
UploadTotal int64 `json:"upload"`
|
||||
DownloadTotal int64 `json:"download"`
|
||||
Start time.Time `json:"start"`
|
||||
Chain C.Chain `json:"chains"`
|
||||
Rule string `json:"rule"`
|
||||
RulePayload string `json:"rulePayload"`
|
||||
UUID uuid.UUID `json:"id"`
|
||||
Metadata *C.Metadata `json:"metadata"`
|
||||
UploadTotal *atomic.Int64 `json:"upload"`
|
||||
DownloadTotal *atomic.Int64 `json:"download"`
|
||||
Start time.Time `json:"start"`
|
||||
Chain C.Chain `json:"chains"`
|
||||
Rule string `json:"rule"`
|
||||
RulePayload string `json:"rulePayload"`
|
||||
}
|
||||
|
||||
type tcpTracker struct {
|
||||
@ -37,16 +39,16 @@ func (tt *tcpTracker) ID() string {
|
||||
func (tt *tcpTracker) Read(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Read(b)
|
||||
download := int64(n)
|
||||
tt.manager.Download() <- download
|
||||
tt.DownloadTotal += download
|
||||
tt.manager.PushDownloaded(download)
|
||||
tt.DownloadTotal.Add(download)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Write(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Write(b)
|
||||
upload := int64(n)
|
||||
tt.manager.Upload() <- upload
|
||||
tt.UploadTotal += upload
|
||||
tt.manager.PushUploaded(upload)
|
||||
tt.UploadTotal.Add(upload)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@ -62,11 +64,13 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
||||
Conn: conn,
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: conn.Chains(),
|
||||
Rule: "",
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: conn.Chains(),
|
||||
Rule: "",
|
||||
UploadTotal: atomic.NewInt64(0),
|
||||
DownloadTotal: atomic.NewInt64(0),
|
||||
},
|
||||
}
|
||||
|
||||
@ -92,16 +96,16 @@ func (ut *udpTracker) ID() string {
|
||||
func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := ut.PacketConn.ReadFrom(b)
|
||||
download := int64(n)
|
||||
ut.manager.Download() <- download
|
||||
ut.DownloadTotal += download
|
||||
ut.manager.PushDownloaded(download)
|
||||
ut.DownloadTotal.Add(download)
|
||||
return n, addr, err
|
||||
}
|
||||
|
||||
func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
n, err := ut.PacketConn.WriteTo(b, addr)
|
||||
upload := int64(n)
|
||||
ut.manager.Upload() <- upload
|
||||
ut.UploadTotal += upload
|
||||
ut.manager.PushUploaded(upload)
|
||||
ut.UploadTotal.Add(upload)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@ -117,11 +121,13 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
||||
PacketConn: conn,
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: conn.Chains(),
|
||||
Rule: "",
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: conn.Chains(),
|
||||
Rule: "",
|
||||
UploadTotal: atomic.NewInt64(0),
|
||||
DownloadTotal: atomic.NewInt64(0),
|
||||
},
|
||||
}
|
||||
|
||||
|
144
tunnel/tunnel.go
144
tunnel/tunnel.go
@ -12,21 +12,17 @@ import (
|
||||
"github.com/Dreamacro/clash/component/nat"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
channels "gopkg.in/eapache/channels.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
tcpQueue = channels.NewInfiniteChannel()
|
||||
udpQueue = channels.NewInfiniteChannel()
|
||||
natTable = nat.New()
|
||||
rules []C.Rule
|
||||
proxies = make(map[string]C.Proxy)
|
||||
providers map[string]provider.ProxyProvider
|
||||
configMux sync.RWMutex
|
||||
enhancedMode *dns.Resolver
|
||||
tcpQueue = make(chan C.ServerAdapter, 200)
|
||||
udpQueue = make(chan *inbound.PacketAdapter, 200)
|
||||
natTable = nat.New()
|
||||
rules []C.Rule
|
||||
proxies = make(map[string]C.Proxy)
|
||||
providers map[string]provider.ProxyProvider
|
||||
configMux sync.RWMutex
|
||||
|
||||
// Outbound Rule
|
||||
mode = Rule
|
||||
@ -41,12 +37,15 @@ func init() {
|
||||
|
||||
// Add request to queue
|
||||
func Add(req C.ServerAdapter) {
|
||||
tcpQueue.In() <- req
|
||||
tcpQueue <- req
|
||||
}
|
||||
|
||||
// AddPacket add udp Packet to queue
|
||||
func AddPacket(packet *inbound.PacketAdapter) {
|
||||
udpQueue.In() <- packet
|
||||
select {
|
||||
case udpQueue <- packet:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Rules return all rules
|
||||
@ -89,16 +88,10 @@ func SetMode(m TunnelMode) {
|
||||
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
|
||||
func processUDP() {
|
||||
queue := udpQueue.Out()
|
||||
for elm := range queue {
|
||||
conn := elm.(*inbound.PacketAdapter)
|
||||
queue := udpQueue
|
||||
for conn := range queue {
|
||||
handleUDPConn(conn)
|
||||
}
|
||||
}
|
||||
@ -112,15 +105,14 @@ func process() {
|
||||
go processUDP()
|
||||
}
|
||||
|
||||
queue := tcpQueue.Out()
|
||||
for elm := range queue {
|
||||
conn := elm.(C.ServerAdapter)
|
||||
queue := tcpQueue
|
||||
for conn := range queue {
|
||||
go handleTCPConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -131,17 +123,17 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
||||
|
||||
// preprocess enhanced-mode metadata
|
||||
if needLookupIP(metadata) {
|
||||
host, exist := enhancedMode.IPToHost(metadata.DstIP)
|
||||
host, exist := resolver.FindHostByIP(metadata.DstIP)
|
||||
if exist {
|
||||
metadata.Host = host
|
||||
metadata.AddrType = C.AtypDomainName
|
||||
if enhancedMode.FakeIPEnabled() {
|
||||
if resolver.FakeIPEnabled() {
|
||||
metadata.DstIP = nil
|
||||
} else if node := resolver.DefaultHosts.Search(host); node != nil {
|
||||
// redir-host should lookup the hosts
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -175,9 +167,9 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
return
|
||||
}
|
||||
|
||||
// make a fAddr if requset ip is fakeip
|
||||
// make a fAddr if request ip is fakeip
|
||||
var fAddr net.Addr
|
||||
if enhancedMode != nil && enhancedMode.IsFakeIP(metadata.DstIP) {
|
||||
if resolver.IsExistFakeIP(metadata.DstIP) {
|
||||
fAddr = metadata.UDPAddr()
|
||||
}
|
||||
|
||||
@ -187,57 +179,65 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
}
|
||||
|
||||
key := packet.LocalAddr().String()
|
||||
pc := natTable.Get(key)
|
||||
if pc != nil {
|
||||
handleUDPToRemote(packet, pc, metadata)
|
||||
|
||||
handle := func() bool {
|
||||
pc := natTable.Get(key)
|
||||
if pc != nil {
|
||||
handleUDPToRemote(packet, pc, metadata)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if handle() {
|
||||
return
|
||||
}
|
||||
|
||||
lockKey := key + "-lock"
|
||||
wg, loaded := natTable.GetOrCreateLock(lockKey)
|
||||
cond, loaded := natTable.GetOrCreateLock(lockKey)
|
||||
|
||||
go func() {
|
||||
if !loaded {
|
||||
wg.Add(1)
|
||||
proxy, rule, err := resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
|
||||
natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
if loaded {
|
||||
cond.L.Lock()
|
||||
cond.Wait()
|
||||
handle()
|
||||
cond.L.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
rawPc, err := proxy.DialUDP(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error())
|
||||
natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
pc = newUDPTracker(rawPc, DefaultManager, metadata, rule)
|
||||
|
||||
switch true {
|
||||
case rule != nil:
|
||||
log.Infoln("[UDP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String())
|
||||
case mode == Global:
|
||||
log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String())
|
||||
case mode == Direct:
|
||||
log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
default:
|
||||
log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
}
|
||||
|
||||
natTable.Set(key, pc)
|
||||
defer func() {
|
||||
natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr)
|
||||
cond.Broadcast()
|
||||
}()
|
||||
|
||||
proxy, rule, err := resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
pc := natTable.Get(key)
|
||||
if pc != nil {
|
||||
handleUDPToRemote(packet, pc, metadata)
|
||||
rawPc, err := proxy.DialUDP(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
|
||||
return
|
||||
}
|
||||
pc := newUDPTracker(rawPc, DefaultManager, metadata, rule)
|
||||
|
||||
switch true {
|
||||
case rule != nil:
|
||||
log.Infoln("[UDP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String())
|
||||
case mode == Global:
|
||||
log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String())
|
||||
case mode == Direct:
|
||||
log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
default:
|
||||
log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
}
|
||||
|
||||
go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr)
|
||||
|
||||
natTable.Set(key, pc)
|
||||
handle()
|
||||
}()
|
||||
}
|
||||
|
||||
@ -257,13 +257,13 @@ func handleTCPConn(localConn C.ServerAdapter) {
|
||||
|
||||
proxy, rule, err := resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("Parse metadata failed: %v", err)
|
||||
log.Warnln("[Metadata] parse failed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
remoteConn, err := proxy.Dial(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("dial %s error: %s", proxy.Name(), err.Error())
|
||||
log.Warnln("dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
|
||||
return
|
||||
}
|
||||
remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule)
|
||||
|
Reference in New Issue
Block a user