Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3600077f3b | ||
|
|
de7656a787 | ||
|
|
5dfe7f8561 | ||
|
|
ed27898a33 | ||
|
|
532396d25c | ||
|
|
4b1b494164 | ||
|
|
0d33dc3eb9 | ||
|
|
994cbff215 | ||
|
|
bea2ee8bf2 | ||
|
|
1e5593f1a9 | ||
|
|
34febc4579 | ||
|
|
97581148b5 | ||
|
|
0402878daa | ||
|
|
4735f61fd1 | ||
|
|
16ae107e70 | ||
|
|
83efe2ae57 | ||
|
|
87e4d94290 | ||
|
|
b98e9ea202 | ||
|
|
9a62b1081d | ||
|
|
2cd1b890ce | ||
|
|
ba060bd0ee | ||
|
|
b1795b1e3d | ||
|
|
76c9820065 | ||
|
|
2db4ce57ef | ||
|
|
50b3d497f6 | ||
|
|
2321e9139d | ||
|
|
baabf21340 | ||
|
|
d3bb4c65a8 | ||
|
|
8c3e2a7559 | ||
|
|
bc52f8e4fd |
62
.github/workflows/docker.yml
vendored
62
.github/workflows/docker.yml
vendored
@@ -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}}
|
||||||
|
|||||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
2
common/cache/lrucache.go
vendored
2
common/cache/lrucache.go
vendored
@@ -121,7 +121,7 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
|
|||||||
c.SetWithExpire(key, value, time.Unix(expires, 0))
|
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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
104
component/dialer/bind.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errPlatformNotSupport = errors.New("unsupport platform")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
||||||
|
ipv4 := ip.To4() != nil
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrV4 := addr.IP.To4() != nil
|
||||||
|
|
||||||
|
if addrV4 && ipv4 {
|
||||||
|
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
} else if !addrV4 && !ipv4 {
|
||||||
|
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrAddrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
||||||
|
ipv4 := ip.To4() != nil
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrV4 := addr.IP.To4() != nil
|
||||||
|
|
||||||
|
if addrV4 && ipv4 {
|
||||||
|
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
} else if !addrV4 && !ipv4 {
|
||||||
|
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrAddrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
||||||
|
dialer.LocalAddr = addr
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
||||||
|
dialer.LocalAddr = addr
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackBindToListenConfig(name string) (string, error) {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok || addr.IP.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.JoinHostPort(addr.IP.String(), "0"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrAddrNotFound
|
||||||
|
}
|
||||||
51
component/dialer/bind_darwin.go
Normal file
51
component/dialer/bind_darwin.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
|
func bindControl(ifaceIdx int) controlFn {
|
||||||
|
return func(network, address string, c syscall.RawConn) error {
|
||||||
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
|
if err == nil {
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.Control = bindControl(iface.Index)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lc.Control = bindControl(iface.Index)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
36
component/dialer/bind_linux.go
Normal file
36
component/dialer/bind_linux.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
|
func bindControl(ifaceName string) controlFn {
|
||||||
|
return func(network, address string, c syscall.RawConn) error {
|
||||||
|
ipStr, _, err := net.SplitHostPort(address)
|
||||||
|
if err == nil {
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip != nil && !ip.IsGlobalUnicast() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
syscall.BindToDevice(int(fd), ifaceName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||||
|
dialer.Control = bindControl(ifaceName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||||
|
lc.Control = bindControl(ifaceName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
13
component/dialer/bind_others.go
Normal file
13
component/dialer/bind_others.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build !linux,!darwin
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||||
|
return errPlatformNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||||
|
return errPlatformNotSupport
|
||||||
|
}
|
||||||
@@ -19,17 +19,6 @@ func Dialer() (*net.Dialer, error) {
|
|||||||
return dialer, nil
|
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) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
21
component/process/process.go
Normal file
21
component/process/process.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -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 {
|
||||||
@@ -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 {
|
||||||
10
component/process/process_other.go
Normal file
10
component/process/process_other.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
11
dns/util.go
11
dns/util.go
@@ -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
19
go.mod
@@ -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
48
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
71
proxy/redir/tproxy.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TProxyListener struct {
|
||||||
|
net.Listener
|
||||||
|
address string
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTProxy(addr string) (*TProxyListener, error) {
|
||||||
|
l, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tl := l.(*net.TCPListener)
|
||||||
|
rc, err := tl.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setsockopt(rc, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := &TProxyListener{
|
||||||
|
Listener: l,
|
||||||
|
address: addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Infoln("TProxy server listening at: %s", addr)
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if rl.closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go rl.handleRedir(c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return rl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *TProxyListener) Close() {
|
||||||
|
l.closed = true
|
||||||
|
l.Listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *TProxyListener) Address() string {
|
||||||
|
return l.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *TProxyListener) handleRedir(conn net.Conn) {
|
||||||
|
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||||
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
|
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))
|
||||||
|
}
|
||||||
40
proxy/redir/tproxy_linux.go
Normal file
40
proxy/redir/tproxy_linux.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setsockopt(rc syscall.RawConn, addr string) error {
|
||||||
|
isIPv6 := true
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip != nil && ip.To4() != nil {
|
||||||
|
isIPv6 = false
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.Control(func(fd uintptr) {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
|
}
|
||||||
|
if err == nil && isIPv6 {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||||
|
}
|
||||||
|
if err == nil && isIPv6 {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
12
proxy/redir/tproxy_other.go
Normal file
12
proxy/redir/tproxy_other.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setsockopt(rc syscall.RawConn, addr string) error {
|
||||||
|
return errors.New("not supported on current platform")
|
||||||
|
}
|
||||||
@@ -26,7 +26,12 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
|||||||
|
|
||||||
c := l.(*net.UDPConn)
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
65
rules/process.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
127
tunnel/tunnel.go
127
tunnel/tunnel.go
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user