Compare commits

...

42 Commits

Author SHA1 Message Date
2f6f9ebc2e Merge branch 'Dev' into Meta
# Conflicts:
#	config/config.go
2022-02-05 19:30:12 +08:00
90e6ed4612 [Fixed] Fixed clash process name is Clash.Meta 2022-02-04 17:38:06 +08:00
ae5a790510 [Fixed] Abnormal rule when host is ip addr 2022-02-04 17:38:06 +08:00
a732e1a603 Merge remote-tracking branch 'clash/dev' into Dev 2022-02-04 02:40:15 +08:00
b1a639feae Fix: domain trie search 2022-01-26 22:28:13 +08:00
76dccebbf6 github action build config 2022-01-26 21:35:18 +08:00
cd5b735973 [Refactor] logic rule parse 2022-01-26 21:34:49 +08:00
a5ce62db33 Merge branch 'clash-dev' into Dev 2022-01-25 15:05:24 +08:00
2f8e575308 [Fixed] modified RULE-SET supported rule 2022-01-23 18:35:48 +08:00
62b70725ef [Fixed] GEOSITE rule load fail 2022-01-23 18:27:44 +08:00
8595d6c2e9 [Feature]
1.Add Network rule, match network type(TCP/UDP)
2.Add logic rules(NOT,OR,AND)
-AND,((DOMAIN,baidu.com),(NETWORK,UDP)),REJECT

(cherry picked from commit d7092e2e37f2c48282c878edea1b2ebc2912b09a)
2022-01-22 22:37:07 +08:00
03b956b7a3 [Fixed] auto-route support use ip route 2022-01-22 13:24:31 +08:00
e5c99cbee7 modify gitignore 2022-01-21 22:39:00 +08:00
58a47e1835 [Style] clear unless notes 2022-01-21 22:38:28 +08:00
daf83eb6f7 [Fixed] select group crash 2022-01-21 22:38:02 +08:00
bb68b59c9a Merge pull request #7 from CHIZI-0618/DnsHijack
Fix DnsHijack default value bug.
2022-01-21 18:27:26 +08:00
c3cfa3d6cd Fix DnsHijack default value bug. 2022-01-21 18:11:21 +08:00
b15344ec78 [Refactor]
1.allow maybe empty group
2.use COMPATIBLE(DIRECT alias) when proxy group is empty
3.http provider pass through tunnel
2022-01-18 21:09:36 +08:00
cfe7354c07 Improve: change provider file modify time when updated (#1918) 2022-01-18 13:32:47 +08:00
9732efe938 Fix: tls handshake requires a timeout (#1893) 2022-01-15 19:33:21 +08:00
ee6c1871a9 [Refactor] lazy loading geosite.bat 2022-01-11 22:17:24 +08:00
8f3385bbb6 Feature: support snell v3 (#1884) 2022-01-10 20:24:20 +08:00
00e44cd141 [Style] Modify the default configuration, tun config delete default hijack dns and modify auto-route to false. modify NameServer to 223.5.5.5 and 119.29.29.29 by Skyxim 2022-01-09 00:36:05 +08:00
4ab986cccb [Refactor] gvisor support hijack dns list
dns-hijack:
 - 1.1.1.1
 - 8.8.8.8:53
 - tcp://1.1.1.1:53
 - udp://223.5.5.5
 - 10.0.0.1:5353
2022-01-09 00:35:45 +08:00
64869d0f17 [Fixed] Remove the Linux automatic routing configuration Change the name of the Linux network card to utun 2022-01-08 16:57:59 +08:00
7f0368da66 [Style] Adjust delete routes on macos 2022-01-08 16:55:02 +08:00
4f1b227ca2 [Style] Positive health check 2022-01-08 09:23:49 +08:00
16abba385a [Style] Adjust the routing table of tun on mac 2022-01-07 22:40:05 +08:00
75b5f633cd [Fixed] Positive health check multithreading is not safe 2022-01-07 12:58:40 +08:00
8ae68552a6 [Fixed] Stupid mistakes 2022-01-06 10:49:50 +08:00
d35d6c9ac9 [Fixed] Stupid mistakes 2022-01-06 10:49:26 +08:00
a832cfdb65 [Fixed] compatible cfw 2022-01-05 19:28:54 +08:00
951a5a0eb5 [update]readme 2022-01-05 18:45:32 +08:00
89609cc4a2 [update]readme 2022-01-05 17:04:56 +08:00
bfb976bbdc [test]Add name filter to proxy group 2022-01-05 12:19:49 +08:00
d237b041b3 Fix: ignore empty dns server error 2022-01-05 11:41:31 +08:00
a15d2535f1 升级版本号 2022-01-05 11:41:17 +08:00
610c79570a make tun config compatible with premium 2022-01-05 11:24:00 +08:00
051c81518c make tun config compatible with premium 2022-01-05 01:56:35 +08:00
0209efd423 Revert "make tun config compatible with premium"
This reverts commit ba6fdd2962.
2022-01-05 01:56:05 +08:00
ba6fdd2962 make tun config compatible with premium 2022-01-05 01:50:43 +08:00
9475799615 make tun config compatible with premium 2022-01-05 00:33:42 +08:00
70 changed files with 1756 additions and 526 deletions

44
.github/workflows/dev.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Dev
on: [push]
jobs:
dev-build:
if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }}
runs-on: ubuntu-latest
steps:
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Cache go module
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# - name: Get dependencies, run test
# run: |
# go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j releases
- name: Upload Dev
uses: softprops/action-gh-release@v1
if: ${{ env.GIT_BRANCH != 'Meta' && success() }}
with:
tag_name: develop
files: bin/*
prerelease: true

2
.gitignore vendored
View File

@ -23,3 +23,5 @@ vendor
# test suite # test suite
test/config/cache* test/config/cache*
/output
/.vscode

View File

@ -1,6 +1,10 @@
NAME=Clash.Meta NAME=Clash.Meta
BINDIR=bin BINDIR=bin
VERSION=$(shell git describe --tags || echo "unknown version") BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
VERSION=$(shell git describe --tags || echo "unknown version" )
ifeq ($(BRANCH),Dev)
VERSION=develop-$(shell git rev-parse --short HEAD)
endif
BUILDTIME=$(shell date -u) BUILDTIME=$(shell date -u)
AUTOIPTABLES=Enable AUTOIPTABLES=Enable
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
@ -15,18 +19,34 @@ GOBUILDOP=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/cl
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64 \ darwin-amd64 \
darwin-arm64 \ darwin-arm64 \
linux-arm64 \
linux-amd64 \ linux-amd64 \
linux-arm64-AutoIptables\ linux-armv5 \
linux-amd64-AutoIptables linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-mips64 \
linux-mips64le \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle-softfloat \
linux-mipsle-hardfloat \
freebsd-386 \
freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \ windows-386 \
windows-amd64 windows-amd64 \
windows-arm64 \
windows-arm32v7
all: linux-arm64-AutoIptables linux-amd64-AutoIptables linux-arm64 linux-amd64 darwin-amd64 darwin-arm64 windows-amd64 windows-386 # Most used all:linux-amd64-AutoIptables linux-amd64\
linux-arm64 linux-arm64-AutoIptables linux-armv7\
darwin-amd64 darwin-arm64\
windows-amd64 windows-386 \
linux-mips-hardfloat linux-mips-softfloat linux-mips64 linux-mips64le linux-mipsle-hardfloat linux-mipsle-softfloat# Most used
docker: docker:
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -109,6 +129,9 @@ windows-386:
windows-amd64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm32v7: windows-arm32v7:
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
@ -126,4 +149,4 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases) releases: $(gz_releases) $(zip_releases)
clean: clean:
rm $(BINDIR)/* rm $(BINDIR)/*

View File

@ -1,9 +1,9 @@
<h1 align="center"> <h1 align="center">
<img src="Meta.png" alt="Meta Kennel" width="200"> <img src="Meta.png" alt="Meta Kennel" width="200">
<br>Meta Kennel<br> <br>Meta Kernel<br>
</h1> </h1>
<h3 align="center">Another Clash Kennel.</h3> <h3 align="center">Another Clash Kernel.</h3>
<p align="center"> <p align="center">
<a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta"> <a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta">
@ -33,16 +33,32 @@
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).
## Advanced usage for this branch ## Advanced usage for this branch
### DNS configuration ### DNS configuration
Support resolve ip with a proxy tunnel.
Support `geosite` with `fallback-filter`. Support `geosite` with `fallback-filter`.
Restore `Redir remote resolution`.
Support resolve ip with a `Proxy Tunnel`.
```yaml
proxy-groups:
- name: DNS
type: url-test
use:
- HK
url: http://cp.cloudflare.com
interval: 180
lazy: true
```
```yaml ```yaml
dns: dns:
enable: true enable: true
use-hosts: true use-hosts: true
ipv6: false ipv6: false
enhanced-mode: fake-ip enhanced-mode: redir-host
fake-ip-range: 198.18.0.1/16 fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868 listen: 127.0.0.1:6868
default-nameserver: default-nameserver:
@ -52,8 +68,8 @@ dns:
- https://doh.pub/dns-query - https://doh.pub/dns-query
- tls://223.5.5.5:853 - tls://223.5.5.5:853
fallback: fallback:
- 'https://1.0.0.1/dns-query#Proxy' # append the proxy adapter name to the end of DNS URL with '#' prefix. - 'https://1.0.0.1/dns-query#DNS' # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
- 'tls://8.8.4.4:853#Proxy' - 'tls://8.8.4.4:853#DNS'
fallback-filter: fallback-filter:
geoip: false geoip: false
geosite: geosite:
@ -74,8 +90,9 @@ Built-in [Wintun](https://www.wintun.net) driver.
# Enable the TUN listener # Enable the TUN listener
tun: tun:
enable: true enable: true
stack: gvisor # system or gvisor stack: gvisor # only gvisor
dns-listen: 0.0.0.0:53 # additional dns server listen on TUN dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route auto-route: true # auto set global route
``` ```
### Rules configuration ### Rules configuration
@ -118,9 +135,44 @@ rules:
### Proxies configuration ### Proxies configuration
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
Support `Policy Group Filter`
```yaml
proxy-groups:
- name: 🚀 HK Group
type: select
use:
- ALL
filter: 'HK'
- name: 🚀 US Group
type: select
use:
- ALL
filter: 'US'
proxy-providers:
ALL:
type: http
url: "xxxxx"
interval: 3600
path: "xxxxx"
health-check:
enable: true
interval: 600
url: http://www.gstatic.com/generate_204
```
Support outbound transport protocol `VLESS`. Support outbound transport protocol `VLESS`.
The XTLS only support TCP transport by the XRAY-CORE. The XTLS support TCP/UDP by the XRAY-CORE.
```yaml ```yaml
proxies: proxies:
- name: "vless-tcp" - name: "vless-tcp"
@ -160,13 +212,13 @@ tun:
``` ```
Create user given name `Clash.Meta`. Create user given name `Clash.Meta`.
Run Meta Kennel by user `Clash.Meta` as a daemon. Run Meta Kernel by user `Clash.Meta` as a daemon.
Create the systemd configuration file at /etc/systemd/system/clash.service: Create the systemd configuration file at /etc/systemd/system/clash.service:
``` ```
[Unit] [Unit]
Description=Clash.Meta Daemon, Another Clash Kennel. Description=Clash.Meta Daemon, Another Clash Kernel.
After=network.target After=network.target
[Service] [Service]

View File

@ -20,3 +20,26 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
return context.NewConnContext(conn, metadata) return context.NewConnContext(conn, metadata)
} }
func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
metadata := &C.Metadata{}
metadata.NetWork = C.TCP
metadata.Type = C.INNER
metadata.DNSMode = C.DNSMapping
metadata.Host = host
metadata.AddrType = C.AtypDomainName
metadata.Process = C.ClashName
if ip, port, err := parseAddr(dst); err == nil {
metadata.DstPort = port
if host == "" {
metadata.DstIP = ip
if ip.To4() == nil {
metadata.AddrType = C.AtypIPv6
} else {
metadata.AddrType = C.AtypIPv4
}
}
}
return context.NewConnContext(conn, metadata)
}

View File

@ -44,3 +44,13 @@ func NewDirect() *Direct {
}, },
} }
} }
func NewCompatible() *Direct {
return &Direct{
Base: &Base{
name: "COMPATIBLE",
tp: C.Compatible,
udp: true,
},
}
}

View File

@ -27,6 +27,7 @@ type SnellOption struct {
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Psk string `proxy:"psk"` Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,omitempty"` Version int `proxy:"version,omitempty"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
} }
@ -85,6 +86,24 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return NewConn(c, s), err return NewConn(c, s), err
} }
// ListenPacketContext implements C.ProxyAdapter
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
if err != nil {
return nil, err
}
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
}
func NewSnell(option SnellOption) (*Snell, error) { func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk) psk := []byte(option.Psk)
@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == 0 { if option.Version == 0 {
option.Version = snell.DefaultSnellVersion option.Version = snell.DefaultSnellVersion
} }
if option.Version != snell.Version1 && option.Version != snell.Version2 { switch option.Version {
case snell.Version1, snell.Version2:
if option.UDP {
return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
}
case snell.Version3:
default:
return nil, fmt.Errorf("snell version error: %d", option.Version) return nil, fmt.Errorf("snell version error: %d", option.Version)
} }
@ -115,6 +140,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Snell, tp: C.Snell,
udp: option.UDP,
iface: option.Interface, iface: option.Interface,
}, },
psk: psk, psk: psk,

View File

@ -1,6 +1,8 @@
package outboundgroup package outboundgroup
import ( import (
"github.com/Dreamacro/clash/tunnel"
"regexp"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -11,7 +13,7 @@ const (
defaultGetProxiesDuration = time.Second * 5 defaultGetProxiesDuration = time.Second * 5
) )
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter string) []C.Proxy {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for _, provider := range providers { for _, provider := range providers {
if touch { if touch {
@ -20,5 +22,28 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Pro
proxies = append(proxies, provider.Proxies()...) proxies = append(proxies, provider.Proxies()...)
} }
} }
return proxies
var filterReg *regexp.Regexp = nil
matchedProxies := []C.Proxy{}
if len(filter) > 0 {
filterReg = regexp.MustCompile(filter)
for _, p := range proxies {
if filterReg.MatchString(p.Name()) {
matchedProxies = append(matchedProxies, p)
}
}
if len(matchedProxies) > 0 {
return matchedProxies
} else {
return append([]C.Proxy{}, tunnel.Proxies()["COMPATIBLE"])
}
} else {
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
} else {
return proxies
}
}
} }

View File

@ -17,6 +17,7 @@ import (
type Fallback struct { type Fallback struct {
*outbound.Base *outbound.Base
disableUDP bool disableUDP bool
filter string
single *singledo.Single single *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
failedTimes *atomic.Int32 failedTimes *atomic.Int32
@ -65,18 +66,20 @@ func (f *Fallback) onDialFailed() {
f.failedTime.Store(now) f.failedTime.Store(now)
f.failedTimes.Store(1) f.failedTimes.Store(1)
} else { } else {
if f.failedTime.Load()-time.Now().UnixMilli() > 5*1000 { if f.failedTime.Load()-time.Now().UnixMilli() > 5*time.Second.Milliseconds() {
f.failedTimes.Store(-1) f.failedTimes.Store(-1)
f.failedTime.Store(-1) f.failedTime.Store(-1)
} else { } else {
f.failedTimes.Inc() failedCount := f.failedTimes.Inc()
failedCount := f.failedTimes.Load()
log.Warnln("%s failed count: %d", f.Name(), failedCount) log.Warnln("%s failed count: %d", f.Name(), failedCount)
if failedCount > 5 { if failedCount >= 5 {
log.Debugln("%s failed multiple times.", f.Name()) log.Warnln("because %s failed multiple times, active health check", f.Name())
for _, proxyProvider := range f.providers { for _, proxyProvider := range f.providers {
go proxyProvider.HealthCheck() go proxyProvider.HealthCheck()
} }
f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} }
} }
} }
@ -94,10 +97,11 @@ func (f *Fallback) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string all := make([]string, 0)
for _, proxy := range f.proxies(false) { 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{}{
"type": f.Type().String(), "type": f.Type().String(),
"now": f.Now(), "now": f.Now(),
@ -113,7 +117,7 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
func (f *Fallback) proxies(touch bool) []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, touch), nil return getProvidersProxies(f.providers, touch, f.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@ -141,6 +145,7 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1), failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1), failedTime: atomic.NewInt64(-1),
} }

View File

@ -23,6 +23,7 @@ type LoadBalance struct {
*outbound.Base *outbound.Base
disableUDP bool disableUDP bool
single *singledo.Single single *singledo.Single
filter string
providers []provider.ProxyProvider providers []provider.ProxyProvider
strategyFn strategyFn strategyFn strategyFn
} }
@ -141,7 +142,7 @@ func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
func (lb *LoadBalance) proxies(touch bool) []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, touch), nil return getProvidersProxies(lb.providers, touch, lb.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@ -149,10 +150,12 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string all := make([]string, 0)
for _, proxy := range lb.proxies(false) { 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{}{
"type": lb.Type().String(), "type": lb.Type().String(),
"all": all, "all": all,
@ -180,5 +183,6 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
providers: providers, providers: providers,
strategyFn: strategyFn, strategyFn: strategyFn,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
}, nil }, nil
} }

View File

@ -29,6 +29,7 @@ type GroupCommonOption struct {
Interval int `group:"interval,omitempty"` Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"` Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
} }
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
@ -95,6 +96,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
return nil, err return nil, err
} }
providers = append(providers, list...) providers = append(providers, list...)
} else {
groupOption.Filter = ""
} }
var group C.ProxyAdapter var group C.ProxyAdapter

View File

@ -16,13 +16,14 @@ type Relay struct {
*outbound.Base *outbound.Base
single *singledo.Single single *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
filter string
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
var proxies []C.Proxy var proxies []C.Proxy
for _, proxy := range r.proxies(metadata, true) { for _, proxy := range r.proxies(metadata, true) {
if proxy.Type() != C.Direct { if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
proxies = append(proxies, proxy) proxies = append(proxies, proxy)
} }
} }
@ -68,10 +69,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string all := make([]string, 0)
for _, proxy := range r.rawProxies(false) { 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{}{
"type": r.Type().String(), "type": r.Type().String(),
"all": all, "all": all,
@ -80,7 +83,7 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
func (r *Relay) rawProxies(touch bool) []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, touch), nil return getProvidersProxies(r.providers, touch, r.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@ -110,5 +113,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
filter: option.Filter,
} }
} }

View File

@ -17,6 +17,7 @@ type Selector struct {
disableUDP bool disableUDP bool
single *singledo.Single single *singledo.Single
selected string selected string
filter string
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
@ -49,8 +50,9 @@ func (s *Selector) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string all := make([]string, 0)
for _, proxy := range getProvidersProxies(s.providers, false) {
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
@ -66,7 +68,7 @@ func (s *Selector) Now() string {
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
for _, proxy := range getProvidersProxies(s.providers, false) { for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
if proxy.Name() == name { if proxy.Name() == name {
s.selected = name s.selected = name
s.single.Reset() s.single.Reset()
@ -84,7 +86,7 @@ func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
func (s *Selector) selectedProxy(touch bool) 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, touch) proxies := getProvidersProxies(s.providers, touch, s.filter)
for _, proxy := range proxies { for _, proxy := range proxies {
if proxy.Name() == s.selected { if proxy.Name() == s.selected {
return proxy, nil return proxy, nil
@ -98,7 +100,6 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
} }
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0].Name()
return &Selector{ return &Selector{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
@ -108,7 +109,8 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
selected: selected, selected: "COMPATIBLE",
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
} }
} }

View File

@ -27,6 +27,7 @@ type URLTest struct {
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
fastNode C.Proxy fastNode C.Proxy
filter string
single *singledo.Single single *singledo.Single
fastSingle *singledo.Single fastSingle *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
@ -73,7 +74,7 @@ func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
func (u *URLTest) proxies(touch bool) []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, touch), nil return getProvidersProxies(u.providers, touch, u.filter), nil
}) })
return elm.([]C.Proxy) return elm.([]C.Proxy)
@ -124,10 +125,12 @@ func (u *URLTest) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string all := make([]string, 0)
for _, proxy := range u.proxies(false) { 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{}{
"type": u.Type().String(), "type": u.Type().String(),
"now": u.Now(), "now": u.Now(),
@ -146,14 +149,16 @@ func (u *URLTest) onDialFailed() {
u.failedTimes.Store(-1) u.failedTimes.Store(-1)
u.failedTime.Store(-1) u.failedTime.Store(-1)
} else { } else {
u.failedTimes.Inc() failedCount := u.failedTimes.Inc()
failedCount := u.failedTimes.Load()
log.Warnln("%s failed count: %d", u.Name(), failedCount) log.Warnln("%s failed count: %d", u.Name(), failedCount)
if failedCount > 5 { if failedCount >= 5 {
log.Debugln("%s failed multiple times.", u.Name()) log.Warnln("because %s failed multiple times, active health check", u.Name())
for _, proxyProvider := range u.providers { for _, proxyProvider := range u.providers {
go proxyProvider.HealthCheck() go proxyProvider.HealthCheck()
} }
u.failedTimes.Store(-1)
u.failedTime.Store(-1)
} }
} }
} }
@ -184,6 +189,7 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
fastSingle: singledo.NewSingle(time.Second * 10), fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers, providers: providers,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1), failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1), failedTime: atomic.NewInt64(-1),
} }

View File

@ -102,6 +102,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
hash := md5.Sum(buf) hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) { if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now f.updatedAt = &now
os.Chtimes(f.vehicle.Path(), now, now)
return nil, true, nil return nil, true, nil
} }

View File

@ -67,6 +67,10 @@ func (pp *proxySetProvider) Initial() error {
} }
pp.onUpdate(elm) pp.onUpdate(elm)
if pp.healthCheck.auto() {
go pp.healthCheck.process()
}
return nil return nil
} }
@ -102,10 +106,6 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
return nil, fmt.Errorf("invalid filter regex: %w", err) return nil, fmt.Errorf("invalid filter regex: %w", err)
} }
if hc.auto() {
go hc.process()
}
pd := &proxySetProvider{ pd := &proxySetProvider{
proxies: []C.Proxy{}, proxies: []C.Proxy{},
healthCheck: hc, healthCheck: hc,
@ -190,6 +190,10 @@ func (cp *compatibleProvider) Update() error {
} }
func (cp *compatibleProvider) Initial() error { func (cp *compatibleProvider) Initial() error {
if cp.healthCheck.auto() {
go cp.healthCheck.process()
}
return nil return nil
} }
@ -219,10 +223,6 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
return nil, errors.New("provider need one proxy at least") return nil, errors.New("provider need one proxy at least")
} }
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{ pd := &compatibleProvider{
name: name, name: name,
proxies: proxies, proxies: proxies,

View File

@ -2,6 +2,7 @@ package provider
import ( import (
"context" "context"
"github.com/Dreamacro/clash/listener/inner"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -10,7 +11,6 @@ import (
"time" "time"
netHttp "github.com/Dreamacro/clash/common/net" netHttp "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
) )
@ -77,7 +77,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address) conn := inner.HandleTcp(address, uri.Hostname())
return conn, nil
}, },
} }

View File

@ -0,0 +1,56 @@
package collections
import "sync"
type (
stack struct {
top *node
length int
lock *sync.RWMutex
}
node struct {
value interface{}
prev *node
}
)
// NewStack Create a new stack
func NewStack() *stack {
return &stack{nil, 0, &sync.RWMutex{}}
}
// Len Return the number of items in the stack
func (this *stack) Len() int {
return this.length
}
// Peek View the top item on the stack
func (this *stack) Peek() interface{} {
if this.length == 0 {
return nil
}
return this.top.value
}
// Pop the top item of the stack and return it
func (this *stack) Pop() interface{} {
this.lock.Lock()
defer this.lock.Unlock()
if this.length == 0 {
return nil
}
n := this.top
this.top = n.prev
this.length--
return n.value
}
// Push a value onto the top of the stack
func (this *stack) Push(value interface{}) {
this.lock.Lock()
defer this.lock.Unlock()
n := &node{value, this.top}
this.top = n
this.length++
}

46
common/net/tcpip.go Normal file
View File

@ -0,0 +1,46 @@
package net
import (
"fmt"
"net"
"strings"
)
func SplitNetworkType(s string) (string, string, error) {
var (
shecme string
hostPort string
)
result := strings.Split(s, "://")
if len(result) == 2 {
shecme = result[0]
hostPort = result[1]
} else if len(result) == 1 {
hostPort = result[0]
} else {
return "", "", fmt.Errorf("tcp/udp style error")
}
if len(shecme) == 0 {
shecme = "udp"
}
if shecme != "tcp" && shecme != "udp" {
return "", "", fmt.Errorf("scheme should be tcp:// or udp://")
} else {
return shecme, hostPort, nil
}
}
func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
temp := s
hasPort = true
if !strings.Contains(s, ":") && !strings.Contains(s, "]:") {
temp += ":0"
hasPort = false
}
host, port, err = net.SplitHostPort(temp)
return
}

View File

@ -109,13 +109,13 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
} }
if c := node.getChild(parts[len(parts)-1]); c != nil { if c := node.getChild(parts[len(parts)-1]); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil { if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
return n return n
} }
} }
if c := node.getChild(wildcard); c != nil { if c := node.getChild(wildcard); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil { if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
return n return n
} }
} }

View File

@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) {
assert.NotNil(t, tree.Insert("..dev", localIP)) assert.NotNil(t, tree.Insert("..dev", localIP))
assert.Nil(t, tree.Search("dev")) assert.Nil(t, tree.Search("dev"))
} }
func TestTrie_WildcardBoundary(t *testing.T) {
tree := New()
tree.Insert("+.*", localIP)
tree.Insert("stun.*.*.*", localIP)
assert.NotNil(t, tree.Search("example.com"))
}

View File

@ -2,8 +2,8 @@ package trie
// Node is the trie's node // Node is the trie's node
type Node struct { type Node struct {
Data interface{}
children map[string]*Node children map[string]*Node
Data interface{}
} }
func (n *Node) getChild(s string) *Node { func (n *Node) getChild(s string) *Node {

View File

@ -4,6 +4,8 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"net" "net"
"net/url" "net/url"
"os" "os"
@ -24,7 +26,6 @@ import (
providerTypes "github.com/Dreamacro/clash/constant/provider" providerTypes "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -48,7 +49,6 @@ type Inbound struct {
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"` TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"` MixedPort int `json:"mixed-port"`
Tun Tun `json:"tun"`
Authentication []string `json:"authentication"` Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"` BindAddress string `json:"bind-address"`
@ -99,10 +99,10 @@ type Profile struct {
// Tun config // Tun config
type Tun struct { type Tun struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Stack string `yaml:"stack" json:"stack"` Stack string `yaml:"stack" json:"stack"`
DNSListen string `yaml:"dns-listen" json:"dns-listen"` DnsHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoRoute bool `yaml:"auto-route" json:"auto-route"`
} }
// Script config // Script config
@ -209,8 +209,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Tun: Tun{ Tun: Tun{
Enable: false, Enable: false,
Stack: "gvisor", Stack: "gvisor",
DNSListen: "0.0.0.0:53", DnsHijack: []string{"198.18.0.2:53"},
AutoRoute: true, AutoRoute: false,
}, },
DNS: RawDNS{ DNS: RawDNS{
Enable: false, Enable: false,
@ -225,6 +225,17 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
DefaultNameserver: []string{ DefaultNameserver: []string{
"114.114.114.114", "114.114.114.114",
"223.5.5.5", "223.5.5.5",
"8.8.8.8",
"1.0.0.1",
},
NameServer: []string{
"223.5.5.5",
"119.29.29",
},
FakeIPFilter: []string{
"dns.msftnsci.com",
"www.msftnsci.com",
"www.msftconnecttest.com",
}, },
}, },
Profile: Profile{ Profile: Profile{
@ -254,7 +265,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
return nil, err return nil, err
} }
config.General = general config.General = general
//TODO 暂未使用
config.Tun = &rawCfg.Tun config.Tun = &rawCfg.Tun
proxies, providers, err := parseProxies(rawCfg) proxies, providers, err := parseProxies(rawCfg)
@ -312,7 +323,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
RedirPort: cfg.RedirPort, RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort, TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort, MixedPort: cfg.MixedPort,
Tun: cfg.Tun,
AllowLan: cfg.AllowLan, AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress, BindAddress: cfg.BindAddress,
}, },
@ -332,7 +342,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) { func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
proxies = make(map[string]C.Proxy) proxies = make(map[string]C.Proxy)
providersMap = make(map[string]providerTypes.ProxyProvider) providersMap = make(map[string]providerTypes.ProxyProvider)
proxyList := []string{} var proxyList []string
_proxiesList := list.New() _proxiesList := list.New()
_groupsList := list.New() _groupsList := list.New()
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
@ -341,6 +351,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
proxyList = append(proxyList, "DIRECT", "REJECT") proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy // parse proxy
@ -387,13 +398,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
providersMap[name] = pd providersMap[name] = pd
} }
for _, rp := range providersMap {
log.Infoln("Start initial provider %s", rp.Name())
if err := rp.Initial(); err != nil {
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", rp.Name(), err)
}
}
// parse proxy group // parse proxy group
for idx, mapping := range groupsConfig { for idx, mapping := range groupsConfig {
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
@ -409,19 +413,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
proxies[groupName] = adapter.NewProxy(group) proxies[groupName] = adapter.NewProxy(group)
} }
// initial compatible provider var ps []C.Proxy
for _, pd := range providersMap {
if pd.VehicleType() != providerTypes.Compatible {
continue
}
log.Infoln("Start initial compatible provider %s", pd.Name())
if err := pd.Initial(); err != nil {
return nil, nil, err
}
}
ps := []C.Proxy{}
for _, v := range proxyList { for _, v := range proxyList {
ps = append(ps, proxies[v]) ps = append(ps, proxies[v])
} }
@ -493,23 +485,16 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
// parse rule provider // parse rule provider
for name, mapping := range cfg.RuleProvider { for name, mapping := range cfg.RuleProvider {
rp, err := R.ParseRuleProvider(name, mapping) rp, err := RP.ParseRuleProvider(name, mapping)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
ruleProviders[name] = &rp ruleProviders[name] = &rp
R.SetRuleProvider(rp) RP.SetRuleProvider(rp)
} }
for _, provider := range ruleProviders { var rules []C.Rule
log.Infoln("Start initial provider %s", (*provider).Name())
if err := (*provider).Initial(); err != nil {
return nil, nil, fmt.Errorf("initial rule provider %s error: %w", (*provider).Name(), err)
}
}
rules := []C.Rule{}
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
mode := cfg.Mode mode := cfg.Mode
@ -519,7 +504,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
var ( var (
payload string payload string
target string target string
params = []string{} params []string
ruleName = strings.ToUpper(rule[0]) ruleName = strings.ToUpper(rule[0])
) )
@ -527,24 +512,29 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
continue continue
} }
switch l := len(rule); { if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
case l == 2: payload = strings.Join(rule[1:len(rule)-1], ",")
target = rule[1] target = rule[len(rule)-1]
case l == 3: } else {
if ruleName == "MATCH" { switch l := len(rule); {
payload = "" case l == 2:
target = rule[1] target = rule[1]
params = rule[2:] case l == 3:
break if ruleName == "MATCH" {
payload = ""
target = rule[1]
params = rule[2:]
break
}
payload = rule[1]
target = rule[2]
case l >= 4:
payload = rule[1]
target = rule[2]
params = rule[3:]
default:
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
} }
payload = rule[1]
target = rule[2]
case l >= 4:
payload = rule[1]
target = rule[2]
params = rule[3:]
default:
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
} }
if _, ok := proxies[target]; mode != T.Script && !ok { if _, ok := proxies[target]; mode != T.Script && !ok {
@ -553,6 +543,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
params = trimArr(params) params = trimArr(params)
if ruleName == "GEOSITE" {
if err := initGeoSite(); err != nil {
return nil, nil, fmt.Errorf("can't initial GeoSite: %w", err)
}
}
parsed, parseErr := R.ParseRule(ruleName, payload, target, params) parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
if parseErr != nil { if parseErr != nil {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
@ -607,7 +602,7 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
} }
func parseNameServer(servers []string) ([]dns.NameServer, error) { func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers := []dns.NameServer{} var nameservers []dns.NameServer
for idx, server := range servers { for idx, server := range servers {
// parse without scheme .e.g 8.8.8.8:53 // parse without scheme .e.g 8.8.8.8:53
@ -675,7 +670,7 @@ func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServe
} }
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
ipNets := []*net.IPNet{} var ipNets []*net.IPNet
for idx, ip := range ips { for idx, ip := range ips {
_, ipnet, err := net.ParseCIDR(ip) _, ipnet, err := net.ParseCIDR(ip)
@ -689,7 +684,12 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
} }
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) { func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
sites := []*router.DomainMatcher{} var sites []*router.DomainMatcher
if len(countries) > 0 {
if err := initGeoSite(); err != nil {
return nil, fmt.Errorf("can't initial GeoSite: %w", err)
}
}
for _, country := range countries { for _, country := range countries {
found := false found := false
@ -784,7 +784,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
if net.ParseIP(fb.Addr) != nil { if net.ParseIP(fb.Addr) != nil {
continue continue
} }
host.Insert(fb.Addr, true) _ = host.Insert(fb.Addr, true)
} }
} }

View File

@ -50,23 +50,6 @@ func initMMDB() error {
return nil return nil
} }
//func downloadGeoIP(path string) (err error) {
// resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat")
// if err != nil {
// return
// }
// defer resp.Body.Close()
//
// f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
// if err != nil {
// return err
// }
// defer f.Close()
// _, err = io.Copy(f, resp.Body)
//
// return err
//}
func downloadGeoSite(path string) (err error) { func downloadGeoSite(path string) (err error) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat") resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
if err != nil { if err != nil {
@ -84,22 +67,9 @@ func downloadGeoSite(path string) (err error) {
return err return err
} }
//
//func initGeoIP() error {
// if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
// log.Infoln("Can't find GeoIP.dat, start download")
// if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
// return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
// }
// log.Infoln("Download GeoIP.dat finish")
// }
//
// return nil
//}
func initGeoSite() error { func initGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Can't find GeoSite.dat, start download") log.Infoln("Need GeoSite but can't find GeoSite.dat, start download")
if err := downloadGeoSite(C.Path.GeoSite()); err != nil { if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
} }
@ -139,9 +109,5 @@ func Init(dir string) error {
return fmt.Errorf("can't initial MMDB: %w", err) return fmt.Errorf("can't initial MMDB: %w", err)
} }
// initial GeoSite
if err := initGeoSite(); err != nil {
return fmt.Errorf("can't initial GeoSite: %w", err)
}
return nil return nil
} }

View File

@ -13,7 +13,7 @@ import (
const ( const (
Direct AdapterType = iota Direct AdapterType = iota
Reject Reject
Compatible
Shadowsocks Shadowsocks
ShadowsocksR ShadowsocksR
Snell Snell
@ -33,6 +33,7 @@ const (
const ( const (
DefaultTCPTimeout = 5 * time.Second DefaultTCPTimeout = 5 * time.Second
DefaultUDPTimeout = DefaultTCPTimeout DefaultUDPTimeout = DefaultTCPTimeout
DefaultTLSTimeout = DefaultTCPTimeout
) )
type Connection interface { type Connection interface {
@ -128,7 +129,8 @@ func (at AdapterType) String() string {
return "Direct" return "Direct"
case Reject: case Reject:
return "Reject" return "Reject"
case Compatible:
return "Compatible"
case Shadowsocks: case Shadowsocks:
return "Shadowsocks" return "Shadowsocks"
case ShadowsocksR: case ShadowsocksR:

View File

@ -2,6 +2,7 @@ package constant
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"strconv" "strconv"
) )
@ -23,6 +24,7 @@ const (
REDIR REDIR
TPROXY TPROXY
TUN TUN
INNER
) )
type NetWork int type NetWork int
@ -58,6 +60,8 @@ func (t Type) String() string {
return "TProxy" return "TProxy"
case TUN: case TUN:
return "Tun" return "Tun"
case INNER:
return "Inner"
default: default:
return "Unknown" return "Unknown"
} }
@ -89,6 +93,18 @@ func (m *Metadata) SourceAddress() string {
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort) return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
} }
func (m *Metadata) SourceDetail() string {
if m.Process != "" {
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
} else {
if m.Type == INNER {
return fmt.Sprintf("[Clash]")
}
return fmt.Sprintf("%s", m.SourceAddress())
}
}
func (m *Metadata) Resolved() bool { func (m *Metadata) Resolved() bool {
return m.DstIP != nil return m.DstIP != nil
} }

View File

@ -14,7 +14,12 @@ const (
Process Process
Script Script
RuleSet RuleSet
Network
Combination
MATCH MATCH
AND
OR
NOT
) )
type RuleType int type RuleType int
@ -47,6 +52,14 @@ func (rt RuleType) String() string {
return "Match" return "Match"
case RuleSet: case RuleSet:
return "RuleSet" return "RuleSet"
case Network:
return "Network"
case AND:
return "AND"
case OR:
return "OR"
case NOT:
return "NOT"
default: default:
return "Unknown" return "Unknown"
} }

View File

@ -2,7 +2,8 @@ package constant
var ( var (
Meta = true Meta = true
Version = "1.8.0" Version = "1.9.0"
BuildTime = "unknown time" BuildTime = "unknown time"
AutoIptables string AutoIptables string
ClashName = "Clash.Meta"
) )

View File

@ -60,6 +60,10 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
address = "" address = ""
} }
if addr == "" {
return
}
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {

View File

@ -2,7 +2,6 @@ package executor
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/listener/tproxy"
"net" "net"
"os" "os"
"runtime" "runtime"
@ -10,6 +9,8 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/Dreamacro/clash/listener/tproxy"
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
@ -74,14 +75,17 @@ func ApplyConfig(cfg *config.Config, force bool) {
defer mux.Unlock() defer mux.Unlock()
updateUsers(cfg.Users) updateUsers(cfg.Users)
updateHosts(cfg.Hosts)
updateProxies(cfg.Proxies, cfg.Providers) updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules, cfg.RuleProviders) updateRules(cfg.Rules, cfg.RuleProviders)
updateHosts(cfg.Hosts) updateIPTables(cfg.DNS, cfg.General, cfg.Tun)
updateProfile(cfg) updateDNS(cfg.DNS, cfg.Tun)
updateIPTables(cfg.DNS, cfg.General) updateGeneral(cfg.General, cfg.Tun, force)
updateDNS(cfg.DNS, cfg.General) updateTun(cfg.Tun)
updateGeneral(cfg.General, force)
updateExperimental(cfg) updateExperimental(cfg)
loadProvider(cfg.RuleProviders, cfg.Providers)
updateProfile(cfg)
} }
func GetGeneral() *config.General { func GetGeneral() *config.General {
@ -98,7 +102,6 @@ func GetGeneral() *config.General {
RedirPort: ports.RedirPort, RedirPort: ports.RedirPort,
TProxyPort: ports.TProxyPort, TProxyPort: ports.TProxyPort,
MixedPort: ports.MixedPort, MixedPort: ports.MixedPort,
Tun: P.Tun(),
Authentication: authenticator, Authentication: authenticator,
AllowLan: P.AllowLan(), AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(), BindAddress: P.BindAddress(),
@ -113,8 +116,8 @@ func GetGeneral() *config.General {
func updateExperimental(c *config.Config) {} func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS, general *config.General) { func updateDNS(c *config.DNS, Tun *config.Tun) {
if !c.Enable && !general.Tun.Enable { if !c.Enable && !Tun.Enable {
resolver.DefaultResolver = nil resolver.DefaultResolver = nil
resolver.MainResolver = nil resolver.MainResolver = nil
resolver.DefaultHostMapper = nil resolver.DefaultHostMapper = nil
@ -152,7 +155,7 @@ func updateDNS(c *config.DNS, general *config.General) {
resolver.DefaultResolver = r resolver.DefaultResolver = r
resolver.MainResolver = mr resolver.MainResolver = mr
resolver.DefaultHostMapper = m resolver.DefaultHostMapper = m
if general.Tun.Enable && !strings.EqualFold(general.Tun.Stack, "gvisor") { if Tun.Enable && !strings.EqualFold(Tun.Stack, "gVisor") {
resolver.DefaultLocalServer = dns.NewLocalServer(r, m) resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
} else { } else {
resolver.DefaultLocalServer = nil resolver.DefaultLocalServer = nil
@ -175,12 +178,44 @@ func updateRules(rules []C.Rule, ruleProviders map[string]*provider.RuleProvider
tunnel.UpdateRules(rules, ruleProviders) tunnel.UpdateRules(rules, ruleProviders)
} }
func updateGeneral(general *config.General, force bool) { func loadProvider(ruleProviders map[string]*provider.RuleProvider, proxyProviders map[string]provider.ProxyProvider) {
load := func(pv provider.Provider) {
if pv.VehicleType() == provider.Compatible {
log.Infoln("Start initial compatible provider %s", pv.Name())
} else {
log.Infoln("Start initial provider %s", (pv).Name())
}
if err := (pv).Initial(); err != nil {
switch pv.Type() {
case provider.Proxy:
{
log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err)
}
case provider.Rule:
{
log.Warnln("initial rule provider %s error: %v", (pv).Name(), err)
}
}
}
}
for _, proxyProvider := range proxyProviders {
load(proxyProvider)
}
for _, ruleProvider := range ruleProviders {
load(*ruleProvider)
}
}
func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
adapter.UnifiedDelay.Store(general.UnifiedDelay) adapter.UnifiedDelay.Store(general.UnifiedDelay)
if (general.Tun.Enable || general.TProxyPort != 0) && general.Interface == "" { if (Tun.Enable || general.TProxyPort != 0) && general.Interface == "" {
autoDetectInterfaceName, err := dev.GetAutoDetectInterface() autoDetectInterfaceName, err := dev.GetAutoDetectInterface()
if err == nil { if err == nil {
if autoDetectInterfaceName != "" && autoDetectInterfaceName != "<nil>" { if autoDetectInterfaceName != "" && autoDetectInterfaceName != "<nil>" {
@ -219,12 +254,21 @@ func updateGeneral(general *config.General, force bool) {
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn) P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn) P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
if err := P.ReCreateTun(general.Tun, tcpIn, udpIn); err != nil { log.SetLevel(general.LogLevel)
}
func updateTun(Tun *config.Tun) {
if Tun == nil {
return
}
tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn()
if err := P.ReCreateTun(*Tun, tcpIn, udpIn); err != nil {
log.Errorln("Start Tun interface error: %s", err.Error()) log.Errorln("Start Tun interface error: %s", err.Error())
os.Exit(2) os.Exit(2)
} }
log.SetLevel(general.LogLevel)
} }
func updateUsers(users []auth.AuthUser) { func updateUsers(users []auth.AuthUser) {
@ -270,9 +314,9 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
} }
} }
func updateIPTables(dns *config.DNS, general *config.General) { func updateIPTables(dns *config.DNS, general *config.General, tun *config.Tun) {
AutoIptables := C.AutoIptables AutoIptables := C.AutoIptables
if runtime.GOOS != "linux" || dns.Listen == "" || general.TProxyPort == 0 || general.Tun.Enable || AutoIptables != "Enable" { if runtime.GOOS != "linux" || dns.Listen == "" || general.TProxyPort == 0 || tun.Enable || AutoIptables != "Enable" {
return return
} }

View File

@ -23,7 +23,6 @@ type Rule struct {
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := tunnel.Rules() rawRules := tunnel.Rules()
rules := []Rule{} rules := []Rule{}
for _, rule := range rawRules { for _, rule := range rawRules {
rules = append(rules, Rule{ rules = append(rules, Rule{
@ -31,6 +30,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
Payload: rule.Payload(), Payload: rule.Payload(),
Proxy: rule.Adapter(), Proxy: rule.Adapter(),
}) })
} }
render.JSON(w, r, render.M{ render.JSON(w, r, render.M{

16
hub/route/script.go Normal file
View File

@ -0,0 +1,16 @@
package route
import (
"github.com/go-chi/chi/v5"
"net/http"
)
func scriptRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getScript)
return r
}
func getScript(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusMethodNotAllowed)
}

View File

@ -72,6 +72,7 @@ func Start(addr string, secret string) {
r.Mount("/connections", connectionRouter()) r.Mount("/connections", connectionRouter())
r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/script", scriptRouter())
}) })
if uiPath != "" { if uiPath != "" {

20
listener/inner/tcp.go Normal file
View File

@ -0,0 +1,20 @@
package inner
import (
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"net"
)
var tcpIn chan<- C.ConnContext
func New(in chan<- C.ConnContext) {
tcpIn = in
}
func HandleTcp(dst string, host string) net.Conn {
conn1, conn2 := net.Pipe()
context := inbound.NewInner(conn2, dst, host)
tcpIn <- context
return conn1
}

View File

@ -2,6 +2,7 @@ package proxy
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/listener/inner"
"net" "net"
"runtime" "runtime"
"strconv" "strconv"
@ -71,7 +72,7 @@ func Tun() config.Tun {
return config.Tun{ return config.Tun{
Enable: true, Enable: true,
Stack: tunAdapter.Stack(), Stack: tunAdapter.Stack(),
DNSListen: tunAdapter.DNSListen(), DnsHijack: tunAdapter.DnsHijack(),
AutoRoute: tunAdapter.AutoRoute(), AutoRoute: tunAdapter.AutoRoute(),
} }
} }
@ -124,6 +125,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
log.Errorln("Start SOCKS server error: %s", err.Error()) log.Errorln("Start SOCKS server error: %s", err.Error())
} }
}() }()
inner.New(tcpIn)
addr := genAddr(bindAddress, port, allowLan) addr := genAddr(bindAddress, port, allowLan)

View File

@ -20,7 +20,7 @@ var (
const ( const (
PROXY_FWMARK = "0x2d0" PROXY_FWMARK = "0x2d0"
PROXY_ROUTE_TABLE = "0x2d0" PROXY_ROUTE_TABLE = "0x2d0"
USERNAME = "root" USERNAME = "Clash.Meta"
) )
func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error { func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {

View File

@ -1,13 +1,5 @@
package dev package dev
import (
"bytes"
"os/exec"
"runtime"
"github.com/Dreamacro/clash/log"
)
// TunDevice is cross-platform tun interface // TunDevice is cross-platform tun interface
type TunDevice interface { type TunDevice interface {
Name() string Name() string
@ -18,54 +10,3 @@ type TunDevice interface {
Read(buff []byte) (int, error) Read(buff []byte) (int, error)
Write(buff []byte) (int, error) Write(buff []byte) (int, error)
} }
func SetLinuxAutoRoute() {
log.Infoln("Tun adapter auto setting global route")
addLinuxSystemRoute("0")
//addLinuxSystemRoute("1")
//addLinuxSystemRoute("2/7")
//addLinuxSystemRoute("4/6")
//addLinuxSystemRoute("8/5")
//addLinuxSystemRoute("16/4")
//addLinuxSystemRoute("32/3")
//addLinuxSystemRoute("64/2")
//addLinuxSystemRoute("128.0/1")
//addLinuxSystemRoute("198.18.0/16")
}
func RemoveLinuxAutoRoute() {
log.Infoln("Tun adapter removing global route")
delLinuxSystemRoute("0")
//delLinuxSystemRoute("1")
//delLinuxSystemRoute("2/7")
//delLinuxSystemRoute("4/6")
//delLinuxSystemRoute("8/5")
//delLinuxSystemRoute("16/4")
//delLinuxSystemRoute("32/3")
//delLinuxSystemRoute("64/2")
//delLinuxSystemRoute("128.0/1")
//delLinuxSystemRoute("198.18.0/16")
}
func addLinuxSystemRoute(net string) {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
return
}
cmd := exec.Command("route", "add", "-net", net, "meta")
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
}
}
func delLinuxSystemRoute(net string) {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
return
}
cmd := exec.Command("route", "delete", "-net", net, "meta")
_ = cmd.Run()
//if err := cmd.Run(); err != nil {
// log.Errorln("[auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String())
//}
}

View File

@ -5,11 +5,13 @@ package dev
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/log"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strings"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
@ -176,7 +178,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
} }
if autoRoute { if autoRoute {
SetLinuxAutoRoute() setAutoRoute(tunAddress)
} }
return tun, nil return tun, nil
@ -280,7 +282,7 @@ func (t *tunDarwin) IsClose() bool {
func (t *tunDarwin) Close() error { func (t *tunDarwin) Close() error {
t.stopOnce.Do(func() { t.stopOnce.Do(func() {
if t.autoRoute { if t.autoRoute {
RemoveLinuxAutoRoute() resetAutoRoute(t.tunAddress)
} }
t.closed = true t.closed = true
t.tunFile.Close() t.tunFile.Close()
@ -480,15 +482,60 @@ func (t *tunDarwin) attachLinkLocal() error {
// GetAutoDetectInterface get ethernet interface // GetAutoDetectInterface get ethernet interface
func GetAutoDetectInterface() (string, error) { func GetAutoDetectInterface() (string, error) {
cmd := exec.Command("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n") cmd := exec.Command("route", "-n", "get", "default")
var out bytes.Buffer if result, err := cmd.Output(); err != nil {
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err return "", err
} else {
resultString := string(result)
reg, err := regexp.Compile("(interface:)(.*)")
if err != nil {
return "", err
}
matchResult := reg.FindStringSubmatch(resultString)
interfaceName := strings.TrimSpace(matchResult[len(matchResult)-1])
return interfaceName, nil
}
}
func setAutoRoute(tunGateway string) {
addRoute("1", tunGateway)
addRoute("2/7", tunGateway)
addRoute("4/6", tunGateway)
addRoute("8/5", tunGateway)
addRoute("16/4", tunGateway)
addRoute("32/3", tunGateway)
addRoute("64/2", tunGateway)
addRoute("128.0/1", tunGateway)
addRoute("198.18.0/16", tunGateway)
}
func resetAutoRoute(tunGateway string) {
delRoute("1", tunGateway)
delRoute("2/7", tunGateway)
delRoute("4/6", tunGateway)
delRoute("8/5", tunGateway)
delRoute("16/4", tunGateway)
delRoute("32/3", tunGateway)
delRoute("64/2", tunGateway)
delRoute("128.0/1", tunGateway)
delRoute("198.18.0/16", tunGateway)
}
func addRoute(net, name string) {
cmd := exec.Command("route", "add", "-net", net, name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
}
}
func delRoute(net, name string) {
cmd := exec.Command("route", "delete", "-net", net, name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
} }
if out.Len() == 0 {
return "", errors.New("interface not found by default route")
}
return out.String(), nil
} }

View File

@ -7,6 +7,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/log"
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
@ -38,7 +39,7 @@ type tunLinux struct {
// OpenTunDevice return a TunDevice according a URL // OpenTunDevice return a TunDevice according a URL
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
deviceURL, _ := url.Parse("dev://meta") deviceURL, _ := url.Parse("dev://utun")
mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32) mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32)
t := &tunLinux{ t := &tunLinux{
@ -62,8 +63,9 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
} }
if autoRoute { if autoRoute {
SetLinuxAutoRoute() addRoute(tunAddress)
} }
return dev, nil return dev, nil
case "fd": case "fd":
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32) fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
@ -76,7 +78,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
return nil, err return nil, err
} }
if autoRoute { if autoRoute {
SetLinuxAutoRoute() log.Warnln("linux unsupported automatic route")
} }
return dev, nil return dev, nil
} }
@ -105,9 +107,6 @@ func (t *tunLinux) IsClose() bool {
func (t *tunLinux) Close() error { func (t *tunLinux) Close() error {
t.stopOnce.Do(func() { t.stopOnce.Do(func() {
if t.autoRoute {
RemoveLinuxAutoRoute()
}
t.closed = true t.closed = true
t.tunFile.Close() t.tunFile.Close()
}) })
@ -330,3 +329,21 @@ func GetAutoDetectInterface() (string, error) {
} }
return out.String(), nil return out.String(), nil
} }
func addRoute(gateway string) {
cmd := exec.Command("route", "add", "default", "gw", gateway)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
}
}
func delRoute(gateway string) {
cmd := exec.Command("ip", "route", "delete", "gw")
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
}
}

View File

@ -36,7 +36,7 @@ const nicID tcpip.NICID = 1
type gvisorAdapter struct { type gvisorAdapter struct {
device dev.TunDevice device dev.TunDevice
ipstack *stack.Stack ipstack *stack.Stack
dnsserver *DNSServer dnsServer *DNSServer
udpIn chan<- *inbound.PacketAdapter udpIn chan<- *inbound.PacketAdapter
stackName string stackName string
@ -47,7 +47,7 @@ type gvisorAdapter struct {
writeHandle *channel.NotificationHandle writeHandle *channel.NotificationHandle
} }
// GvisorAdapter create GvisorAdapter // NewAdapter GvisorAdapter create GvisorAdapter
func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) { func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
ipstack := stack.New(stack.Options{ ipstack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
@ -113,7 +113,7 @@ func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContex
ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, adapter.udpHandlePacket) ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, adapter.udpHandlePacket)
if resolver.DefaultResolver != nil { if resolver.DefaultResolver != nil {
err = adapter.ReCreateDNSServer(resolver.DefaultResolver.(*dns.Resolver), resolver.DefaultHostMapper.(*dns.ResolverEnhancer), conf.DNSListen) err = adapter.ReCreateDNSServer(resolver.DefaultResolver.(*dns.Resolver), resolver.DefaultHostMapper.(*dns.ResolverEnhancer), conf.DnsHijack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,9 +132,7 @@ func (t *gvisorAdapter) AutoRoute() bool {
// Close close the TunAdapter // Close close the TunAdapter
func (t *gvisorAdapter) Close() { func (t *gvisorAdapter) Close() {
if t.dnsserver != nil { t.StopDNSServer()
t.dnsserver.Stop()
}
if t.ipstack != nil { if t.ipstack != nil {
t.ipstack.Close() t.ipstack.Close()
} }

View File

@ -2,13 +2,14 @@ package gvisor
import ( import (
"fmt" "fmt"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"net" "net"
Common "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
@ -23,15 +24,33 @@ var (
ipv6Zero = tcpip.Address(net.IPv6zero.To16()) ipv6Zero = tcpip.Address(net.IPv6zero.To16())
) )
type ListenerWrap struct {
net.Listener
listener net.Listener
}
func (l *ListenerWrap) Accept() (conn net.Conn, err error) {
conn, err = l.listener.Accept()
log.Debugln("[DNS] hijack tcp:%s", l.Addr())
return
}
func (l *ListenerWrap) Close() error {
return l.listener.Close()
}
func (l *ListenerWrap) Addr() net.Addr {
return l.listener.Addr()
}
// DNSServer is DNS Server listening on tun devcice // DNSServer is DNS Server listening on tun devcice
type DNSServer struct { type DNSServer struct {
*dns.Server dnsServers []*dns.Server
resolver *dns.Resolver tcpListeners []net.Listener
resolver *dns.Resolver
stack *stack.Stack stack *stack.Stack
tcpListener net.Listener udpEndpoints []*dnsEndpoint
udpEndpoint *dnsEndpoint udpEndpointIDs []*stack.TransportEndpointID
udpEndpointID *stack.TransportEndpointID
tcpip.NICID tcpip.NICID
} }
@ -66,6 +85,7 @@ func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pack
var msg D.Msg var msg D.Msg
msg.Unpack(pkt.Data().AsRange().ToOwnedView()) msg.Unpack(pkt.Data().AsRange().ToOwnedView())
writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id} writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id}
log.Debugln("[DNS] hijack udp:%s:%d", id.LocalAddress.String(), id.LocalPort)
go e.server.ServeDNS(&writer, &msg) go e.server.ServeDNS(&writer, &msg)
} }
@ -129,163 +149,276 @@ func (w *dnsResponseWriter) Close() error {
} }
// CreateDNSServer create a dns server on given netstack // CreateDNSServer create a dns server on given netstack
func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, ip net.IP, port int, nicID tcpip.NICID) (*DNSServer, error) { func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijack []net.Addr, nicID tcpip.NICID) (*DNSServer, error) {
var v4 bool
var err error var err error
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
var protocol tcpip.NetworkProtocolNumber
if ip.To4() != nil {
v4 = true
address.Addr = tcpip.Address(ip.To4())
protocol = ipv4.ProtocolNumber
} else {
v4 = false
address.Addr = tcpip.Address(ip.To16())
protocol = ipv6.ProtocolNumber
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: protocol,
AddressWithPrefix: address.Addr.WithPrefix(),
}
// netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
if address.Addr == ipv4Zero || address.Addr == ipv6Zero {
address.Addr = ""
}
handler := dns.NewHandler(resolver, mapper) handler := dns.NewHandler(resolver, mapper)
serverIn := &dns.Server{} serverIn := &dns.Server{}
serverIn.SetHandler(handler) serverIn.SetHandler(handler)
tcpDnsArr := make([]net.TCPAddr, 0, len(dnsHijack))
// UDP DNS udpDnsArr := make([]net.UDPAddr, 0, len(dnsHijack))
id := &stack.TransportEndpointID{ for _, d := range dnsHijack {
LocalAddress: address.Addr, switch d.(type) {
LocalPort: uint16(port), case *net.TCPAddr:
RemotePort: 0, {
RemoteAddress: "", tcpDnsArr = append(tcpDnsArr, *d.(*net.TCPAddr))
} break
}
// TransportEndpoint for DNS case *net.UDPAddr:
endpoint := &dnsEndpoint{ {
stack: s, udpDnsArr = append(udpDnsArr, *d.(*net.UDPAddr))
uniqueID: s.UniqueID(), break
server: serverIn, }
} }
if tcpiperr := s.RegisterTransportEndpoint(
[]tcpip.NetworkProtocolNumber{
ipv4.ProtocolNumber,
ipv6.ProtocolNumber,
},
udp.ProtocolNumber,
*id,
endpoint,
ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect.
nicID); tcpiperr != nil {
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
}
// TCP DNS
var tcpListener net.Listener
if v4 {
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
} else {
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
}
if err != nil {
return nil, fmt.Errorf("can not listen on tun: %v", err)
} }
endpoints, ids := hijackUdpDns(udpDnsArr, s, serverIn)
tcpListeners, dnsServers := hijackTcpDns(tcpDnsArr, s, serverIn)
server := &DNSServer{ server := &DNSServer{
Server: serverIn, resolver: resolver,
resolver: resolver, stack: s,
stack: s, udpEndpoints: endpoints,
tcpListener: tcpListener, udpEndpointIDs: ids,
udpEndpoint: endpoint, NICID: nicID,
udpEndpointID: id, tcpListeners: tcpListeners,
NICID: nicID,
} }
server.SetHandler(handler)
server.Server.Server = &D.Server{Listener: tcpListener, Handler: server}
go func() { server.dnsServers = dnsServers
server.ActivateAndServe()
}()
return server, err return server, err
} }
func hijackUdpDns(dnsArr []net.UDPAddr, s *stack.Stack, serverIn *dns.Server) ([]*dnsEndpoint, []*stack.TransportEndpointID) {
endpoints := make([]*dnsEndpoint, len(dnsArr))
ids := make([]*stack.TransportEndpointID, len(dnsArr))
for i, dns := range dnsArr {
port := dns.Port
ip := dns.IP
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
var protocol tcpip.NetworkProtocolNumber
if ip.To4() != nil {
address.Addr = tcpip.Address(ip.To4())
protocol = ipv4.ProtocolNumber
} else {
address.Addr = tcpip.Address(ip.To16())
protocol = ipv6.ProtocolNumber
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: protocol,
AddressWithPrefix: address.Addr.WithPrefix(),
}
// netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
if address.Addr == ipv4Zero || address.Addr == ipv6Zero {
address.Addr = ""
}
// UDP DNS
id := &stack.TransportEndpointID{
LocalAddress: address.Addr,
LocalPort: uint16(port),
RemotePort: 0,
RemoteAddress: "",
}
// TransportEndpoint for DNS
endpoint := &dnsEndpoint{
stack: s,
uniqueID: s.UniqueID(),
server: serverIn,
}
if tcpiperr := s.RegisterTransportEndpoint(
[]tcpip.NetworkProtocolNumber{
ipv4.ProtocolNumber,
ipv6.ProtocolNumber,
},
udp.ProtocolNumber,
*id,
endpoint,
ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect.
nicID); tcpiperr != nil {
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
}
ids[i] = id
endpoints[i] = endpoint
}
return endpoints, ids
}
func hijackTcpDns(dnsArr []net.TCPAddr, s *stack.Stack, serverIn *dns.Server) ([]net.Listener, []*dns.Server) {
tcpListeners := make([]net.Listener, len(dnsArr))
dnsServers := make([]*dns.Server, len(dnsArr))
for i, dnsAddr := range dnsArr {
var tcpListener net.Listener
var v4 bool
var err error
port := dnsAddr.Port
ip := dnsAddr.IP
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
if ip.To4() != nil {
address.Addr = tcpip.Address(ip.To4())
v4 = true
} else {
address.Addr = tcpip.Address(ip.To16())
v4 = false
}
if v4 {
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
} else {
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
}
if err != nil {
log.Errorln("can not listen on tun: %v, hijack tcp[%s] failed", err, dnsAddr)
} else {
tcpListeners[i] = tcpListener
server := &D.Server{Listener: &ListenerWrap{
listener: tcpListener,
}, Handler: serverIn}
dnsServer := dns.Server{}
dnsServer.Server = server
go dnsServer.ActivateAndServe()
dnsServers[i] = &dnsServer
}
}
//
//for _, listener := range tcpListeners {
// server := &D.Server{Listener: listener, Handler: serverIn}
//
// dnsServers = append(dnsServers, &dnsServer)
// go dnsServer.ActivateAndServe()
//}
return tcpListeners, dnsServers
}
// Stop stop the DNS Server on tun // Stop stop the DNS Server on tun
func (s *DNSServer) Stop() { func (s *DNSServer) Stop() {
// shutdown TCP DNS Server if s == nil {
s.Server.Shutdown() return
// remove TCP endpoint from stack }
if s.Listener != nil {
s.Listener.Close() for i := 0; i < len(s.udpEndpointIDs); i++ {
ep := s.udpEndpoints[i]
id := s.udpEndpointIDs[i]
// remove udp endpoint from stack
s.stack.UnregisterTransportEndpoint(
[]tcpip.NetworkProtocolNumber{
ipv4.ProtocolNumber,
ipv6.ProtocolNumber,
},
udp.ProtocolNumber,
*id,
ep,
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
s.NICID)
}
for _, server := range s.dnsServers {
server.Shutdown()
}
for _, listener := range s.tcpListeners {
listener.Close()
} }
// remove udp endpoint from stack
s.stack.UnregisterTransportEndpoint(
[]tcpip.NetworkProtocolNumber{
ipv4.ProtocolNumber,
ipv6.ProtocolNumber,
},
udp.ProtocolNumber,
*s.udpEndpointID,
s.udpEndpoint,
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
s.NICID)
} }
// DNSListen return the listening address of DNS Server // DnsHijack return the listening address of DNS Server
func (t *gvisorAdapter) DNSListen() string { func (t *gvisorAdapter) DnsHijack() []string {
if t.dnsserver != nil { dnsHijackArr := make([]string, len(t.dnsServer.udpEndpoints))
id := t.dnsserver.udpEndpointID for _, id := range t.dnsServer.udpEndpointIDs {
return fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort) dnsHijackArr = append(dnsHijackArr, fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort))
} }
return ""
return dnsHijackArr
} }
// Stop stop the DNS Server on tun func (t *gvisorAdapter) StopDNSServer() {
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addr string) error { t.dnsServer.Stop()
if addr == "" && t.dnsserver == nil { log.Debugln("tun DNS server stoped")
return nil t.dnsServer = nil
} }
if addr == t.DNSListen() && t.dnsserver != nil && t.dnsserver.resolver == resolver { // ReCreateDNSServer recreate the DNS Server on tun
return nil func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijackArr []string) error {
} t.StopDNSServer()
if t.dnsserver != nil {
t.dnsserver.Stop()
t.dnsserver = nil
log.Debugln("tun DNS server stoped")
}
var err error
_, port, err := net.SplitHostPort(addr)
if port == "0" || port == "" || err != nil {
return nil
}
if resolver == nil { if resolver == nil {
return fmt.Errorf("failed to create DNS server on tun: resolver not provided") return fmt.Errorf("failed to create DNS server on tun: resolver not provided")
} }
udpAddr, err := net.ResolveUDPAddr("udp", addr) if len(dnsHijackArr) == 0 {
return fmt.Errorf("failed to create DNS server on tun: len(addrs) == 0")
}
var err error
var addrs []net.Addr
for _, addr := range dnsHijackArr {
var (
addrType string
hostPort string
)
addrType, hostPort, err = Common.SplitNetworkType(addr)
if err != nil {
return err
}
var (
host, port string
hasPort bool
)
host, port, hasPort, err = Common.SplitHostPort(hostPort)
if !hasPort {
port = "53"
}
switch addrType {
case "udp", "":
{
var udpDNS *net.UDPAddr
udpDNS, err = net.ResolveUDPAddr("udp", net.JoinHostPort(host, port))
if err != nil {
return err
}
addrs = append(addrs, udpDNS)
break
}
case "tcp":
{
var tcpDNS *net.TCPAddr
tcpDNS, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port))
if err != nil {
return err
}
addrs = append(addrs, tcpDNS)
break
}
default:
err = fmt.Errorf("unspported dns scheme:%s", addrType)
}
}
server, err := CreateDNSServer(t.ipstack, resolver, mapper, addrs, nicID)
if err != nil { if err != nil {
return err return err
} }
server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID) t.dnsServer = server
if err != nil {
return err
}
t.dnsserver = server
log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled())
return nil return nil
} }

View File

@ -4,6 +4,6 @@ package ipstack
type TunAdapter interface { type TunAdapter interface {
Close() Close()
Stack() string Stack() string
DNSListen() string DnsHijack() []string
AutoRoute() bool AutoRoute() bool
} }

View File

@ -17,26 +17,26 @@ import (
) )
type systemAdapter struct { type systemAdapter struct {
device dev.TunDevice device dev.TunDevice
tun *tun2socket.Tun2Socket tun *tun2socket.Tun2Socket
lock sync.Mutex lock sync.Mutex
stackName string stackName string
dnsListen string dnsHackjack []string
autoRoute bool autoRoute bool
} }
func NewAdapter(device dev.TunDevice, conf config.Tun, mtu int, gateway, mirror string, onStop func(), tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) { func NewAdapter(device dev.TunDevice, conf config.Tun, mtu int, gateway, mirror string, onStop func(), tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
adapter := &systemAdapter{ adapter := &systemAdapter{
device: device, device: device,
stackName: conf.Stack, stackName: conf.Stack,
dnsListen: conf.DNSListen, dnsHackjack: conf.DnsHijack,
autoRoute: conf.AutoRoute, autoRoute: conf.AutoRoute,
} }
adapter.lock.Lock() adapter.lock.Lock()
defer adapter.lock.Unlock() defer adapter.lock.Unlock()
dnsHost, dnsPort, err := net.SplitHostPort(conf.DNSListen) dnsHost, dnsPort, err := net.SplitHostPort(conf.DnsHijack[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,8 +91,8 @@ func (t *systemAdapter) AutoRoute() bool {
return t.autoRoute return t.autoRoute
} }
func (t *systemAdapter) DNSListen() string { func (t *systemAdapter) DnsHijack() []string {
return t.dnsListen return t.dnsHackjack
} }
func (t *systemAdapter) Close() { func (t *systemAdapter) Close() {

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"errors" "errors"
@ -22,7 +22,7 @@ func HasNoResolve(params []string) bool {
return false return false
} }
func findNetwork(params []string) C.NetWork { func FindNetwork(params []string) C.NetWork {
for _, p := range params { for _, p := range params {
if p == "tcp" { if p == "tcp" {
return C.TCP return C.TCP
@ -33,7 +33,7 @@ func findNetwork(params []string) C.NetWork {
return C.ALLNet return C.ALLNet
} }
func findSourceIPs(params []string) []*net.IPNet { func FindSourceIPs(params []string) []*net.IPNet {
var ips []*net.IPNet var ips []*net.IPNet
for _, p := range params { for _, p := range params {
if p == noResolve || len(p) < 7 { if p == noResolve || len(p) < 7 {

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"strings" "strings"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"strings" "strings"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"strings" "strings"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"strings" "strings"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"net" "net"

View File

@ -0,0 +1,53 @@
package common
import (
"fmt"
C "github.com/Dreamacro/clash/constant"
"strings"
)
type NetworkType struct {
network C.NetWork
adapter string
}
func NewNetworkType(network, adapter string) (*NetworkType, error) {
ntType := new(NetworkType)
ntType.adapter = adapter
switch strings.ToUpper(network) {
case "TCP":
ntType.network = C.TCP
break
case "UDP":
ntType.network = C.UDP
break
default:
return nil, fmt.Errorf("unsupported network type, only TCP/UDP")
}
return ntType, nil
}
func (n *NetworkType) RuleType() C.RuleType {
return C.Network
}
func (n *NetworkType) Match(metadata *C.Metadata) bool {
return n.network == metadata.NetWork
}
func (n *NetworkType) Adapter() string {
return n.adapter
}
func (n *NetworkType) Payload() string {
return n.network.String()
}
func (n *NetworkType) ShouldResolveIP() bool {
return false
}
func (n *NetworkType) RuleExtra() *C.RuleExtra {
return nil
}

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package rules package common
import ( import (
"fmt" "fmt"
@ -35,26 +35,28 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
} }
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
cached, hit := processCache.Get(key) if strings.TrimSpace(metadata.Process) == "" {
if !hit { cached, hit := processCache.Get(key)
srcPort, err := strconv.Atoi(metadata.SrcPort) if !hit {
if err != nil { srcPort, err := strconv.Atoi(metadata.SrcPort)
processCache.Set(key, "") if err != nil {
return false 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
} }
name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) metadata.Process = cached.(string)
if err != nil {
log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
} }
metadata.Process = cached.(string)
return strings.EqualFold(metadata.Process, ps.process) return strings.EqualFold(metadata.Process, ps.process)
} }

58
rule/logic/and.go Normal file
View File

@ -0,0 +1,58 @@
package logic
import C "github.com/Dreamacro/clash/constant"
type AND struct {
rules []C.Rule
payload string
adapter string
needIP bool
}
func NewAND(payload string, adapter string) (*AND, error) {
and := &AND{payload: payload, adapter: adapter}
rules, err := parseRuleByPayload(payload)
if err != nil {
return nil, err
}
and.rules = rules
for _, rule := range rules {
if rule.ShouldResolveIP() {
and.needIP = true
break
}
}
return and, nil
}
func (A *AND) RuleType() C.RuleType {
return C.AND
}
func (A *AND) Match(metadata *C.Metadata) bool {
for _, rule := range A.rules {
if !rule.Match(metadata) {
return false
}
}
return true
}
func (A *AND) Adapter() string {
return A.adapter
}
func (A *AND) Payload() string {
return A.payload
}
func (A *AND) ShouldResolveIP() bool {
return A.needIP
}
func (A *AND) RuleExtra() *C.RuleExtra {
return nil
}

168
rule/logic/common.go Normal file
View File

@ -0,0 +1,168 @@
package logic
import (
"fmt"
"github.com/Dreamacro/clash/common/collections"
C "github.com/Dreamacro/clash/constant"
RC "github.com/Dreamacro/clash/rule/common"
"github.com/Dreamacro/clash/rule/provider"
"regexp"
"strings"
)
func parseRuleByPayload(payload string) ([]C.Rule, error) {
regex, err := regexp.Compile("\\(.*\\)")
if err != nil {
return nil, err
}
if regex.MatchString(payload) {
subAllRanges, err := format(payload)
if err != nil {
return nil, err
}
rules := make([]C.Rule, 0, len(subAllRanges))
subRanges := findSubRuleRange(payload, subAllRanges)
for _, subRange := range subRanges {
subPayload := payload[subRange.start+1 : subRange.end]
rule, err := payloadToRule(subPayload)
if err != nil {
return nil, err
}
rules = append(rules, rule)
}
return rules, nil
}
return nil, fmt.Errorf("payload format error")
}
func containRange(r Range, preStart, preEnd int) bool {
return preStart < r.start && preEnd > r.end
}
func payloadToRule(subPayload string) (C.Rule, error) {
splitStr := strings.SplitN(subPayload, ",", 2)
tp := splitStr[0]
payload := splitStr[1]
if tp == "NOT" || tp == "OR" || tp == "AND" {
return parseRule(tp, payload, nil)
}
param := strings.Split(payload, ",")
return parseRule(tp, param[0], param[1:])
}
func parseRule(tp, payload string, params []string) (C.Rule, error) {
var (
parseErr error
parsed C.Rule
)
switch tp {
case "DOMAIN":
parsed = RC.NewDomain(payload, "", nil)
case "DOMAIN-SUFFIX":
parsed = RC.NewDomainSuffix(payload, "", nil)
case "DOMAIN-KEYWORD":
parsed = RC.NewDomainKeyword(payload, "", nil)
case "GEOSITE":
parsed, parseErr = RC.NewGEOSITE(payload, "", nil)
case "GEOIP":
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewGEOIP(payload, "", noResolve, nil)
case "IP-CIDR", "IP-CIDR6":
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRNoResolve(noResolve))
case "SRC-IP-CIDR":
parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
case "SRC-PORT":
parsed, parseErr = RC.NewPort(payload, "", true, nil)
case "DST-PORT":
parsed, parseErr = RC.NewPort(payload, "", false, nil)
case "PROCESS-NAME":
parsed, parseErr = RC.NewProcess(payload, "", nil)
case "RULE-SET":
parsed, parseErr = provider.NewRuleSet(payload, "", nil)
case "NOT":
parsed, parseErr = NewNOT(payload, "")
case "AND":
parsed, parseErr = NewAND(payload, "")
case "OR":
parsed, parseErr = NewOR(payload, "")
case "NETWORK":
parsed, parseErr = RC.NewNetworkType(payload, "")
default:
parseErr = fmt.Errorf("unsupported rule type %s", tp)
}
return parsed, parseErr
}
type Range struct {
start int
end int
index int
}
func format(payload string) ([]Range, error) {
stack := collections.NewStack()
num := 0
subRanges := make([]Range, 0)
for i, c := range payload {
if c == '(' {
sr := Range{
start: i,
index: num,
}
num++
stack.Push(sr)
} else if c == ')' {
sr := stack.Pop().(Range)
sr.end = i
subRanges = append(subRanges, sr)
}
}
if stack.Len() != 0 {
return nil, fmt.Errorf("format error is missing )")
}
sortResult := make([]Range, len(subRanges))
for _, sr := range subRanges {
sortResult[sr.index] = sr
}
return sortResult, nil
}
func findSubRuleRange(payload string, ruleRanges []Range) []Range {
payloadLen := len(payload)
subRuleRange := make([]Range, 0)
for _, rr := range ruleRanges {
if rr.start == 0 && rr.end == payloadLen-1 {
// 最大范围跳过
continue
}
containInSub := false
for _, r := range subRuleRange {
if containRange(rr, r.start, r.end) {
// The subRuleRange contains a range of rr, which is the next level node of the tree
containInSub = true
break
}
}
if !containInSub {
subRuleRange = append(subRuleRange, rr)
}
}
return subRuleRange
}

51
rule/logic/not.go Normal file
View File

@ -0,0 +1,51 @@
package logic
import (
"fmt"
C "github.com/Dreamacro/clash/constant"
)
type NOT struct {
rule C.Rule
payload string
adapter string
}
func NewNOT(payload string, adapter string) (*NOT, error) {
not := &NOT{payload: payload, adapter: adapter}
rule, err := parseRuleByPayload(payload)
if err != nil {
return nil, err
}
if len(rule) < 1 {
return nil, fmt.Errorf("the parsed rule is empty")
}
not.rule = rule[0]
return not, nil
}
func (not *NOT) RuleType() C.RuleType {
return C.NOT
}
func (not *NOT) Match(metadata *C.Metadata) bool {
return !not.rule.Match(metadata)
}
func (not *NOT) Adapter() string {
return not.adapter
}
func (not *NOT) Payload() string {
return not.payload
}
func (not *NOT) ShouldResolveIP() bool {
return not.rule.ShouldResolveIP()
}
func (not *NOT) RuleExtra() *C.RuleExtra {
return nil
}

58
rule/logic/or.go Normal file
View File

@ -0,0 +1,58 @@
package logic
import C "github.com/Dreamacro/clash/constant"
type OR struct {
rules []C.Rule
payload string
adapter string
needIP bool
}
func (or *OR) RuleType() C.RuleType {
return C.OR
}
func (or *OR) Match(metadata *C.Metadata) bool {
for _, rule := range or.rules {
if rule.Match(metadata) {
return true
}
}
return false
}
func (or *OR) Adapter() string {
return or.adapter
}
func (or *OR) Payload() string {
return or.payload
}
func (or *OR) ShouldResolveIP() bool {
return or.needIP
}
func (or *OR) RuleExtra() *C.RuleExtra {
return nil
}
func NewOR(payload string, adapter string) (*OR, error) {
or := &OR{payload: payload, adapter: adapter}
rules, err := parseRuleByPayload(payload)
if err != nil {
return nil, err
}
or.rules = rules
for _, rule := range rules {
if rule.ShouldResolveIP() {
or.needIP = true
break
}
}
return or, nil
}

View File

@ -1,12 +1,11 @@
package rules package rule
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
P "github.com/Dreamacro/clash/constant/provider" RC "github.com/Dreamacro/clash/rule/common"
"time" "github.com/Dreamacro/clash/rule/logic"
RP "github.com/Dreamacro/clash/rule/provider"
) )
func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
@ -16,81 +15,48 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
) )
ruleExtra := &C.RuleExtra{ ruleExtra := &C.RuleExtra{
Network: findNetwork(params), Network: RC.FindNetwork(params),
SourceIPs: findSourceIPs(params), SourceIPs: RC.FindSourceIPs(params),
} }
switch tp { switch tp {
case "DOMAIN": case "DOMAIN":
parsed = NewDomain(payload, target, ruleExtra) parsed = RC.NewDomain(payload, target, ruleExtra)
case "DOMAIN-SUFFIX": case "DOMAIN-SUFFIX":
parsed = NewDomainSuffix(payload, target, ruleExtra) parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
case "DOMAIN-KEYWORD": case "DOMAIN-KEYWORD":
parsed = NewDomainKeyword(payload, target, ruleExtra) parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
case "GEOSITE": case "GEOSITE":
parsed, parseErr = NewGEOSITE(payload, target, ruleExtra) parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
case "GEOIP": case "GEOIP":
noResolve := HasNoResolve(params) noResolve := RC.HasNoResolve(params)
parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra) parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra)
case "IP-CIDR", "IP-CIDR6": case "IP-CIDR", "IP-CIDR6":
noResolve := HasNoResolve(params) noResolve := RC.HasNoResolve(params)
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve)) parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
case "SRC-IP-CIDR": case "SRC-IP-CIDR":
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
case "SRC-PORT": case "SRC-PORT":
parsed, parseErr = NewPort(payload, target, true, ruleExtra) parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
case "DST-PORT": case "DST-PORT":
parsed, parseErr = NewPort(payload, target, false, ruleExtra) parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
case "PROCESS-NAME": case "PROCESS-NAME":
parsed, parseErr = NewProcess(payload, target, ruleExtra) parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
case "MATCH": case "MATCH":
parsed = NewMatch(target, ruleExtra) parsed = RC.NewMatch(target, ruleExtra)
case "RULE-SET": case "RULE-SET":
parsed, parseErr = NewRuleSet(payload, target, ruleExtra) parsed, parseErr = RP.NewRuleSet(payload, target, ruleExtra)
case "NETWORK":
parsed, parseErr = RC.NewNetworkType(payload, target)
case "AND":
parsed, parseErr = logic.NewAND(payload, target)
case "OR":
parsed, parseErr = logic.NewOR(payload, target)
case "NOT":
parsed, parseErr = logic.NewNOT(payload, target)
default: default:
parseErr = fmt.Errorf("unsupported rule type %s", tp) parseErr = fmt.Errorf("unsupported rule type %s", tp)
} }
return parsed, parseErr return parsed, parseErr
} }
type ruleProviderSchema struct {
Type string `provider:"type"`
Behavior string `provider:"behavior"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
}
func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) {
schema := &ruleProviderSchema{}
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
var behavior P.RuleType
switch schema.Behavior {
case "domain":
behavior = P.Domain
case "ipcidr":
behavior = P.IPCIDR
case "classical":
behavior = P.Classical
default:
return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior)
}
path := C.Path.Resolve(schema.Path)
var vehicle P.Vehicle
switch schema.Type {
case "file":
vehicle = provider.NewFileVehicle(path)
case "http":
vehicle = provider.NewHTTPVehicle(schema.URL, path)
default:
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
}
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil
}

View File

@ -1,4 +1,4 @@
package rules package provider
import ( import (
"bytes" "bytes"

90
rule/provider/parse.go Normal file
View File

@ -0,0 +1,90 @@
package provider
import (
"fmt"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
P "github.com/Dreamacro/clash/constant/provider"
RC "github.com/Dreamacro/clash/rule/common"
"time"
)
type ruleProviderSchema struct {
Type string `provider:"type"`
Behavior string `provider:"behavior"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
}
func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) {
schema := &ruleProviderSchema{}
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
var behavior P.RuleType
switch schema.Behavior {
case "domain":
behavior = P.Domain
case "ipcidr":
behavior = P.IPCIDR
case "classical":
behavior = P.Classical
default:
return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior)
}
path := C.Path.Resolve(schema.Path)
var vehicle P.Vehicle
switch schema.Type {
case "file":
vehicle = provider.NewFileVehicle(path)
case "http":
vehicle = provider.NewHTTPVehicle(schema.URL, path)
default:
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
}
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil
}
func parseRule(tp, payload, target string, params []string) (C.Rule, error) {
var (
parseErr error
parsed C.Rule
)
ruleExtra := &C.RuleExtra{
Network: RC.FindNetwork(params),
SourceIPs: RC.FindSourceIPs(params),
}
switch tp {
case "DOMAIN":
parsed = RC.NewDomain(payload, target, ruleExtra)
case "DOMAIN-SUFFIX":
parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
case "DOMAIN-KEYWORD":
parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
case "GEOSITE":
parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
case "IP-CIDR", "IP-CIDR6":
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
case "SRC-IP-CIDR":
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
case "SRC-PORT":
parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
case "DST-PORT":
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
case "PROCESS-NAME":
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
default:
parseErr = fmt.Errorf("unsupported rule type %s", tp)
}
return parsed, parseErr
}

View File

@ -1,4 +1,4 @@
package rules package provider
import ( import (
"encoding/json" "encoding/json"
@ -75,6 +75,10 @@ func (rp *ruleSetProvider) Behavior() P.RuleType {
} }
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool { func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
if rp.count == 0 {
return false
}
switch rp.behavior { switch rp.behavior {
case P.Domain: case P.Domain:
return rp.DomainRules.Search(metadata.Host) != nil return rp.DomainRules.Search(metadata.Host) != nil
@ -201,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) {
return nil, errors.New("error rule type") return nil, errors.New("error rule type")
} }
r, err := ParseRule(ruleType, rule, "", params) r, err := parseRule(ruleType, rule, "", params)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,4 +1,4 @@
package rules package provider
import ( import (
"fmt" "fmt"

View File

@ -32,7 +32,7 @@ const (
ImageVmess = "v2fly/v2fly-core:latest" ImageVmess = "v2fly/v2fly-core:latest"
ImageTrojan = "trojangfw/trojan:latest" ImageTrojan = "trojangfw/trojan:latest"
ImageTrojanGo = "p4gefau1t/trojan-go:latest" ImageTrojanGo = "p4gefau1t/trojan-go:latest"
ImageSnell = "icpz/snell-server:latest" ImageSnell = "ghcr.io/icpz/snell-server:latest"
ImageXray = "teddysun/xray:latest" ImageXray = "teddysun/xray:latest"
) )

View File

@ -120,6 +120,42 @@ func TestClash_Snell(t *testing.T) {
testSuit(t, proxy) testSuit(t, proxy)
} }
func TestClash_Snellv3(t *testing.T) {
cfg := &container.Config{
Image: ImageSnell,
ExposedPorts: defaultExposedPorts,
Cmd: []string{"-c", "/config.conf"},
}
hostCfg := &container.HostConfig{
PortBindings: defaultPortBindings,
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell.conf"))},
}
id, err := startContainer(cfg, hostCfg, "snell")
if err != nil {
assert.FailNow(t, err.Error())
}
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewSnell(outbound.SnellOption{
Name: "snell",
Server: localIP.String(),
Port: 10002,
Psk: "password",
UDP: true,
Version: 3,
})
if err != nil {
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime)
testSuit(t, proxy)
}
func Benchmark_Snell(b *testing.B) { func Benchmark_Snell(b *testing.B) {
cfg := &container.Config{ cfg := &container.Config{
Image: ImageSnell, Image: ImageSnell,

View File

@ -5,6 +5,7 @@ package gun
import ( import (
"bufio" "bufio"
"context"
"crypto/tls" "crypto/tls"
"encoding/binary" "encoding/binary"
"errors" "errors"
@ -17,6 +18,7 @@ import (
"time" "time"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic" "go.uber.org/atomic"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@ -173,7 +175,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
} }
cn := tls.Client(pconn, cfg) cn := tls.Client(pconn, cfg)
if err := cn.Handshake(); err != nil {
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
if err := cn.HandshakeContext(ctx); err != nil {
pconn.Close() pconn.Close()
return nil, err return nil, err
} }

View File

@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"sync"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowaead"
) )
@ -15,13 +17,19 @@ import (
const ( const (
Version1 = 1 Version1 = 1
Version2 = 2 Version2 = 2
Version3 = 3
DefaultSnellVersion = Version1 DefaultSnellVersion = Version1
// max packet length
maxLength = 0x3FFF
) )
const ( const (
CommandPing byte = 0 CommandPing byte = 0
CommandConnect byte = 1 CommandConnect byte = 1
CommandConnectV2 byte = 5 CommandConnectV2 byte = 5
CommandUDP byte = 6
CommondUDPForward byte = 1
CommandTunnel byte = 0 CommandTunnel byte = 0
CommandPong byte = 1 CommandPong byte = 1
@ -100,6 +108,16 @@ func WriteHeader(conn net.Conn, host string, port uint, version int) error {
return nil return nil
} }
func WriteUDPHeader(conn net.Conn, version int) error {
if version < Version3 {
return errors.New("unsupport UDP version")
}
// version, command, clientID length
_, err := conn.Write([]byte{Version, CommandUDP, 0x00})
return err
}
// HalfClose works only on version2 // HalfClose works only on version2
func HalfClose(conn net.Conn) error { func HalfClose(conn net.Conn) error {
if _, err := conn.Write(endSignal); err != nil { if _, err := conn.Write(endSignal); err != nil {
@ -114,10 +132,147 @@ func HalfClose(conn net.Conn) error {
func StreamConn(conn net.Conn, psk []byte, version int) *Snell { func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
var cipher shadowaead.Cipher var cipher shadowaead.Cipher
if version == Version2 { if version != Version1 {
cipher = NewAES128GCM(psk) cipher = NewAES128GCM(psk)
} else { } else {
cipher = NewChacha20Poly1305(psk) cipher = NewChacha20Poly1305(psk)
} }
return &Snell{Conn: shadowaead.NewConn(conn, cipher)} return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
} }
func PacketConn(conn net.Conn) net.PacketConn {
return &packetConn{
Conn: conn,
}
}
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
// compose snell UDP address format (refer: icpz/snell-server-reversed)
// a brand new wheel to replace socks5 address format, well done Yachen
buf.WriteByte(CommondUDPForward)
switch socks5Addr[0] {
case socks5.AtypDomainName:
hostLen := socks5Addr[1]
buf.Write(socks5Addr[1 : 1+1+hostLen+2])
case socks5.AtypIPv4:
buf.Write([]byte{0x00, 0x04})
buf.Write(socks5Addr[1 : 1+net.IPv4len+2])
case socks5.AtypIPv6:
buf.Write([]byte{0x00, 0x06})
buf.Write(socks5Addr[1 : 1+net.IPv6len+2])
}
buf.Write(payload)
_, err := w.Write(buf.Bytes())
if err != nil {
return 0, err
}
return len(payload), nil
}
func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
if len(payload) <= maxLength {
return writePacket(w, socks5Addr, payload)
}
offset := 0
total := len(payload)
for {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := writePacket(w, socks5Addr, payload[offset:cursor])
if err != nil {
return offset + n, err
}
offset = cursor
if offset == total {
break
}
}
return total, nil
}
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, error) {
buf := pool.Get(pool.UDPBufferSize)
defer pool.Put(buf)
n, err := r.Read(buf)
headLen := 1
if err != nil {
return nil, 0, err
}
if n < headLen {
return nil, 0, errors.New("insufficient UDP length")
}
// parse snell UDP response address format
switch buf[0] {
case 0x04:
headLen += net.IPv4len + 2
if n < headLen {
err = errors.New("insufficient UDP length")
break
}
buf[0] = socks5.AtypIPv4
case 0x06:
headLen += net.IPv6len + 2
if n < headLen {
err = errors.New("insufficient UDP length")
break
}
buf[0] = socks5.AtypIPv6
default:
err = errors.New("ip version invalid")
}
if err != nil {
return nil, 0, err
}
addr := socks5.SplitAddr(buf[0:])
if addr == nil {
return nil, 0, errors.New("remote address invalid")
}
uAddr := addr.UDPAddr()
length := len(payload)
if n-headLen < length {
length = n - headLen
}
copy(payload[:], buf[headLen:headLen+length])
return uAddr, length, nil
}
type packetConn struct {
net.Conn
rMux sync.Mutex
wMux sync.Mutex
}
func (pc *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
pc.wMux.Lock()
defer pc.wMux.Unlock()
return WritePacket(pc, socks5.ParseAddr(addr.String()), b)
}
func (pc *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
pc.rMux.Lock()
defer pc.rMux.Unlock()
addr, n, err := ReadPacket(pc.Conn, b)
if err != nil {
return 0, nil, err
}
return n, addr, nil
}

View File

@ -1,6 +1,7 @@
package trojan package trojan
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/binary" "encoding/binary"
@ -12,6 +13,7 @@ import (
"sync" "sync"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
) )
@ -68,7 +70,11 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
} }
tlsConn := tls.Client(conn, tlsConfig) tlsConn := tls.Client(conn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(ctx); err != nil {
return nil, err return nil, err
} }

View File

@ -1,8 +1,11 @@
package vmess package vmess
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
C "github.com/Dreamacro/clash/constant"
) )
type TLSConfig struct { type TLSConfig struct {
@ -19,6 +22,10 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
} }
tlsConn := tls.Client(conn, tlsConfig) tlsConn := tls.Client(conn, tlsConfig)
err := tlsConn.Handshake()
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
return tlsConn, err return tlsConn, err
} }

View File

@ -3,6 +3,7 @@ package tunnel
import ( import (
"context" "context"
"fmt" "fmt"
R "github.com/Dreamacro/clash/rule/common"
"net" "net"
"runtime" "runtime"
"sync" "sync"
@ -15,7 +16,6 @@ import (
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
icontext "github.com/Dreamacro/clash/context" icontext "github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rule"
"github.com/Dreamacro/clash/tunnel/statistic" "github.com/Dreamacro/clash/tunnel/statistic"
) )
@ -158,7 +158,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
return nil return nil
} }
func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
switch mode { switch mode {
case Direct: case Direct:
proxy = proxies["DIRECT"] proxy = proxies["DIRECT"]
@ -222,7 +222,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
}() }()
pCtx := icontext.NewPacketConnContext(metadata) pCtx := icontext.NewPacketConnContext(metadata)
proxy, rule, err := resolveMetadata(pCtx, metadata) proxy, rule, err := resolveMetadata(metadata)
if err != nil { if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return return
@ -244,15 +244,19 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
switch true { switch true {
case rule != nil: case rule != nil:
log.Infoln("[UDP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) if rule.Payload() != "" {
log.Infoln("[UDP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), rawPc.Chains().String())
} else {
log.Infoln("[UDP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), rule.Payload(), rawPc.Chains().String())
}
case mode == Script: case mode == Script:
log.Infoln("[UDP] %s --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.RemoteAddress(), rawPc.Chains().String()) log.Infoln("[UDP] %s --> %s using SCRIPT %s", metadata.SourceDetail(), metadata.RemoteAddress(), rawPc.Chains().String())
case mode == Global: case mode == Global:
log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[UDP] %s --> %s using GLOBAL", metadata.SourceDetail(), metadata.RemoteAddress())
case mode == Direct: case mode == Direct:
log.Infoln("[UDP] %s(%s) --> %s using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[UDP] %s --> %s using DIRECT", metadata.SourceDetail(), metadata.RemoteAddress())
default: default:
log.Infoln("[UDP] %s(%s) --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[UDP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceDetail(), metadata.RemoteAddress())
} }
go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr)
@ -276,7 +280,7 @@ func handleTCPConn(connCtx C.ConnContext) {
return return
} }
proxy, rule, err := resolveMetadata(connCtx, metadata) proxy, rule, err := resolveMetadata(metadata)
if err != nil { if err != nil {
log.Warnln("[Metadata] parse failed: %s", err.Error()) log.Warnln("[Metadata] parse failed: %s", err.Error())
return return
@ -298,15 +302,19 @@ func handleTCPConn(connCtx C.ConnContext) {
switch true { switch true {
case rule != nil: case rule != nil:
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) if rule.Payload() != "" {
log.Infoln("[TCP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), remoteConn.Chains().String())
} else {
log.Infoln("[TCP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), rule.RuleType().String(), remoteConn.Chains().String())
}
case mode == Script: case mode == Script:
log.Infoln("[TCP] %s(%s) --> %s using SCRIPT %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), remoteConn.Chains().String()) log.Infoln("[TCP] %s --> %s using SCRIPT %s", metadata.SourceDetail(), metadata.RemoteAddress(), remoteConn.Chains().String())
case mode == Global: case mode == Global:
log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[TCP] %s --> %s using GLOBAL", metadata.SourceDetail(), metadata.RemoteAddress())
case mode == Direct: case mode == Direct:
log.Infoln("[TCP] %s(%s) --> %s using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[TCP] %s --> %s using DIRECT", metadata.SourceDetail(), metadata.RemoteAddress())
default: default:
log.Infoln("[TCP] %s(%s) --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) log.Infoln("[TCP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress())
} }
handleSocket(connCtx, remoteConn) handleSocket(connCtx, remoteConn)