Compare commits

..

30 Commits

Author SHA1 Message Date
Dreamacro
3600077f3b Chore: update dependencies 2020-12-27 18:59:59 +08:00
Dreamacro
de7656a787 Chore: update premium README 2020-12-27 00:14:24 +08:00
Dreamacro
5dfe7f8561 Fix: handle keep alive on http connect proxy 2020-12-24 14:55:11 +08:00
Dreamacro
ed27898a33 Fix: snell should support the config without obfs 2020-12-24 13:47:56 +08:00
icpz
532396d25c Fix: PROCESS-NAME rule for UDP sessions on Windows (#1140) 2020-12-22 15:13:44 +08:00
Dreamacro
4b1b494164 Chore: move find process name to a single part 2020-12-17 22:17:27 +08:00
Kr328
0d33dc3eb9 Chore: health checks return immediately if completed (#1097) 2020-11-24 22:52:23 +08:00
Dreamacro
994cbff215 Fix: should not log rule when rule = nil 2020-11-22 23:38:12 +08:00
Dreamacro
bea2ee8bf2 Chore: log rule msg on dial error 2020-11-22 19:12:36 +08:00
Dreamacro
1e5593f1a9 Chore: update dependencies 2020-11-20 20:36:20 +08:00
Dreamacro
34febc4579 Chore: more detailed error when dial failed 2020-11-20 00:27:37 +08:00
Dreamacro
97581148b5 Fix: static check 2020-11-19 00:56:36 +08:00
Dreamacro
0402878daa Feature: add lazy for proxy group and provider 2020-11-19 00:53:22 +08:00
Dreamacro
4735f61fd1 Feature: add disable-udp option for all proxy group 2020-11-13 21:48:52 +08:00
Dreamacro
16ae107e70 Chore: push image to github docker registry 2020-11-10 15:19:12 +08:00
maskedeken
83efe2ae57 Feature: add TCP TPROXY support (#1049) 2020-11-09 10:46:10 +08:00
Jason Lyu
87e4d94290 Fix: tunnel manager & tracker race condition (#1048) 2020-10-29 17:51:14 +08:00
Dreamacro
b98e9ea202 Improve: #1038 and #1041 2020-10-29 00:32:31 +08:00
uchuhimo
9a62b1081d Feature: support round-robin strategy for load-balance group (#1044) 2020-10-28 22:35:02 +08:00
Jason Lyu
2cd1b890ce Fix: tunnel UDP race condition (#1043) 2020-10-28 21:26:50 +08:00
Dreamacro
ba060bd0ee Fix: should not bind interface on local address 2020-10-25 20:31:01 +08:00
Dreamacro
b1795b1e3d Fix: stale typo 2020-10-25 11:53:03 +08:00
Dreamacro
76c9820065 Fix: undefined variable 2020-10-23 17:49:34 +08:00
Dreamacro
2db4ce57ef Chore: make stale time into 60 days 2020-10-23 00:30:17 +08:00
Dreamacro
50b3d497f6 Feature: use native syscall to bind interface on Linux and macOS 2020-10-22 22:32:03 +08:00
Dreamacro
2321e9139d Chore: deprecated eapache/channels 2020-10-20 17:44:39 +08:00
Dreamacro
baabf21340 Chore: update github workflow 2020-10-17 13:46:05 +08:00
Dreamacro
d3bb4c65a8 Fix: missing fake-ip record should return error 2020-10-17 12:52:43 +08:00
kongminhao
8c3e2a7559 Chore: fix typo (#1017) 2020-10-14 19:56:02 +08:00
Dreamacro
bc52f8e4fd Chore: return empty record in SVCB/HTTPSSVC on fake-ip mode 2020-10-13 00:15:49 +08:00
65 changed files with 1181 additions and 865 deletions

View File

@@ -17,36 +17,60 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up docker buildx - name: Set up docker buildx
id: buildx id: buildx
uses: crazy-max/ghaction-docker-buildx@v3 uses: docker/setup-buildx-action@v1
with: with:
buildx-version: latest version: latest
qemu-version: latest
- name: Docker login - name: Login to DockerHub
env: uses: docker/login-action@v1
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} with:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} username: ${{ secrets.DOCKER_USERNAME }}
run: | password: ${{ secrets.DOCKER_PASSWORD }}
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
- 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' if: github.ref == 'refs/heads/dev'
run: | uses: docker/build-push-action@v2
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev . 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/') if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v3 uses: actions/github-script@v3
id: version id: tags
with: with:
script: | 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 result-encoding: string
- name: Docker buildx image and push on release - name: Build release and push
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
run: | uses: docker/build-push-action@v2
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} . with:
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest . context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: ${{steps.tags.outputs.result}}

View File

@@ -14,6 +14,6 @@ jobs:
- uses: actions/stale@v3 - uses: actions/stale@v3
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 120 days-before-stale: 60
days-before-close: 5 days-before-close: 5

View File

@@ -10,6 +10,7 @@ RUN go mod download && \
mv ./bin/clash-docker /clash mv ./bin/clash-docker /clash
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates
COPY --from=builder /Country.mmdb /root/.config/clash/ COPY --from=builder /Country.mmdb /root/.config/clash/

View File

@@ -28,9 +28,18 @@
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller - Comprehensive HTTP RESTful API controller
## Premium Features
- TUN mode on macOS, Linux and Windows. [Doc](https://github.com/Dreamacro/clash/wiki/premium-core-features#tun-device)
- Match your tunnel by [Script](https://github.com/Dreamacro/clash/wiki/premium-core-features#script)
- [Rule Provider](https://github.com/Dreamacro/clash/wiki/premium-core-features#rule-providers)
## Getting Started ## Getting Started
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Premium Release
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
## Credits ## Credits
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)

View File

@@ -6,11 +6,12 @@ import (
"errors" "errors"
"net" "net"
"net/http" "net/http"
"sync/atomic"
"time" "time"
"github.com/Dreamacro/clash/common/queue" "github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
) )
type Base struct { type Base struct {
@@ -95,11 +96,11 @@ func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue history *queue.Queue
alive uint32 alive *atomic.Bool
} }
func (p *Proxy) Alive() 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) { func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
@@ -111,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) { func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata) conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil { if err != nil {
atomic.StoreUint32(&p.alive, 0) p.alive.Store(false)
} }
return conn, err return conn, err
} }
@@ -128,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. // LastDelay return last history record. if proxy is not alive, return the max value of uint16.
func (p *Proxy) LastDelay() (delay uint16) { func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff var max uint16 = 0xffff
if atomic.LoadUint32(&p.alive) == 0 { if !p.alive.Load() {
return max return max
} }
@@ -159,11 +160,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() { defer func() {
if err == nil { p.alive.Store(err == nil)
atomic.StoreUint32(&p.alive, 1)
} else {
atomic.StoreUint32(&p.alive, 0)
}
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
if err == nil { if err == nil {
record.Delay = t record.Delay = t
@@ -219,5 +216,5 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
} }
func NewProxy(adapter C.ProxyAdapter) *Proxy { func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), 1} return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
} }

View File

@@ -40,7 +40,7 @@ type ShadowSocksOption struct {
} }
type simpleObfsOption struct { type simpleObfsOption struct {
Mode string `obfs:"mode"` Mode string `obfs:"mode,omitempty"`
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
} }

View File

@@ -87,7 +87,10 @@ func NewSnell(option SnellOption) (*Snell, error) {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
} }
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { switch obfsOption.Mode {
case "tls", "http", "":
break
default:
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
} }

View File

@@ -11,10 +11,14 @@ const (
defaultGetProxiesDuration = time.Second * 5 defaultGetProxiesDuration = time.Second * 5
) )
func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy { func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for _, provider := range providers { for _, provider := range providers {
proxies = append(proxies, provider.Proxies()...) if touch {
proxies = append(proxies, provider.ProxiesWithTouch()...)
} else {
proxies = append(proxies, provider.Proxies()...)
}
} }
return proxies return proxies
} }

View File

@@ -12,17 +12,18 @@ import (
type Fallback struct { type Fallback struct {
*outbound.Base *outbound.Base
single *singledo.Single disableUDP bool
providers []provider.ProxyProvider single *singledo.Single
providers []provider.ProxyProvider
} }
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
proxy := f.findAliveProxy() proxy := f.findAliveProxy(false)
return proxy.Name() return proxy.Name()
} }
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 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) c, err := proxy.DialContext(ctx, metadata)
if err == nil { if err == nil {
c.AppendToChains(f) 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) { func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy() proxy := f.findAliveProxy(true)
pc, err := proxy.DialUDP(metadata) pc, err := proxy.DialUDP(metadata)
if err == nil { if err == nil {
pc.AppendToChains(f) pc.AppendToChains(f)
@@ -40,13 +41,17 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
} }
func (f *Fallback) SupportUDP() bool { func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy() if f.disableUDP {
return false
}
proxy := f.findAliveProxy(false)
return proxy.SupportUDP() return proxy.SupportUDP()
} }
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range f.proxies() { for _, proxy := range f.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ 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 { func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
proxy := f.findAliveProxy() proxy := f.findAliveProxy(true)
return proxy return proxy
} }
func (f *Fallback) proxies() []C.Proxy { func (f *Fallback) proxies(touch bool) []C.Proxy {
elm, _, _ := f.single.Do(func() (interface{}, error) { elm, _, _ := f.single.Do(func() (interface{}, error) {
return getProvidersProxies(f.providers), nil return getProvidersProxies(f.providers, touch), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
} }
func (f *Fallback) findAliveProxy() C.Proxy { func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.proxies() proxies := f.proxies(touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if proxy.Alive() { if proxy.Alive() {
return proxy 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{ return &Fallback{
Base: outbound.NewBase(name, "", C.Fallback, false), Base: outbound.NewBase(options.Name, "", C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
disableUDP: options.DisableUDP,
} }
} }

View File

@@ -3,6 +3,8 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt"
"net" "net"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
@@ -14,11 +16,25 @@ import (
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
type LoadBalance struct { type LoadBalance struct {
*outbound.Base *outbound.Base
single *singledo.Single disableUDP bool
maxRetry int single *singledo.Single
providers []provider.ProxyProvider 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 { 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 { 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 { func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) proxies := lb.proxies(true)
proxies := lb.proxies() return lb.strategyFn(proxies, metadata)
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]
} }
func (lb *LoadBalance) proxies() []C.Proxy { func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
elm, _, _ := lb.single.Do(func() (interface{}, error) { elm, _, _ := lb.single.Do(func() (interface{}, error) {
return getProvidersProxies(lb.providers), nil return getProvidersProxies(lb.providers, touch), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@@ -106,7 +145,7 @@ func (lb *LoadBalance) proxies() []C.Proxy {
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range lb.proxies() { for _, proxy := range lb.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
@@ -115,11 +154,21 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
}) })
} }
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
return &LoadBalance{ var strategyFn strategyFn
Base: outbound.NewBase(name, "", C.LoadBalance, false), switch strategy {
single: singledo.NewSingle(defaultGetProxiesDuration), case "consistent-hashing":
maxRetry: 3, strategyFn = strategyConsistentHashing()
providers: providers, 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
} }

View File

@@ -18,18 +18,22 @@ var (
) )
type GroupCommonOption struct { type GroupCommonOption struct {
Name string `group:"name"` Name string `group:"name"`
Type string `group:"type"` Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"` Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"` Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"` URL string `group:"url,omitempty"`
Interval int `group:"interval,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) { 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}) decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{} groupOption := &GroupCommonOption{
Lazy: true,
}
if err := decoder.Decode(config, groupOption); err != nil { if err := decoder.Decode(config, groupOption); err != nil {
return nil, errFormat return nil, errFormat
} }
@@ -54,7 +58,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
// if Use not empty, drop health check options // if Use not empty, drop health check options
if len(groupOption.Use) != 0 { if len(groupOption.Use) != 0 {
hc := provider.NewHealthCheck(ps, "", 0) hc := provider.NewHealthCheck(ps, "", 0, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -68,7 +72,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
// select don't need health check // select don't need health check
if groupOption.Type == "select" || groupOption.Type == "relay" { if groupOption.Type == "select" || groupOption.Type == "relay" {
hc := provider.NewHealthCheck(ps, "", 0) hc := provider.NewHealthCheck(ps, "", 0, true)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -81,7 +85,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
return nil, errMissHealthCheck 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) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -105,15 +109,16 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
switch groupOption.Type { switch groupOption.Type {
case "url-test": case "url-test":
opts := parseURLTestOption(config) opts := parseURLTestOption(config)
group = NewURLTest(groupName, providers, opts...) group = NewURLTest(groupOption, providers, opts...)
case "select": case "select":
group = NewSelector(groupName, providers) group = NewSelector(groupOption, providers)
case "fallback": case "fallback":
group = NewFallback(groupName, providers) group = NewFallback(groupOption, providers)
case "load-balance": case "load-balance":
group = NewLoadBalance(groupName, providers) strategy := parseStrategy(config)
return NewLoadBalance(groupOption, providers, strategy)
case "relay": case "relay":
group = NewRelay(groupName, providers) group = NewRelay(groupOption, providers)
default: default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
} }

View File

@@ -20,7 +20,7 @@ type Relay struct {
} }
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies := r.proxies(metadata) proxies := r.proxies(metadata, true)
if len(proxies) == 0 { if len(proxies) == 0 {
return nil, errors.New("proxy does not exist") return nil, errors.New("proxy does not exist")
} }
@@ -58,7 +58,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range r.rawProxies() { for _, proxy := range r.rawProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ 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) { elm, _, _ := r.single.Do(func() (interface{}, error) {
return getProvidersProxies(r.providers), nil return getProvidersProxies(r.providers, touch), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
} }
func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy { func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
proxies := r.rawProxies() proxies := r.rawProxies(touch)
for n, proxy := range proxies { for n, proxy := range proxies {
subproxy := proxy.Unwrap(metadata) subproxy := proxy.Unwrap(metadata)
@@ -89,9 +89,9 @@ func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
return proxies return proxies
} }
func NewRelay(name string, providers []provider.ProxyProvider) *Relay { func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{ return &Relay{
Base: outbound.NewBase(name, "", C.Relay, false), Base: outbound.NewBase(options.Name, "", C.Relay, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
} }

View File

@@ -13,13 +13,14 @@ import (
type Selector struct { type Selector struct {
*outbound.Base *outbound.Base
single *singledo.Single disableUDP bool
selected string single *singledo.Single
providers []provider.ProxyProvider selected string
providers []provider.ProxyProvider
} }
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 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 { if err == nil {
c.AppendToChains(s) 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) { 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 { if err == nil {
pc.AppendToChains(s) pc.AppendToChains(s)
} }
@@ -35,12 +36,16 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
} }
func (s *Selector) SupportUDP() bool { 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) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range getProvidersProxies(s.providers) { for _, proxy := range getProvidersProxies(s.providers, false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
@@ -52,11 +57,11 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
} }
func (s *Selector) Now() string { func (s *Selector) Now() string {
return s.selectedProxy().Name() return s.selectedProxy(false).Name()
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
for _, proxy := range getProvidersProxies(s.providers) { for _, proxy := range getProvidersProxies(s.providers, false) {
if proxy.Name() == name { if proxy.Name() == name {
s.selected = name s.selected = name
s.single.Reset() s.single.Reset()
@@ -68,12 +73,12 @@ func (s *Selector) Set(name string) error {
} }
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { 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) { elm, _, _ := s.single.Do(func() (interface{}, error) {
proxies := getProvidersProxies(s.providers) proxies := getProvidersProxies(s.providers, touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if proxy.Name() == s.selected { if proxy.Name() == s.selected {
return proxy, nil return proxy, nil
@@ -86,12 +91,13 @@ func (s *Selector) selectedProxy() C.Proxy {
return elm.(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() selected := providers[0].Proxies()[0].Name()
return &Selector{ return &Selector{
Base: outbound.NewBase(name, "", C.Selector, false), Base: outbound.NewBase(options.Name, "", C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
selected: selected, selected: selected,
disableUDP: options.DisableUDP,
} }
} }

View File

@@ -22,6 +22,7 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
type URLTest struct { type URLTest struct {
*outbound.Base *outbound.Base
tolerance uint16 tolerance uint16
disableUDP bool
fastNode C.Proxy fastNode C.Proxy
single *singledo.Single single *singledo.Single
fastSingle *singledo.Single fastSingle *singledo.Single
@@ -29,11 +30,11 @@ type URLTest struct {
} }
func (u *URLTest) Now() string { 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) { 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 { if err == nil {
c.AppendToChains(u) 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) { 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 { if err == nil {
pc.AppendToChains(u) 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 { 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) { elm, _, _ := u.single.Do(func() (interface{}, error) {
return getProvidersProxies(u.providers), nil return getProvidersProxies(u.providers, touch), nil
}) })
return elm.([]C.Proxy) 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) { elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
proxies := u.proxies() proxies := u.proxies(touch)
fast := proxies[0] fast := proxies[0]
min := fast.LastDelay() min := fast.LastDelay()
for _, proxy := range proxies[1:] { for _, proxy := range proxies[1:] {
@@ -89,12 +90,16 @@ func (u *URLTest) fast() C.Proxy {
} }
func (u *URLTest) SupportUDP() bool { 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) { func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range u.proxies() { for _, proxy := range u.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
@@ -117,12 +122,13 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption {
return opts return opts
} }
func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{ urlTest := &URLTest{
Base: outbound.NewBase(name, "", C.URLTest, false), Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10), fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers, providers: providers,
disableUDP: commonOptions.DisableUDP,
} }
for _, option := range options { for _, option := range options {

View File

@@ -2,9 +2,12 @@ package provider
import ( import (
"context" "context"
"sync"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
) )
const ( const (
@@ -17,10 +20,12 @@ type HealthCheckOption struct {
} }
type HealthCheck struct { type HealthCheck struct {
url string url string
proxies []C.Proxy proxies []C.Proxy
interval uint interval uint
done chan struct{} lazy bool
lastTouch *atomic.Int64
done chan struct{}
} }
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
@@ -30,7 +35,10 @@ func (hc *HealthCheck) process() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
hc.check() now := time.Now().Unix()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
hc.check()
}
case <-hc.done: case <-hc.done:
ticker.Stop() ticker.Stop()
return return
@@ -46,13 +54,24 @@ func (hc *HealthCheck) auto() bool {
return hc.interval != 0 return hc.interval != 0
} }
func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix())
}
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
wg := &sync.WaitGroup{}
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
go proxy.URLTest(ctx, hc.url) wg.Add(1)
go func(p C.Proxy) {
p.URLTest(ctx, hc.url)
wg.Done()
}(proxy)
} }
<-ctx.Done() wg.Wait()
cancel() cancel()
} }
@@ -60,11 +79,13 @@ func (hc *HealthCheck) close() {
hc.done <- struct{}{} 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{ return &HealthCheck{
proxies: proxies, proxies: proxies,
url: url, url: url,
interval: interval, interval: interval,
done: make(chan struct{}, 1), lazy: lazy,
lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1),
} }
} }

View File

@@ -17,6 +17,7 @@ type healthCheckSchema struct {
Enable bool `provider:"enable"` Enable bool `provider:"enable"`
URL string `provider:"url"` URL string `provider:"url"`
Interval int `provider:"interval"` Interval int `provider:"interval"`
Lazy bool `provider:"lazy,omitempty"`
} }
type proxyProviderSchema struct { type proxyProviderSchema struct {
@@ -30,7 +31,11 @@ type proxyProviderSchema struct {
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) { func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) 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 { if err := decoder.Decode(mapping, schema); err != nil {
return nil, err return nil, err
} }
@@ -39,7 +44,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
if schema.HealthCheck.Enable { if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval) 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) path := C.Path.Resolve(schema.Path)

View File

@@ -50,6 +50,9 @@ type Provider interface {
type ProxyProvider interface { type ProxyProvider interface {
Provider Provider
Proxies() []C.Proxy 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() HealthCheck()
} }
@@ -112,6 +115,11 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies return pp.proxies
} }
func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
pp.healthCheck.touch()
return pp.Proxies()
}
func proxiesParse(buf []byte) (interface{}, error) { func proxiesParse(buf []byte) (interface{}, error) {
schema := &ProxySchema{} schema := &ProxySchema{}
@@ -223,6 +231,11 @@ func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies return cp.proxies
} }
func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy {
cp.healthCheck.touch()
return cp.Proxies()
}
func stopCompatibleProvider(pd *CompatibleProvider) { func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close() pd.healthCheck.close()
} }

View File

@@ -121,7 +121,7 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.SetWithExpire(key, value, time.Unix(expires, 0)) 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. // The expires time will round to second.
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) { func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
c.mu.Lock() c.mu.Lock()

View File

@@ -2,11 +2,11 @@ package observable
import ( import (
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/atomic"
) )
func iterator(item []interface{}) chan interface{} { func iterator(item []interface{}) chan interface{} {
@@ -33,25 +33,25 @@ func TestObservable(t *testing.T) {
assert.Equal(t, count, 5) 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}) iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable(iter)
ch1, _ := src.Subscribe() ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe() ch2, _ := src.Subscribe()
var count int32 var count = atomic.NewInt32(0)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
waitCh := func(ch <-chan interface{}) { waitCh := func(ch <-chan interface{}) {
for range ch { for range ch {
atomic.AddInt32(&count, 1) count.Inc()
} }
wg.Done() wg.Done()
} }
go waitCh(ch1) go waitCh(ch1)
go waitCh(ch2) go waitCh(ch2)
wg.Wait() wg.Wait()
assert.Equal(t, int32(10), count) assert.Equal(t, int32(10), count.Load())
} }
func TestObservable_UnSubscribe(t *testing.T) { func TestObservable_UnSubscribe(t *testing.T) {
@@ -113,3 +113,34 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
_, more := <-list[0] _, more := <-list[0]
assert.False(t, more) 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()
}

View File

@@ -2,34 +2,32 @@ package observable
import ( import (
"sync" "sync"
"gopkg.in/eapache/channels.v1"
) )
type Subscription <-chan interface{} type Subscription <-chan interface{}
type Subscriber struct { type Subscriber struct {
buffer *channels.InfiniteChannel buffer chan interface{}
once sync.Once once sync.Once
} }
func (s *Subscriber) Emit(item interface{}) { func (s *Subscriber) Emit(item interface{}) {
s.buffer.In() <- item s.buffer <- item
} }
func (s *Subscriber) Out() Subscription { func (s *Subscriber) Out() Subscription {
return s.buffer.Out() return s.buffer
} }
func (s *Subscriber) Close() { func (s *Subscriber) Close() {
s.once.Do(func() { s.once.Do(func() {
s.buffer.Close() close(s.buffer)
}) })
} }
func newSubscriber() *Subscriber { func newSubscriber() *Subscriber {
sub := &Subscriber{ sub := &Subscriber{
buffer: channels.NewInfiniteChannel(), buffer: make(chan interface{}, 200),
} }
return sub return sub
} }

View File

@@ -61,7 +61,7 @@ func (alloc *Allocator) Put(buf []byte) error {
return nil return nil
} }
// msb return the pos of most significiant bit // msb return the pos of most significant bit
func msb(size int) uint16 { func msb(size int) uint16 {
return uint16(bits.Len32(uint32(size)) - 1) return uint16(bits.Len32(uint32(size)) - 1)
} }

View File

@@ -24,8 +24,8 @@ type Result struct {
Err error Err error
} }
// Do single.Do likes sync.singleFilght // Do single.Do likes sync.singleFlight
//lint:ignore ST1008 it likes sync.singleFilght //lint:ignore ST1008 it likes sync.singleFlight
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
s.mux.Lock() s.mux.Lock()
now := time.Now() now := time.Now()

View File

@@ -2,17 +2,17 @@ package singledo
import ( import (
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/atomic"
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
var shardCount int32 = 0 var shardCount = atomic.NewInt32(0)
call := func() (interface{}, error) { call := func() (interface{}, error) {
foo++ foo++
time.Sleep(time.Millisecond * 5) time.Sleep(time.Millisecond * 5)
@@ -26,7 +26,7 @@ func TestBasic(t *testing.T) {
go func() { go func() {
_, _, shard := single.Do(call) _, _, shard := single.Do(call)
if shard { if shard {
atomic.AddInt32(&shardCount, 1) shardCount.Inc()
} }
wg.Done() wg.Done()
}() }()
@@ -34,7 +34,7 @@ func TestBasic(t *testing.T) {
wg.Wait() wg.Wait()
assert.Equal(t, 1, foo) assert.Equal(t, 1, foo)
assert.Equal(t, int32(4), shardCount) assert.Equal(t, int32(4), shardCount.Load())
} }
func TestTimer(t *testing.T) { func TestTimer(t *testing.T) {

104
component/dialer/bind.go Normal file
View 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
}

View 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
}

View 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
}

View 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
}

View File

@@ -19,17 +19,6 @@ func Dialer() (*net.Dialer, error) {
return dialer, nil 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) { func Dial(network, address string) (net.Conn, error) {
return DialContext(context.Background(), network, address) 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) { func ListenPacket(network, address string) (net.PacketConn, error) {
lc, err := ListenConfig() cfg := &net.ListenConfig{}
if err != nil { if ListenPacketHook != nil {
return nil, err var err error
} address, err = ListenPacketHook(cfg, address)
if ListenPacketHook != nil && address == "" {
ip, err := ListenPacketHook()
if err != nil { if err != nil {
return nil, err 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) { func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {

View File

@@ -3,20 +3,15 @@ package dialer
import ( import (
"errors" "errors"
"net" "net"
"time"
"github.com/Dreamacro/clash/common/singledo"
) )
type DialerHookFunc = func(dialer *net.Dialer) error type DialerHookFunc = func(dialer *net.Dialer) error
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
type ListenConfigHookFunc = func(*net.ListenConfig) error type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error)
type ListenPacketHookFunc = func() (net.IP, error)
var ( var (
DialerHook DialerHookFunc DialerHook DialerHookFunc
DialHook DialHookFunc DialHook DialHookFunc
ListenConfigHook ListenConfigHookFunc
ListenPacketHook ListenPacketHookFunc ListenPacketHook ListenPacketHookFunc
) )
@@ -25,124 +20,24 @@ var (
ErrNetworkNotSupport = errors.New("network not support") 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 { func ListenPacketWithInterface(name string) ListenPacketHookFunc {
single := singledo.NewSingle(5 * time.Second) return func(lc *net.ListenConfig, address string) (string, error) {
err := bindIfaceToListenConfig(lc, name)
return func() (net.IP, error) { if err == errPlatformNotSupport {
elm, err, _ := single.Do(func() (interface{}, error) { address, err = fallbackBindToListenConfig(name)
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
} }
addrs := elm.([]net.Addr) return address, err
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok || addr.IP.To4() == nil {
continue
}
return addr.IP, nil
}
return nil, ErrAddrNotFound
} }
} }
func DialerWithInterface(name string) DialHookFunc { func DialerWithInterface(name string) DialHookFunc {
single := singledo.NewSingle(5 * time.Second)
return func(dialer *net.Dialer, network string, ip net.IP) error { return func(dialer *net.Dialer, network string, ip net.IP) error {
elm, err, _ := single.Do(func() (interface{}, error) { err := bindIfaceToDialer(dialer, name)
iface, err := net.InterfaceByName(name) if err == errPlatformNotSupport {
if err != nil { err = fallbackBindToDialer(dialer, network, ip, name)
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return addrs, nil
})
if err != nil {
return err
} }
addrs := elm.([]net.Addr) 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
} }
} }

View File

@@ -17,6 +17,7 @@ type Pool struct {
offset uint32 offset uint32
mux sync.Mutex mux sync.Mutex
host *trie.DomainTrie host *trie.DomainTrie
ipnet *net.IPNet
cache *cache.LruCache cache *cache.LruCache
} }
@@ -89,6 +90,11 @@ func (p *Pool) Gateway() net.IP {
return uintToIP(p.gateway) return uintToIP(p.gateway)
} }
// IPNet return raw ipnet
func (p *Pool) IPNet() *net.IPNet {
return p.ipnet
}
// PatchFrom clone cache from old pool // PatchFrom clone cache from old pool
func (p *Pool) PatchFrom(o *Pool) { func (p *Pool) PatchFrom(o *Pool) {
o.cache.CloneTo(p.cache) o.cache.CloneTo(p.cache)
@@ -141,6 +147,7 @@ func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
max: max, max: max,
gateway: min - 1, gateway: min - 1,
host: host, host: host,
ipnet: ipnet,
cache: cache.NewLRUCache(cache.WithSize(size * 2)), cache: cache.NewLRUCache(cache.WithSize(size * 2)),
}, nil }, nil
} }

View File

@@ -22,9 +22,9 @@ func (t *Table) Get(key string) C.PacketConn {
return item.(C.PacketConn) return item.(C.PacketConn)
} }
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.mapping.LoadOrStore(key, &sync.WaitGroup{}) item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item.(*sync.WaitGroup), loaded return item.(*sync.Cond), loaded
} }
func (t *Table) Delete(key string) { func (t *Table) Delete(key string) {

View File

@@ -0,0 +1,21 @@
package process
import (
"errors"
"net"
)
var (
ErrInvalidNetwork = errors.New("invalid network")
ErrPlatformNotSupport = errors.New("not support on this platform")
ErrNotFound = errors.New("process not found")
)
const (
TCP = "tcp"
UDP = "udp"
)
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort)
}

View File

@@ -1,109 +1,26 @@
package rules package process
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
) )
// store process name for when dealing with multiple PROCESS-NAME rules
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
type Process struct {
adapter string
process string
}
func (ps *Process) RuleType() C.RuleType {
return C.Process
}
func (ps *Process) Match(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 {
name, err := getExecPathFromAddress(metadata)
if err != nil {
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
}
return strings.EqualFold(cached.(string), ps.process)
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
return &Process{
adapter: adapter,
process: process,
}, nil
}
const ( const (
procpidpathinfo = 0xb procpidpathinfo = 0xb
procpidpathinfosize = 1024 procpidpathinfosize = 1024
proccallnumpidinfo = 0x2 proccallnumpidinfo = 0x2
) )
func getExecPathFromPID(pid uint32) (string, error) { func findProcessName(network string, ip net.IP, port int) (string, error) {
buf := make([]byte, procpidpathinfosize)
_, _, errno := syscall.Syscall6(
syscall.SYS_PROC_INFO,
proccallnumpidinfo,
uintptr(pid),
procpidpathinfo,
0,
uintptr(unsafe.Pointer(&buf[0])),
procpidpathinfosize)
if errno != 0 {
return "", errno
}
firstZero := bytes.IndexByte(buf, 0)
if firstZero <= 0 {
return "", nil
}
return filepath.Base(string(buf[:firstZero])), nil
}
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
port, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
var spath string var spath string
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
spath = "net.inet.tcp.pcblist_n" spath = "net.inet.tcp.pcblist_n"
case C.UDP: case UDP:
spath = "net.inet.udp.pcblist_n" spath = "net.inet.udp.pcblist_n"
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
@@ -123,7 +40,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := 384 itemSize := 384
if metadata.NetWork == C.TCP { if network == TCP {
// rup8(sizeof(xtcpcb_n)) // rup8(sizeof(xtcpcb_n))
itemSize += 208 itemSize += 208
} }
@@ -161,7 +78,28 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
return getExecPathFromPID(pid) return getExecPathFromPID(pid)
} }
return "", errors.New("process not found") return "", ErrNotFound
}
func getExecPathFromPID(pid uint32) (string, error) {
buf := make([]byte, procpidpathinfosize)
_, _, errno := syscall.Syscall6(
syscall.SYS_PROC_INFO,
proccallnumpidinfo,
uintptr(pid),
procpidpathinfo,
0,
uintptr(unsafe.Pointer(&buf[0])),
procpidpathinfosize)
if errno != 0 {
return "", errno
}
firstZero := bytes.IndexByte(buf, 0)
if firstZero <= 0 {
return "", nil
}
return filepath.Base(string(buf[:firstZero])), nil
} }
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {

View File

@@ -1,8 +1,7 @@
package rules package process
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"path/filepath" "path/filepath"
@@ -12,78 +11,48 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
// store process name for when dealing with multiple PROCESS-NAME rules // store process name for when dealing with multiple PROCESS-NAME rules
var ( 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 defaultSearcher *searcher
once sync.Once once sync.Once
) )
type Process struct { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
adapter string
process string
}
func (ps *Process) RuleType() C.RuleType {
return C.Process
}
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 {
name, err := getExecPathFromAddress(metadata)
if err != nil {
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
}
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
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
once.Do(func() { once.Do(func() {
err := initSearcher() if err := initSearcher(); err != nil {
if err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
log.Warnln("All PROCESS-NAME rules will be skipped") log.Warnln("All PROCESS-NAME rules will be skipped")
return return
} }
matchMeta = match
}) })
return &Process{
adapter: adapter, var spath string
process: process, isTCP := network == TCP
}, nil switch network {
case TCP:
spath = "net.inet.tcp.pcblist"
case UDP:
spath = "net.inet.udp.pcblist"
default:
return "", ErrInvalidNetwork
}
value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
}
buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
if err != nil {
return "", err
}
return getExecPathFromPID(pid)
} }
func getExecPathFromPID(pid uint32) (string, error) { func getExecPathFromPID(pid uint32) (string, error) {
@@ -107,41 +76,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
return filepath.Base(string(buf[:size-1])), nil return filepath.Base(string(buf[:size-1])), nil
} }
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
port, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
var spath string
var isTCP bool
switch metadata.NetWork {
case C.TCP:
spath = "net.inet.tcp.pcblist"
isTCP = true
case C.UDP:
spath = "net.inet.udp.pcblist"
isTCP = false
default:
return "", ErrInvalidNetwork
}
value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
}
buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
if err != nil {
return "", err
}
return getExecPathFromPID(pid)
}
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0])) return *(*uint32)(unsafe.Pointer(&b[0]))
} }
@@ -213,7 +147,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
return s.searchSocketPid(socket) return s.searchSocketPid(socket)
} }
return 0, errNotFound return 0, ErrNotFound
} }
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
@@ -235,7 +169,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
return pid, nil return pid, nil
} }
} }
return 0, errNotFound return 0, ErrNotFound
} }
func newSearcher(major int) *searcher { func newSearcher(major int) *searcher {

View File

@@ -1,4 +1,4 @@
package rules package process
import ( import (
"bytes" "bytes"
@@ -9,15 +9,10 @@ import (
"net" "net"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
) )
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
@@ -30,7 +25,7 @@ func init() {
} }
} }
type SocketResolver func(metadata *C.Metadata) (inode, uid int, err error) type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
type ProcessNameResolver func(inode, uid int) (name string, err error) type ProcessNameResolver func(inode, uid int) (name string, err error)
// export for android // export for android
@@ -39,51 +34,6 @@ var (
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
) )
type Process struct {
adapter string
process string
}
func (p *Process) RuleType() C.RuleType {
return C.Process
}
func (p *Process) Match(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 {
processName, err := resolveProcessName(metadata)
if err != nil {
log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error())
}
processCache.Set(key, processName)
cached = processName
}
return strings.EqualFold(cached.(string), p.process)
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
return &Process{
adapter: adapter,
process: process,
}, nil
}
const ( const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20 socketDiagByFamily = 20
@@ -92,10 +42,8 @@ const (
var nativeEndian binary.ByteOrder = binary.LittleEndian var nativeEndian binary.ByteOrder = binary.LittleEndian
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
func resolveProcessName(metadata *C.Metadata) (string, error) {
inode, uid, err := DefaultSocketResolver(metadata)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -103,31 +51,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
return DefaultProcessNameResolver(inode, uid) return DefaultProcessNameResolver(inode, uid)
} }
func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) { func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) {
var family byte var family byte
var protocol byte var protocol byte
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
protocol = syscall.IPPROTO_TCP protocol = syscall.IPPROTO_TCP
case C.UDP: case UDP:
protocol = syscall.IPPROTO_UDP protocol = syscall.IPPROTO_UDP
default: default:
return 0, 0, ErrInvalidNetwork return 0, 0, ErrInvalidNetwork
} }
if metadata.SrcIP.To4() != nil { if ip.To4() != nil {
family = syscall.AF_INET family = syscall.AF_INET
} else { } else {
family = syscall.AF_INET6 family = syscall.AF_INET6
} }
srcPort, err := strconv.Atoi(metadata.SrcPort) req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
if err != nil {
return 0, 0, err
}
req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil { if err != nil {

View File

@@ -0,0 +1,10 @@
// +build !darwin,!linux,!windows
// +build !freebsd !amd64
package process
import "net"
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
return "", ErrPlatformNotSupport
}

View File

@@ -1,18 +1,13 @@
package rules package process
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"strings"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@@ -27,10 +22,6 @@ const (
) )
var ( 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 }
getExTcpTable uintptr getExTcpTable uintptr
getExUdpTable uintptr getExUdpTable uintptr
queryProcName uintptr queryProcName uintptr
@@ -67,47 +58,7 @@ func initWin32API() error {
return nil return nil
} }
type Process struct { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
adapter string
process string
}
func (p *Process) RuleType() C.RuleType {
return C.Process
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func match(p *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 {
processName, err := resolveProcessName(metadata)
if err != nil {
log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error())
}
processCache.Set(key, processName)
cached = processName
}
return strings.EqualFold(cached.(string), p.process)
}
func (p *Process) Match(metadata *C.Metadata) bool {
return matchMeta(p, metadata)
}
func NewProcess(process string, adapter string) (*Process, error) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) {
log.Warnln("All PROCESS-NAMES rules will be skiped") log.Warnln("All PROCESS-NAMES rules will be skiped")
return return
} }
matchMeta = match
}) })
return &Process{
adapter: adapter,
process: process,
}, nil
}
func resolveProcessName(metadata *C.Metadata) (string, error) {
ip := metadata.SrcIP
family := windows.AF_INET family := windows.AF_INET
if ip.To4() == nil { if ip.To4() == nil {
family = windows.AF_INET6 family = windows.AF_INET6
@@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
var class int var class int
var fn uintptr var fn uintptr
switch metadata.NetWork { switch network {
case C.TCP: case TCP:
fn = getExTcpTable fn = getExTcpTable
class = tcpTablePidConn class = tcpTablePidConn
case C.UDP: case UDP:
fn = getExUdpTable fn = getExUdpTable
class = udpTablePid class = udpTablePid
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
} }
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
return "", err
}
buf, err := getTransportTable(fn, family, class) buf, err := getTransportTable(fn, family, class)
if err != nil { if err != nil {
return "", err return "", err
} }
s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP) s := newSearcher(family == windows.AF_INET, network == TCP)
pid, err := s.Search(buf, ip, uint16(srcPort)) pid, err := s.Search(buf, ip, uint16(srcPort))
if err != nil { if err != nil {
@@ -196,14 +133,15 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) {
} }
srcIP := net.IP(row[s.ip : s.ip+s.ipSize]) srcIP := net.IP(row[s.ip : s.ip+s.ipSize])
if !ip.Equal(srcIP) { // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue
} }
pid := readNativeUint32(row[s.pid : s.pid+4]) pid := readNativeUint32(row[s.pid : s.pid+4])
return pid, nil return pid, nil
} }
return 0, errNotFound return 0, ErrNotFound
} }
func newSearcher(isV4, isTCP bool) *searcher { func newSearcher(isV4, isTCP bool) *searcher {

View File

@@ -10,6 +10,7 @@ type Enhancer interface {
FakeIPEnabled() bool FakeIPEnabled() bool
MappingEnabled() bool MappingEnabled() bool
IsFakeIP(net.IP) bool IsFakeIP(net.IP) bool
IsExistFakeIP(net.IP) bool
FindHostByIP(net.IP) (string, bool) FindHostByIP(net.IP) (string, bool)
} }
@@ -37,6 +38,14 @@ func IsFakeIP(ip net.IP) bool {
return false 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) { func FindHostByIP(ip net.IP) (string, bool) {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.FindHostByIP(ip) return mapper.FindHostByIP(ip)

View File

@@ -38,6 +38,7 @@ type Inbound struct {
Port int `json:"port"` Port int `json:"port"`
SocksPort int `json:"socks-port"` SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"` MixedPort int `json:"mixed-port"`
Authentication []string `json:"authentication"` Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
@@ -111,6 +112,7 @@ type RawConfig struct {
Port int `yaml:"port"` Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"` SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"` RedirPort int `yaml:"redir-port"`
TProxyPort int `yaml:"tproxy-port"`
MixedPort int `yaml:"mixed-port"` MixedPort int `yaml:"mixed-port"`
Authentication []string `yaml:"authentication"` Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
@@ -234,6 +236,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
Port: cfg.Port, Port: cfg.Port,
SocksPort: cfg.SocksPort, SocksPort: cfg.SocksPort,
RedirPort: cfg.RedirPort, RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort, MixedPort: cfg.MixedPort,
AllowLan: cfg.AllowLan, AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress, BindAddress: cfg.BindAddress,
@@ -342,11 +345,16 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
for _, v := range proxyList { for _, v := range proxyList {
ps = append(ps, proxies[v]) ps = append(ps, proxies[v])
} }
hc := provider.NewHealthCheck(ps, "", 0) hc := provider.NewHealthCheck(ps, "", 0, true)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
providersMap[provider.ReservedName] = pd 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) proxies["GLOBAL"] = outbound.NewProxy(global)
return proxies, providersMap, nil return proxies, providersMap, nil
} }
@@ -387,10 +395,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
parsed, parseErr := R.ParseRule(rule[0], payload, target, params) parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
if parseErr != nil { if parseErr != nil {
if parseErr == R.ErrPlatformNotSupport {
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())
} }

View File

@@ -23,7 +23,7 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
indegree int indegree int
// topological order // topological order
topo int topo int
// the origional data in `groupsConfig` // the original data in `groupsConfig`
data map[string]interface{} data map[string]interface{}
// `outdegree` and `from` are used in loop locating // `outdegree` and `from` are used in loop locating
outdegree int outdegree int
@@ -65,7 +65,7 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
index := 0 index := 0
queue := make([]string, 0) queue := make([]string, 0)
for name, node := range graph { 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 { if node.indegree == 0 {
queue = append(queue, name) queue = append(queue, name)
} }

View File

@@ -137,7 +137,7 @@ type UDPPacket interface {
// WriteBack writes the payload with source IP/Port equals addr // WriteBack writes the payload with source IP/Port equals addr
// - variable source IP/Port is important to STUN // - 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. // this is important when using Fake-IP.
WriteBack(b []byte, addr net.Addr) (n int, err error) WriteBack(b []byte, addr net.Addr) (n int, err error)

View File

@@ -19,6 +19,7 @@ const (
HTTPCONNECT HTTPCONNECT
SOCKS SOCKS
REDIR REDIR
TPROXY
) )
type NetWork int type NetWork int
@@ -46,6 +47,8 @@ func (t Type) String() string {
return "Socks5" return "Socks5"
case REDIR: case REDIR:
return "Redir" return "Redir"
case TPROXY:
return "TProxy"
default: default:
return "Unknown" return "Unknown"
} }

View File

@@ -21,7 +21,7 @@ func (h *ResolverEnhancer) MappingEnabled() bool {
return h.mode == FAKEIP || h.mode == MAPPING return h.mode == FAKEIP || h.mode == MAPPING
} }
func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool {
if !h.FakeIPEnabled() { if !h.FakeIPEnabled() {
return false return false
} }
@@ -33,6 +33,18 @@ func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool {
return false 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) { func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) {
if pool := h.fakePool; pool != nil { if pool := h.fakePool; pool != nil {
if host, existed := pool.LookBack(ip); existed { if host, existed := pool.LookBack(ip); existed {

View File

@@ -107,16 +107,12 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
return next(r) return next(r)
} }
if q.Qtype == D.TypeAAAA { switch q.Qtype {
msg := &D.Msg{} case D.TypeAAAA, D.TypeSVCB, D.TypeHTTPS:
msg.Answer = []D.RR{} return handleMsgWithEmptyAnswer(r), nil
}
msg.SetRcode(r, D.RcodeSuccess) if q.Qtype != D.TypeA {
msg.Authoritative = true
msg.RecursionAvailable = true
return msg, nil
} else if q.Qtype != D.TypeA {
return next(r) return next(r)
} }
@@ -143,14 +139,7 @@ func withResolver(resolver *Resolver) handler {
// return a empty AAAA msg when ipv6 disabled // return a empty AAAA msg when ipv6 disabled
if !resolver.ipv6 && q.Qtype == D.TypeAAAA { if !resolver.ipv6 && q.Qtype == D.TypeAAAA {
msg := &D.Msg{} return handleMsgWithEmptyAnswer(r), nil
msg.Answer = []D.RR{}
msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true
msg.RecursionAvailable = true
return msg, nil
} }
msg, err := resolver.Exchange(r) msg, err := resolver.Exchange(r)

View File

@@ -142,3 +142,14 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
} }
return ret 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
View File

@@ -1,23 +1,22 @@
module github.com/Dreamacro/clash module github.com/Dreamacro/clash
go 1.14 go 1.15
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.6 github.com/Dreamacro/go-shadowsocks2 v0.1.6
github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.1.1 github.com/go-chi/cors v1.1.1
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.3.0+incompatible github.com/gofrs/uuid v3.3.0+incompatible
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.31 github.com/miekg/dns v1.1.35
github.com/oschwald/geoip2-golang v1.4.0 github.com/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus v1.7.0
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de go.uber.org/atomic v1.7.0
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
gopkg.in/eapache/channels.v1 v1.1.0 golang.org/x/sys v0.0.0-20201223074533-0d417f636930
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.4.0
) )

48
go.sum
View File

@@ -3,8 +3,6 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72I
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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 h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 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= github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
@@ -15,56 +13,60 @@ 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/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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
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.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/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/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -86,6 +86,7 @@ func GetGeneral() *config.General {
Port: ports.Port, Port: ports.Port,
SocksPort: ports.SocksPort, SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort, RedirPort: ports.RedirPort,
TProxyPort: ports.TProxyPort,
MixedPort: ports.MixedPort, MixedPort: ports.MixedPort,
Authentication: authenticator, Authentication: authenticator,
AllowLan: P.AllowLan(), AllowLan: P.AllowLan(),
@@ -191,6 +192,10 @@ func updateGeneral(general *config.General, force bool) {
log.Errorln("Start Redir server error: %s", err.Error()) 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 { if err := P.ReCreateMixed(general.MixedPort); err != nil {
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
} }

View File

@@ -26,6 +26,7 @@ type configSchema struct {
Port *int `json:"port"` Port *int `json:"port"`
SocksPort *int `json:"socks-port"` SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"` RedirPort *int `json:"redir-port"`
TProxyPort *int `json:"tproxy-port"`
MixedPort *int `json:"mixed-port"` MixedPort *int `json:"mixed-port"`
AllowLan *bool `json:"allow-lan"` AllowLan *bool `json:"allow-lan"`
BindAddress *string `json:"bind-address"` 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.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
if general.Mode != nil { if general.Mode != nil {
@@ -106,7 +108,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
} else { } else {
if !filepath.IsAbs(req.Path) { if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest) 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 return
} }

View File

@@ -72,21 +72,29 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache
func HandleConn(conn net.Conn, cache *cache.Cache) { func HandleConn(conn net.Conn, cache *cache.Cache) {
br := bufio.NewReader(conn) br := bufio.NewReader(conn)
keepAlive:
request, err := http.ReadRequest(br) request, err := http.ReadRequest(br)
if err != nil || request.URL.Host == "" { if err != nil || request.URL.Host == "" {
conn.Close() conn.Close()
return return
} }
keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
authenticator := authStore.Authenticator() authenticator := authStore.Authenticator()
if authenticator != nil { if authenticator != nil {
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
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() if keepAlive {
goto keepAlive
}
return return
} else if !canActivate(authStrings[1], authenticator, cache) { } else if !canActivate(authStrings[1], authenticator, cache) {
conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n"))
log.Infoln("Auth failed from %s", conn.RemoteAddr().String()) log.Infoln("Auth failed from %s", conn.RemoteAddr().String())
if keepAlive {
goto keepAlive
}
conn.Close() conn.Close()
return return
} }
@@ -95,6 +103,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) {
if request.Method == http.MethodConnect { if request.Method == http.MethodConnect {
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil { if err != nil {
conn.Close()
return return
} }
tunnel.Add(adapters.NewHTTPS(request, conn)) tunnel.Add(adapters.NewHTTPS(request, conn))

View File

@@ -17,26 +17,30 @@ var (
allowLan = false allowLan = false
bindAddress = "*" bindAddress = "*"
socksListener *socks.SockListener socksListener *socks.SockListener
socksUDPListener *socks.SockUDPListener socksUDPListener *socks.SockUDPListener
httpListener *http.HttpListener httpListener *http.HttpListener
redirListener *redir.RedirListener redirListener *redir.RedirListener
redirUDPListener *redir.RedirUDPListener redirUDPListener *redir.RedirUDPListener
mixedListener *mixed.MixedListener tproxyListener *redir.TProxyListener
mixedUDPLister *socks.SockUDPListener tproxyUDPListener *redir.RedirUDPListener
mixedListener *mixed.MixedListener
mixedUDPLister *socks.SockUDPListener
// lock for recreate function // lock for recreate function
socksMux sync.Mutex socksMux sync.Mutex
httpMux sync.Mutex httpMux sync.Mutex
redirMux sync.Mutex redirMux sync.Mutex
mixedMux sync.Mutex tproxyMux sync.Mutex
mixedMux sync.Mutex
) )
type Ports struct { type Ports struct {
Port int `json:"port"` Port int `json:"port"`
SocksPort int `json:"socks-port"` SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
MixedPort int `json:"mixed-port"` TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"`
} }
func AllowLan() bool { func AllowLan() bool {
@@ -174,6 +178,46 @@ func ReCreateRedir(port int) error {
return nil 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 { func ReCreateMixed(port int) error {
mixedMux.Lock() mixedMux.Lock()
defer mixedMux.Unlock() defer mixedMux.Unlock()
@@ -245,6 +289,12 @@ func GetPorts() *Ports {
ports.RedirPort = port ports.RedirPort = port
} }
if tproxyListener != nil {
_, portStr, _ := net.SplitHostPort(tproxyListener.Address())
port, _ := strconv.Atoi(portStr)
ports.TProxyPort = port
}
if mixedListener != nil { if mixedListener != nil {
_, portStr, _ := net.SplitHostPort(mixedListener.Address()) _, portStr, _ := net.SplitHostPort(mixedListener.Address())
port, _ := strconv.Atoi(portStr) port, _ := strconv.Atoi(portStr)

71
proxy/redir/tproxy.go Normal file
View 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))
}

View 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
}

View 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")
}

View File

@@ -26,7 +26,12 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
c := l.(*net.UDPConn) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -14,43 +14,6 @@ const (
IPV6_RECVORIGDSTADDR = 0x4a 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) { func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil { if err != nil {

View File

@@ -7,10 +7,6 @@ import (
"net" "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) { func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
return nil, errors.New("UDP redir not supported on current platform") return nil, errors.New("UDP redir not supported on current platform")
} }

View File

@@ -15,7 +15,7 @@ func (c *packet) Data() []byte {
return c.buf 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) { func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr)
if err != nil { if err != nil {

View File

@@ -18,7 +18,7 @@ func (c *packet) Data() []byte {
return c.payload 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) { func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil { if err != nil {

View File

@@ -5,9 +5,7 @@ import (
) )
var ( var (
errPayload = errors.New("payload error") errPayload = errors.New("payload error")
ErrPlatformNotSupport = errors.New("not support on this platform")
ErrInvalidNetwork = errors.New("invalid network")
noResolve = "no-resolve" noResolve = "no-resolve"
) )

65
rules/process.go Normal file
View File

@@ -0,0 +1,65 @@
package rules
import (
"fmt"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/process"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
type Process struct {
adapter string
process string
}
func (ps *Process) RuleType() C.RuleType {
return C.Process
}
func (ps *Process) Match(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 {
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
processCache.Set(key, "")
return false
}
name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
if err != nil {
log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
}
return strings.EqualFold(cached.(string), ps.process)
}
func (p *Process) Adapter() string {
return p.adapter
}
func (p *Process) Payload() string {
return p.process
}
func (p *Process) ShouldResolveIP() bool {
return false
}
func NewProcess(process string, adapter string) (*Process, error) {
return &Process{
adapter: adapter,
process: process,
}, nil
}

View File

@@ -1,12 +0,0 @@
// +build !darwin,!linux,!windows
// +build !freebsd !amd64
package rules
import (
C "github.com/Dreamacro/clash/constant"
)
func NewProcess(process string, adapter string) (C.Rule, error) {
return nil, ErrPlatformNotSupport
}

View File

@@ -2,26 +2,34 @@ package tunnel
import ( import (
"sync" "sync"
"sync/atomic"
"time" "time"
"go.uber.org/atomic"
) )
var DefaultManager *Manager var DefaultManager *Manager
func init() { func init() {
DefaultManager = &Manager{} DefaultManager = &Manager{
uploadTemp: atomic.NewInt64(0),
downloadTemp: atomic.NewInt64(0),
uploadBlip: atomic.NewInt64(0),
downloadBlip: atomic.NewInt64(0),
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
}
go DefaultManager.handle() go DefaultManager.handle()
} }
type Manager struct { type Manager struct {
connections sync.Map connections sync.Map
uploadTemp int64 uploadTemp *atomic.Int64
downloadTemp int64 downloadTemp *atomic.Int64
uploadBlip int64 uploadBlip *atomic.Int64
downloadBlip int64 downloadBlip *atomic.Int64
uploadTotal int64 uploadTotal *atomic.Int64
downloadTotal int64 downloadTotal *atomic.Int64
} }
func (m *Manager) Join(c tracker) { func (m *Manager) Join(c tracker) {
@@ -33,17 +41,17 @@ func (m *Manager) Leave(c tracker) {
} }
func (m *Manager) PushUploaded(size int64) { func (m *Manager) PushUploaded(size int64) {
atomic.AddInt64(&m.uploadTemp, size) m.uploadTemp.Add(size)
atomic.AddInt64(&m.uploadTotal, size) m.uploadTotal.Add(size)
} }
func (m *Manager) PushDownloaded(size int64) { func (m *Manager) PushDownloaded(size int64) {
atomic.AddInt64(&m.downloadTemp, size) m.downloadTemp.Add(size)
atomic.AddInt64(&m.downloadTotal, size) m.downloadTotal.Add(size)
} }
func (m *Manager) Now() (up int64, down int64) { func (m *Manager) Now() (up int64, down int64) {
return atomic.LoadInt64(&m.uploadBlip), atomic.LoadInt64(&m.downloadBlip) return m.uploadBlip.Load(), m.downloadBlip.Load()
} }
func (m *Manager) Snapshot() *Snapshot { func (m *Manager) Snapshot() *Snapshot {
@@ -54,29 +62,29 @@ func (m *Manager) Snapshot() *Snapshot {
}) })
return &Snapshot{ return &Snapshot{
UploadTotal: atomic.LoadInt64(&m.uploadTotal), UploadTotal: m.uploadTotal.Load(),
DownloadTotal: atomic.LoadInt64(&m.downloadTotal), DownloadTotal: m.downloadTotal.Load(),
Connections: connections, Connections: connections,
} }
} }
func (m *Manager) ResetStatistic() { func (m *Manager) ResetStatistic() {
m.uploadTemp = 0 m.uploadTemp.Store(0)
m.uploadBlip = 0 m.uploadBlip.Store(0)
m.uploadTotal = 0 m.uploadTotal.Store(0)
m.downloadTemp = 0 m.downloadTemp.Store(0)
m.downloadBlip = 0 m.downloadBlip.Store(0)
m.downloadTotal = 0 m.downloadTotal.Store(0)
} }
func (m *Manager) handle() { func (m *Manager) handle() {
ticker := time.NewTicker(time.Second) ticker := time.NewTicker(time.Second)
for range ticker.C { for range ticker.C {
atomic.StoreInt64(&m.uploadBlip, atomic.LoadInt64(&m.uploadTemp)) m.uploadBlip.Store(m.uploadTemp.Load())
atomic.StoreInt64(&m.uploadTemp, 0) m.uploadTemp.Store(0)
atomic.StoreInt64(&m.downloadBlip, atomic.LoadInt64(&m.downloadTemp)) m.downloadBlip.Store(m.downloadTemp.Load())
atomic.StoreInt64(&m.downloadTemp, 0) m.downloadTemp.Store(0)
} }
} }

View File

@@ -5,7 +5,9 @@ import (
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"go.uber.org/atomic"
) )
type tracker interface { type tracker interface {
@@ -14,14 +16,14 @@ type tracker interface {
} }
type trackerInfo struct { type trackerInfo struct {
UUID uuid.UUID `json:"id"` UUID uuid.UUID `json:"id"`
Metadata *C.Metadata `json:"metadata"` Metadata *C.Metadata `json:"metadata"`
UploadTotal int64 `json:"upload"` UploadTotal *atomic.Int64 `json:"upload"`
DownloadTotal int64 `json:"download"` DownloadTotal *atomic.Int64 `json:"download"`
Start time.Time `json:"start"` Start time.Time `json:"start"`
Chain C.Chain `json:"chains"` Chain C.Chain `json:"chains"`
Rule string `json:"rule"` Rule string `json:"rule"`
RulePayload string `json:"rulePayload"` RulePayload string `json:"rulePayload"`
} }
type tcpTracker struct { type tcpTracker struct {
@@ -38,7 +40,7 @@ func (tt *tcpTracker) Read(b []byte) (int, error) {
n, err := tt.Conn.Read(b) n, err := tt.Conn.Read(b)
download := int64(n) download := int64(n)
tt.manager.PushDownloaded(download) tt.manager.PushDownloaded(download)
tt.DownloadTotal += download tt.DownloadTotal.Add(download)
return n, err return n, err
} }
@@ -46,7 +48,7 @@ func (tt *tcpTracker) Write(b []byte) (int, error) {
n, err := tt.Conn.Write(b) n, err := tt.Conn.Write(b)
upload := int64(n) upload := int64(n)
tt.manager.PushUploaded(upload) tt.manager.PushUploaded(upload)
tt.UploadTotal += upload tt.UploadTotal.Add(upload)
return n, err return n, err
} }
@@ -62,11 +64,13 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
Conn: conn, Conn: conn,
manager: manager, manager: manager,
trackerInfo: &trackerInfo{ trackerInfo: &trackerInfo{
UUID: uuid, UUID: uuid,
Start: time.Now(), Start: time.Now(),
Metadata: metadata, Metadata: metadata,
Chain: conn.Chains(), Chain: conn.Chains(),
Rule: "", Rule: "",
UploadTotal: atomic.NewInt64(0),
DownloadTotal: atomic.NewInt64(0),
}, },
} }
@@ -93,7 +97,7 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := ut.PacketConn.ReadFrom(b) n, addr, err := ut.PacketConn.ReadFrom(b)
download := int64(n) download := int64(n)
ut.manager.PushDownloaded(download) ut.manager.PushDownloaded(download)
ut.DownloadTotal += download ut.DownloadTotal.Add(download)
return n, addr, err return n, addr, err
} }
@@ -101,7 +105,7 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
n, err := ut.PacketConn.WriteTo(b, addr) n, err := ut.PacketConn.WriteTo(b, addr)
upload := int64(n) upload := int64(n)
ut.manager.PushUploaded(upload) ut.manager.PushUploaded(upload)
ut.UploadTotal += upload ut.UploadTotal.Add(upload)
return n, err return n, err
} }
@@ -117,11 +121,13 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
PacketConn: conn, PacketConn: conn,
manager: manager, manager: manager,
trackerInfo: &trackerInfo{ trackerInfo: &trackerInfo{
UUID: uuid, UUID: uuid,
Start: time.Now(), Start: time.Now(),
Metadata: metadata, Metadata: metadata,
Chain: conn.Chains(), Chain: conn.Chains(),
Rule: "", Rule: "",
UploadTotal: atomic.NewInt64(0),
DownloadTotal: atomic.NewInt64(0),
}, },
} }

View File

@@ -13,13 +13,11 @@ import (
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
channels "gopkg.in/eapache/channels.v1"
) )
var ( var (
tcpQueue = channels.NewInfiniteChannel() tcpQueue = make(chan C.ServerAdapter, 200)
udpQueue = channels.NewInfiniteChannel() udpQueue = make(chan *inbound.PacketAdapter, 200)
natTable = nat.New() natTable = nat.New()
rules []C.Rule rules []C.Rule
proxies = make(map[string]C.Proxy) proxies = make(map[string]C.Proxy)
@@ -39,12 +37,15 @@ func init() {
// Add request to queue // Add request to queue
func Add(req C.ServerAdapter) { func Add(req C.ServerAdapter) {
tcpQueue.In() <- req tcpQueue <- req
} }
// AddPacket add udp Packet to queue // AddPacket add udp Packet to queue
func AddPacket(packet *inbound.PacketAdapter) { func AddPacket(packet *inbound.PacketAdapter) {
udpQueue.In() <- packet select {
case udpQueue <- packet:
default:
}
} }
// Rules return all rules // Rules return all rules
@@ -89,9 +90,8 @@ func SetMode(m TunnelMode) {
// processUDP starts a loop to handle udp packet // processUDP starts a loop to handle udp packet
func processUDP() { func processUDP() {
queue := udpQueue.Out() queue := udpQueue
for elm := range queue { for conn := range queue {
conn := elm.(*inbound.PacketAdapter)
handleUDPConn(conn) handleUDPConn(conn)
} }
} }
@@ -105,9 +105,8 @@ func process() {
go processUDP() go processUDP()
} }
queue := tcpQueue.Out() queue := tcpQueue
for elm := range queue { for conn := range queue {
conn := elm.(C.ServerAdapter)
go handleTCPConn(conn) go handleTCPConn(conn)
} }
} }
@@ -168,9 +167,9 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
return return
} }
// make a fAddr if requset ip is fakeip // make a fAddr if request ip is fakeip
var fAddr net.Addr var fAddr net.Addr
if resolver.IsFakeIP(metadata.DstIP) { if resolver.IsExistFakeIP(metadata.DstIP) {
fAddr = metadata.UDPAddr() fAddr = metadata.UDPAddr()
} }
@@ -180,57 +179,69 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
} }
key := packet.LocalAddr().String() key := packet.LocalAddr().String()
pc := natTable.Get(key)
if pc != nil { handle := func() bool {
handleUDPToRemote(packet, pc, metadata) pc := natTable.Get(key)
if pc != nil {
handleUDPToRemote(packet, pc, metadata)
return true
}
return false
}
if handle() {
return return
} }
lockKey := key + "-lock" lockKey := key + "-lock"
wg, loaded := natTable.GetOrCreateLock(lockKey) cond, loaded := natTable.GetOrCreateLock(lockKey)
go func() { go func() {
if !loaded { if loaded {
wg.Add(1) cond.L.Lock()
proxy, rule, err := resolveMetadata(metadata) cond.Wait()
if err != nil { handle()
log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) cond.L.Unlock()
natTable.Delete(lockKey) return
wg.Done() }
return
}
rawPc, err := proxy.DialUDP(metadata) defer func() {
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)
natTable.Delete(lockKey) natTable.Delete(lockKey)
wg.Done() cond.Broadcast()
go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) }()
proxy, rule, err := resolveMetadata(metadata)
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return
} }
wg.Wait() rawPc, err := proxy.DialUDP(metadata)
pc := natTable.Get(key) if err != nil {
if pc != nil { if rule == nil {
handleUDPToRemote(packet, pc, metadata) log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
} else {
log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), 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()
}() }()
} }
@@ -250,13 +261,17 @@ func handleTCPConn(localConn C.ServerAdapter) {
proxy, rule, err := resolveMetadata(metadata) proxy, rule, err := resolveMetadata(metadata)
if err != nil { if err != nil {
log.Warnln("Parse metadata failed: %v", err) log.Warnln("[Metadata] parse failed: %s", err.Error())
return return
} }
remoteConn, err := proxy.Dial(metadata) remoteConn, err := proxy.Dial(metadata)
if err != nil { if err != nil {
log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) if rule == nil {
log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
} else {
log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error())
}
return return
} }
remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule)