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/config/cache*
/output
/.vscode

View File

@ -1,6 +1,10 @@
NAME=Clash.Meta
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)
AUTOIPTABLES=Enable
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 = \
darwin-amd64 \
darwin-arm64 \
linux-arm64 \
linux-amd64 \
linux-arm64-AutoIptables\
linux-amd64-AutoIptables
linux-armv5 \
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-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:
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -109,6 +129,9 @@ windows-386:
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm32v7:
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)
clean:
rm $(BINDIR)/*
rm $(BINDIR)/*

View File

@ -1,9 +1,9 @@
<h1 align="center">
<img src="Meta.png" alt="Meta Kennel" width="200">
<br>Meta Kennel<br>
<br>Meta Kernel<br>
</h1>
<h3 align="center">Another Clash Kennel.</h3>
<h3 align="center">Another Clash Kernel.</h3>
<p align="center">
<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).
## Advanced usage for this branch
### DNS configuration
Support resolve ip with a proxy tunnel.
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
dns:
enable: true
use-hosts: true
ipv6: false
enhanced-mode: fake-ip
enhanced-mode: redir-host
fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868
default-nameserver:
@ -52,8 +68,8 @@ dns:
- https://doh.pub/dns-query
- tls://223.5.5.5:853
fallback:
- 'https://1.0.0.1/dns-query#Proxy' # append the proxy adapter name to the end of DNS URL with '#' prefix.
- 'tls://8.8.4.4:853#Proxy'
- '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#DNS'
fallback-filter:
geoip: false
geosite:
@ -74,8 +90,9 @@ Built-in [Wintun](https://www.wintun.net) driver.
# Enable the TUN listener
tun:
enable: true
stack: gvisor # system or gvisor
dns-listen: 0.0.0.0:53 # additional dns server listen on TUN
stack: gvisor # only gvisor
dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route
```
### Rules configuration
@ -118,9 +135,44 @@ rules:
### 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`.
The XTLS only support TCP transport by the XRAY-CORE.
The XTLS support TCP/UDP by the XRAY-CORE.
```yaml
proxies:
- name: "vless-tcp"
@ -160,13 +212,13 @@ tun:
```
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:
```
[Unit]
Description=Clash.Meta Daemon, Another Clash Kennel.
Description=Clash.Meta Daemon, Another Clash Kernel.
After=network.target
[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)
}
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"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,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
}
// 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) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == 0 {
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)
}
@ -115,6 +140,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
name: option.Name,
addr: addr,
tp: C.Snell,
udp: option.UDP,
iface: option.Interface,
},
psk: psk,

View File

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

View File

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

View File

@ -29,6 +29,7 @@ type GroupCommonOption struct {
Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,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) {
@ -95,6 +96,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
return nil, err
}
providers = append(providers, list...)
} else {
groupOption.Filter = ""
}
var group C.ProxyAdapter

View File

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

View File

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

View File

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

View File

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

View File

@ -67,6 +67,10 @@ func (pp *proxySetProvider) Initial() error {
}
pp.onUpdate(elm)
if pp.healthCheck.auto() {
go pp.healthCheck.process()
}
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)
}
if hc.auto() {
go hc.process()
}
pd := &proxySetProvider{
proxies: []C.Proxy{},
healthCheck: hc,
@ -190,6 +190,10 @@ func (cp *compatibleProvider) Update() error {
}
func (cp *compatibleProvider) Initial() error {
if cp.healthCheck.auto() {
go cp.healthCheck.process()
}
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")
}
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{
name: name,
proxies: proxies,

View File

@ -2,6 +2,7 @@ package provider
import (
"context"
"github.com/Dreamacro/clash/listener/inner"
"io"
"net"
"net/http"
@ -10,7 +11,6 @@ import (
"time"
netHttp "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
types "github.com/Dreamacro/clash/constant/provider"
)
@ -77,7 +77,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
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 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
}
}
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
}
}

View File

@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) {
assert.NotNil(t, tree.Insert("..dev", localIP))
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
type Node struct {
Data interface{}
children map[string]*Node
Data interface{}
}
func (n *Node) getChild(s string) *Node {

View File

@ -4,6 +4,8 @@ import (
"container/list"
"errors"
"fmt"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"net"
"net/url"
"os"
@ -24,7 +26,6 @@ import (
providerTypes "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel"
"gopkg.in/yaml.v2"
@ -48,7 +49,6 @@ type Inbound struct {
RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"`
Tun Tun `json:"tun"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
@ -99,10 +99,10 @@ type Profile struct {
// Tun config
type Tun struct {
Enable bool `yaml:"enable" json:"enable"`
Stack string `yaml:"stack" json:"stack"`
DNSListen string `yaml:"dns-listen" json:"dns-listen"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
Enable bool `yaml:"enable" json:"enable"`
Stack string `yaml:"stack" json:"stack"`
DnsHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
}
// Script config
@ -209,8 +209,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Tun: Tun{
Enable: false,
Stack: "gvisor",
DNSListen: "0.0.0.0:53",
AutoRoute: true,
DnsHijack: []string{"198.18.0.2:53"},
AutoRoute: false,
},
DNS: RawDNS{
Enable: false,
@ -225,6 +225,17 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
DefaultNameserver: []string{
"114.114.114.114",
"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{
@ -254,7 +265,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
return nil, err
}
config.General = general
//TODO 暂未使用
config.Tun = &rawCfg.Tun
proxies, providers, err := parseProxies(rawCfg)
@ -312,7 +323,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort,
Tun: cfg.Tun,
AllowLan: cfg.AllowLan,
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) {
proxies = make(map[string]C.Proxy)
providersMap = make(map[string]providerTypes.ProxyProvider)
proxyList := []string{}
var proxyList []string
_proxiesList := list.New()
_groupsList := list.New()
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["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy
@ -387,13 +398,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
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
for idx, mapping := range groupsConfig {
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)
}
// initial compatible provider
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{}
var ps []C.Proxy
for _, v := range proxyList {
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
for name, mapping := range cfg.RuleProvider {
rp, err := R.ParseRuleProvider(name, mapping)
rp, err := RP.ParseRuleProvider(name, mapping)
if err != nil {
return nil, nil, err
}
ruleProviders[name] = &rp
R.SetRuleProvider(rp)
RP.SetRuleProvider(rp)
}
for _, provider := range ruleProviders {
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{}
var rules []C.Rule
rulesConfig := cfg.Rule
mode := cfg.Mode
@ -519,7 +504,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
var (
payload string
target string
params = []string{}
params []string
ruleName = strings.ToUpper(rule[0])
)
@ -527,24 +512,29 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
continue
}
switch l := len(rule); {
case l == 2:
target = rule[1]
case l == 3:
if ruleName == "MATCH" {
payload = ""
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
payload = strings.Join(rule[1:len(rule)-1], ",")
target = rule[len(rule)-1]
} else {
switch l := len(rule); {
case l == 2:
target = rule[1]
params = rule[2:]
break
case l == 3:
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 {
@ -553,6 +543,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
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)
if parseErr != nil {
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) {
nameservers := []dns.NameServer{}
var nameservers []dns.NameServer
for idx, server := range servers {
// 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) {
ipNets := []*net.IPNet{}
var ipNets []*net.IPNet
for idx, ip := range ips {
_, 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) {
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 {
found := false
@ -784,7 +784,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
if net.ParseIP(fb.Addr) != nil {
continue
}
host.Insert(fb.Addr, true)
_ = host.Insert(fb.Addr, true)
}
}

View File

@ -50,23 +50,6 @@ func initMMDB() error {
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) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
if err != nil {
@ -84,22 +67,9 @@ func downloadGeoSite(path string) (err error) {
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 {
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 {
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)
}
// initial GeoSite
if err := initGeoSite(); err != nil {
return fmt.Errorf("can't initial GeoSite: %w", err)
}
return nil
}

View File

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

View File

@ -2,6 +2,7 @@ package constant
import (
"encoding/json"
"fmt"
"net"
"strconv"
)
@ -23,6 +24,7 @@ const (
REDIR
TPROXY
TUN
INNER
)
type NetWork int
@ -58,6 +60,8 @@ func (t Type) String() string {
return "TProxy"
case TUN:
return "Tun"
case INNER:
return "Inner"
default:
return "Unknown"
}
@ -89,6 +93,18 @@ func (m *Metadata) SourceAddress() string {
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 {
return m.DstIP != nil
}

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@ package executor
import (
"fmt"
"github.com/Dreamacro/clash/listener/tproxy"
"net"
"os"
"runtime"
@ -10,6 +9,8 @@ import (
"strings"
"sync"
"github.com/Dreamacro/clash/listener/tproxy"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/component/auth"
@ -74,14 +75,17 @@ func ApplyConfig(cfg *config.Config, force bool) {
defer mux.Unlock()
updateUsers(cfg.Users)
updateHosts(cfg.Hosts)
updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules, cfg.RuleProviders)
updateHosts(cfg.Hosts)
updateProfile(cfg)
updateIPTables(cfg.DNS, cfg.General)
updateDNS(cfg.DNS, cfg.General)
updateGeneral(cfg.General, force)
updateIPTables(cfg.DNS, cfg.General, cfg.Tun)
updateDNS(cfg.DNS, cfg.Tun)
updateGeneral(cfg.General, cfg.Tun, force)
updateTun(cfg.Tun)
updateExperimental(cfg)
loadProvider(cfg.RuleProviders, cfg.Providers)
updateProfile(cfg)
}
func GetGeneral() *config.General {
@ -98,7 +102,6 @@ func GetGeneral() *config.General {
RedirPort: ports.RedirPort,
TProxyPort: ports.TProxyPort,
MixedPort: ports.MixedPort,
Tun: P.Tun(),
Authentication: authenticator,
AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(),
@ -113,8 +116,8 @@ func GetGeneral() *config.General {
func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS, general *config.General) {
if !c.Enable && !general.Tun.Enable {
func updateDNS(c *config.DNS, Tun *config.Tun) {
if !c.Enable && !Tun.Enable {
resolver.DefaultResolver = nil
resolver.MainResolver = nil
resolver.DefaultHostMapper = nil
@ -152,7 +155,7 @@ func updateDNS(c *config.DNS, general *config.General) {
resolver.DefaultResolver = r
resolver.MainResolver = mr
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)
} else {
resolver.DefaultLocalServer = nil
@ -175,12 +178,44 @@ func updateRules(rules []C.Rule, ruleProviders map[string]*provider.RuleProvider
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)
resolver.DisableIPv6 = !general.IPv6
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()
if err == nil {
if autoDetectInterfaceName != "" && autoDetectInterfaceName != "<nil>" {
@ -219,12 +254,21 @@ func updateGeneral(general *config.General, force bool) {
P.ReCreateTProxy(general.TProxyPort, 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())
os.Exit(2)
}
log.SetLevel(general.LogLevel)
}
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
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
}

View File

@ -23,7 +23,6 @@ type Rule struct {
func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := tunnel.Rules()
rules := []Rule{}
for _, rule := range rawRules {
rules = append(rules, Rule{
@ -31,6 +30,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
Payload: rule.Payload(),
Proxy: rule.Adapter(),
})
}
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("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/script", scriptRouter())
})
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 (
"fmt"
"github.com/Dreamacro/clash/listener/inner"
"net"
"runtime"
"strconv"
@ -71,7 +72,7 @@ func Tun() config.Tun {
return config.Tun{
Enable: true,
Stack: tunAdapter.Stack(),
DNSListen: tunAdapter.DNSListen(),
DnsHijack: tunAdapter.DnsHijack(),
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())
}
}()
inner.New(tcpIn)
addr := genAddr(bindAddress, port, allowLan)

View File

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

View File

@ -1,13 +1,5 @@
package dev
import (
"bytes"
"os/exec"
"runtime"
"github.com/Dreamacro/clash/log"
)
// TunDevice is cross-platform tun interface
type TunDevice interface {
Name() string
@ -18,54 +10,3 @@ type TunDevice interface {
Read(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 (
"bytes"
"errors"
"fmt"
"github.com/Dreamacro/clash/log"
"net"
"os"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"unsafe"
@ -176,7 +178,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
}
if autoRoute {
SetLinuxAutoRoute()
setAutoRoute(tunAddress)
}
return tun, nil
@ -280,7 +282,7 @@ func (t *tunDarwin) IsClose() bool {
func (t *tunDarwin) Close() error {
t.stopOnce.Do(func() {
if t.autoRoute {
RemoveLinuxAutoRoute()
resetAutoRoute(t.tunAddress)
}
t.closed = true
t.tunFile.Close()
@ -480,15 +482,60 @@ func (t *tunDarwin) attachLinkLocal() error {
// GetAutoDetectInterface get ethernet interface
func GetAutoDetectInterface() (string, error) {
cmd := exec.Command("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
cmd := exec.Command("route", "-n", "get", "default")
if result, err := cmd.Output(); err != nil {
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"
"errors"
"fmt"
"github.com/Dreamacro/clash/log"
"net/url"
"os"
"os/exec"
@ -38,7 +39,7 @@ type tunLinux struct {
// OpenTunDevice return a TunDevice according a URL
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)
t := &tunLinux{
@ -62,8 +63,9 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
}
if autoRoute {
SetLinuxAutoRoute()
addRoute(tunAddress)
}
return dev, nil
case "fd":
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
@ -76,7 +78,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
return nil, err
}
if autoRoute {
SetLinuxAutoRoute()
log.Warnln("linux unsupported automatic route")
}
return dev, nil
}
@ -105,9 +107,6 @@ func (t *tunLinux) IsClose() bool {
func (t *tunLinux) Close() error {
t.stopOnce.Do(func() {
if t.autoRoute {
RemoveLinuxAutoRoute()
}
t.closed = true
t.tunFile.Close()
})
@ -330,3 +329,21 @@ func GetAutoDetectInterface() (string, error) {
}
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 {
device dev.TunDevice
ipstack *stack.Stack
dnsserver *DNSServer
dnsServer *DNSServer
udpIn chan<- *inbound.PacketAdapter
stackName string
@ -47,7 +47,7 @@ type gvisorAdapter struct {
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) {
ipstack := stack.New(stack.Options{
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)
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 {
return nil, err
}
@ -132,9 +132,7 @@ func (t *gvisorAdapter) AutoRoute() bool {
// Close close the TunAdapter
func (t *gvisorAdapter) Close() {
if t.dnsserver != nil {
t.dnsserver.Stop()
}
t.StopDNSServer()
if t.ipstack != nil {
t.ipstack.Close()
}

View File

@ -2,13 +2,14 @@ package gvisor
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"net"
Common "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
@ -23,15 +24,33 @@ var (
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
type DNSServer struct {
*dns.Server
resolver *dns.Resolver
stack *stack.Stack
tcpListener net.Listener
udpEndpoint *dnsEndpoint
udpEndpointID *stack.TransportEndpointID
dnsServers []*dns.Server
tcpListeners []net.Listener
resolver *dns.Resolver
stack *stack.Stack
udpEndpoints []*dnsEndpoint
udpEndpointIDs []*stack.TransportEndpointID
tcpip.NICID
}
@ -66,6 +85,7 @@ func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pack
var msg D.Msg
msg.Unpack(pkt.Data().AsRange().ToOwnedView())
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)
}
@ -129,163 +149,276 @@ func (w *dnsResponseWriter) Close() error {
}
// 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) {
var v4 bool
func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijack []net.Addr, nicID tcpip.NICID) (*DNSServer, 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)
serverIn := &dns.Server{}
serverIn.SetHandler(handler)
// 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())
}
// 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)
tcpDnsArr := make([]net.TCPAddr, 0, len(dnsHijack))
udpDnsArr := make([]net.UDPAddr, 0, len(dnsHijack))
for _, d := range dnsHijack {
switch d.(type) {
case *net.TCPAddr:
{
tcpDnsArr = append(tcpDnsArr, *d.(*net.TCPAddr))
break
}
case *net.UDPAddr:
{
udpDnsArr = append(udpDnsArr, *d.(*net.UDPAddr))
break
}
}
}
endpoints, ids := hijackUdpDns(udpDnsArr, s, serverIn)
tcpListeners, dnsServers := hijackTcpDns(tcpDnsArr, s, serverIn)
server := &DNSServer{
Server: serverIn,
resolver: resolver,
stack: s,
tcpListener: tcpListener,
udpEndpoint: endpoint,
udpEndpointID: id,
NICID: nicID,
resolver: resolver,
stack: s,
udpEndpoints: endpoints,
udpEndpointIDs: ids,
NICID: nicID,
tcpListeners: tcpListeners,
}
server.SetHandler(handler)
server.Server.Server = &D.Server{Listener: tcpListener, Handler: server}
go func() {
server.ActivateAndServe()
}()
server.dnsServers = dnsServers
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
func (s *DNSServer) Stop() {
// shutdown TCP DNS Server
s.Server.Shutdown()
// remove TCP endpoint from stack
if s.Listener != nil {
s.Listener.Close()
if s == nil {
return
}
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
func (t *gvisorAdapter) DNSListen() string {
if t.dnsserver != nil {
id := t.dnsserver.udpEndpointID
return fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort)
// DnsHijack return the listening address of DNS Server
func (t *gvisorAdapter) DnsHijack() []string {
dnsHijackArr := make([]string, len(t.dnsServer.udpEndpoints))
for _, id := range t.dnsServer.udpEndpointIDs {
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) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addr string) error {
if addr == "" && t.dnsserver == nil {
return nil
}
func (t *gvisorAdapter) StopDNSServer() {
t.dnsServer.Stop()
log.Debugln("tun DNS server stoped")
t.dnsServer = nil
}
if addr == t.DNSListen() && t.dnsserver != nil && t.dnsserver.resolver == resolver {
return nil
}
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
}
// ReCreateDNSServer recreate the DNS Server on tun
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijackArr []string) error {
t.StopDNSServer()
if resolver == nil {
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 {
return err
}
server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID)
if err != nil {
return err
}
t.dnsserver = server
log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled())
t.dnsServer = server
return nil
}

View File

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

View File

@ -17,26 +17,26 @@ import (
)
type systemAdapter struct {
device dev.TunDevice
tun *tun2socket.Tun2Socket
lock sync.Mutex
stackName string
dnsListen string
autoRoute bool
device dev.TunDevice
tun *tun2socket.Tun2Socket
lock sync.Mutex
stackName string
dnsHackjack []string
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) {
adapter := &systemAdapter{
device: device,
stackName: conf.Stack,
dnsListen: conf.DNSListen,
autoRoute: conf.AutoRoute,
device: device,
stackName: conf.Stack,
dnsHackjack: conf.DnsHijack,
autoRoute: conf.AutoRoute,
}
adapter.lock.Lock()
defer adapter.lock.Unlock()
dnsHost, dnsPort, err := net.SplitHostPort(conf.DNSListen)
dnsHost, dnsPort, err := net.SplitHostPort(conf.DnsHijack[0])
if err != nil {
return nil, err
}
@ -91,8 +91,8 @@ func (t *systemAdapter) AutoRoute() bool {
return t.autoRoute
}
func (t *systemAdapter) DNSListen() string {
return t.dnsListen
func (t *systemAdapter) DnsHijack() []string {
return t.dnsHackjack
}
func (t *systemAdapter) Close() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package rules
package common
import (
"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 (
"fmt"

View File

@ -1,4 +1,4 @@
package rules
package common
import (
"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)
cached, hit := processCache.Get(key)
if !hit {
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
processCache.Set(key, "")
return false
if strings.TrimSpace(metadata.Process) == "" {
cached, hit := processCache.Get(key)
if !hit {
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err != nil {
processCache.Set(key, "")
return false
}
name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
if err != nil {
log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error())
}
processCache.Set(key, name)
cached = name
}
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
metadata.Process = cached.(string)
}
metadata.Process = cached.(string)
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 (
"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"
"time"
RC "github.com/Dreamacro/clash/rule/common"
"github.com/Dreamacro/clash/rule/logic"
RP "github.com/Dreamacro/clash/rule/provider"
)
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{
Network: findNetwork(params),
SourceIPs: findSourceIPs(params),
Network: RC.FindNetwork(params),
SourceIPs: RC.FindSourceIPs(params),
}
switch tp {
case "DOMAIN":
parsed = NewDomain(payload, target, ruleExtra)
parsed = RC.NewDomain(payload, target, ruleExtra)
case "DOMAIN-SUFFIX":
parsed = NewDomainSuffix(payload, target, ruleExtra)
parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
case "DOMAIN-KEYWORD":
parsed = NewDomainKeyword(payload, target, ruleExtra)
parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
case "GEOSITE":
parsed, parseErr = NewGEOSITE(payload, target, ruleExtra)
parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
case "GEOIP":
noResolve := HasNoResolve(params)
parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra)
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra)
case "IP-CIDR", "IP-CIDR6":
noResolve := HasNoResolve(params)
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve))
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
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":
parsed, parseErr = NewPort(payload, target, true, ruleExtra)
parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
case "DST-PORT":
parsed, parseErr = NewPort(payload, target, false, ruleExtra)
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
case "PROCESS-NAME":
parsed, parseErr = NewProcess(payload, target, ruleExtra)
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
case "MATCH":
parsed = NewMatch(target, ruleExtra)
parsed = RC.NewMatch(target, ruleExtra)
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:
parseErr = fmt.Errorf("unsupported rule type %s", tp)
}
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 (
"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 (
"encoding/json"
@ -75,6 +75,10 @@ func (rp *ruleSetProvider) Behavior() P.RuleType {
}
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
if rp.count == 0 {
return false
}
switch rp.behavior {
case P.Domain:
return rp.DomainRules.Search(metadata.Host) != nil
@ -201,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) {
return nil, errors.New("error rule type")
}
r, err := ParseRule(ruleType, rule, "", params)
r, err := parseRule(ruleType, rule, "", params)
if err != nil {
return nil, err
}

View File

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

View File

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

View File

@ -120,6 +120,42 @@ func TestClash_Snell(t *testing.T) {
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) {
cfg := &container.Config{
Image: ImageSnell,

View File

@ -5,6 +5,7 @@ package gun
import (
"bufio"
"context"
"crypto/tls"
"encoding/binary"
"errors"
@ -17,6 +18,7 @@ import (
"time"
"github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
"golang.org/x/net/http2"
@ -173,7 +175,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
}
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()
return nil, err
}

View File

@ -6,8 +6,10 @@ import (
"fmt"
"io"
"net"
"sync"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
)
@ -15,13 +17,19 @@ import (
const (
Version1 = 1
Version2 = 2
Version3 = 3
DefaultSnellVersion = Version1
// max packet length
maxLength = 0x3FFF
)
const (
CommandPing byte = 0
CommandConnect byte = 1
CommandConnectV2 byte = 5
CommandPing byte = 0
CommandConnect byte = 1
CommandConnectV2 byte = 5
CommandUDP byte = 6
CommondUDPForward byte = 1
CommandTunnel byte = 0
CommandPong byte = 1
@ -100,6 +108,16 @@ func WriteHeader(conn net.Conn, host string, port uint, version int) error {
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
func HalfClose(conn net.Conn) error {
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 {
var cipher shadowaead.Cipher
if version == Version2 {
if version != Version1 {
cipher = NewAES128GCM(psk)
} else {
cipher = NewChacha20Poly1305(psk)
}
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
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/binary"
@ -12,6 +13,7 @@ import (
"sync"
"github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
"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)
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
}

View File

@ -1,8 +1,11 @@
package vmess
import (
"context"
"crypto/tls"
"net"
C "github.com/Dreamacro/clash/constant"
)
type TLSConfig struct {
@ -19,6 +22,10 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
}
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
}

View File

@ -3,6 +3,7 @@ package tunnel
import (
"context"
"fmt"
R "github.com/Dreamacro/clash/rule/common"
"net"
"runtime"
"sync"
@ -15,7 +16,6 @@ import (
"github.com/Dreamacro/clash/constant/provider"
icontext "github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rule"
"github.com/Dreamacro/clash/tunnel/statistic"
)
@ -158,7 +158,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
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 {
case Direct:
proxy = proxies["DIRECT"]
@ -222,7 +222,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
}()
pCtx := icontext.NewPacketConnContext(metadata)
proxy, rule, err := resolveMetadata(pCtx, metadata)
proxy, rule, err := resolveMetadata(metadata)
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return
@ -244,15 +244,19 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
switch true {
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:
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:
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:
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:
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)
@ -276,7 +280,7 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
proxy, rule, err := resolveMetadata(connCtx, metadata)
proxy, rule, err := resolveMetadata(metadata)
if err != nil {
log.Warnln("[Metadata] parse failed: %s", err.Error())
return
@ -298,15 +302,19 @@ func handleTCPConn(connCtx C.ConnContext) {
switch true {
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:
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:
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:
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:
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)