Compare commits

..

30 Commits

Author SHA1 Message Date
8c3557e96b chore: support v2ray http upgrade server too 2023-11-03 13:58:53 +08:00
228990472d fix: avoid tls panic 2023-11-03 12:04:22 +08:00
09e7866a5c fix: gvisor panic 2023-11-03 11:50:25 +08:00
665ba7f9f1 chore: do websocket client upgrade directly instead of gobwas/ws 2023-11-03 11:50:25 +08:00
ee3038d5e4 chore: add SetupContextForConn for common/net 2023-11-03 11:50:25 +08:00
885ee7a820 fix: v2ray http upgrade Hosts header not working 2023-11-03 11:50:25 +08:00
ef303b11f2 action: trigger CMFA PR update in every commit 2023-11-02 16:01:35 +08:00
a82ce85707 chore: add route exclude support 2023-11-02 11:37:40 +08:00
5bfe7ba169 chore: better tls handshake 2023-11-02 11:22:01 +08:00
ceac5bfaa4 feat: add v2ray-http-upgrade support 2023-11-02 11:11:35 +08:00
b0638cfc49 chore: better bufio.Reader warp 2023-11-02 11:11:35 +08:00
96220aa8ea feat: cancel RULE-SET nested SUB-RULE restrictions 2023-10-31 11:10:38 +00:00
8ff476a3a1 fix: remote logic rules cannot be parsed (#837) 2023-10-31 19:07:01 +08:00
261b6e8dce action: small fix to cmfa core-update trigger 2023-10-30 20:00:15 +08:00
2b9141e0e5 chore: geo link replaced with github 2023-10-30 19:46:56 +08:00
55255faa52 chore: modify configuration fields 2023-10-27 17:49:12 +08:00
d42e3f74ad action: add question issue guidance 2023-10-26 19:08:42 +08:00
81a8a63861 build: more go120 build 2023-10-26 11:39:54 +08:00
c3a61e2db5 build: add go120 build for win7/8.1 2023-10-26 11:09:19 +08:00
bffe47a974 chore: netip.Prefix should not using pointer 2023-10-26 11:02:53 +08:00
4314b37d04 fix: dhcp not working on windows 2023-10-26 10:27:38 +08:00
cf93f69f40 chore: cleanup error using of dialer.DefaultInterface 2023-10-26 09:07:49 +08:00
55f626424f chore: better dns batchExchange 2023-10-25 20:16:44 +08:00
431d52f250 chore: system resolver can autoupdate 2023-10-25 19:21:20 +08:00
c1f24d8f0e chore: code cleanup 2023-10-25 18:07:45 +08:00
fc5a3cf80c action: ban black issues 2023-10-25 18:06:10 +08:00
e1e999180a chore: inMemoryAuthenticator unneed sync map 2023-10-24 21:25:03 +08:00
8755618910 fix: reality panic 2023-10-23 23:33:59 +08:00
aede97571f Merge branch 'Alpha' of https://github.com/MetaCubeX/Clash.Meta into Alpha 2023-10-23 17:02:08 +08:00
01bc84db02 chore: add labels to issue template 2023-10-23 17:02:04 +08:00
72 changed files with 913 additions and 851 deletions

View File

@ -1,6 +1,7 @@
name: Bug report name: Bug report
description: Create a report to help us improve description: Create a report to help us improve
title: "[Bug] " title: "[Bug] "
labels: ["bug"]
body: body:
- type: checkboxes - type: checkboxes
id: ensure id: ensure

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Clash.Meta Community Support
url: https://github.com/MetaCubeX/Clash.Meta/discussions
about: Please ask and answer questions about Clash.Meta here.

View File

@ -1,6 +1,7 @@
name: Feature request name: Feature request
description: Suggest an idea for this project description: Suggest an idea for this project
title: "[Feature] " title: "[Feature] "
labels: ["enhancement"]
body: body:
- type: checkboxes - type: checkboxes
id: ensure id: ensure

12
.github/rename-go120.sh vendored Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then
mv $FILENAME ${FILENAME}-go120
elif [[ $FILENAME =~ ".exe" ]];then
mv $FILENAME ${FILENAME%.*}-go120.exe
else echo "skip $FILENAME"
fi
done

View File

@ -49,3 +49,21 @@ jobs:
- name: Push changes - name: Push changes
run: | run: |
git push origin android-real --force git push origin android-real --force
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
trigger-CMFA-update:
needs: update-dependencies
runs-on: ubuntu-latest
steps:
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.MAINTAINER_APPID }}
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
- name: Trigger update-dependencies
run: |
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
-d '{"event_type": "core-updated"}'

View File

@ -69,6 +69,12 @@ jobs:
target: "darwin-amd64 darwin-arm64 android-arm64", target: "darwin-amd64 darwin-arm64 android-arm64",
id: "9", id: "9",
} }
# only for test
- { type: "WithoutCGO-GO120", target: "linux-amd64 linux-amd64-compatible",id: "1" }
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
- { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" }
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
- { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" }
- { type: "WithCGO", target: "windows/*", id: "1" } - { type: "WithCGO", target: "windows/*", id: "1" }
- { type: "WithCGO", target: "linux/386", id: "2" } - { type: "WithCGO", target: "linux/386", id: "2" }
- { type: "WithCGO", target: "linux/amd64", id: "3" } - { type: "WithCGO", target: "linux/amd64", id: "3" }
@ -126,18 +132,26 @@ jobs:
shell: bash shell: bash
- name: Setup Go - name: Setup Go
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: "1.21" go-version: "1.21"
check-latest: true check-latest: true
- name: Setup Go
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
uses: actions/setup-go@v4
with:
go-version: "1.20"
check-latest: true
- name: Test - name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }} if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
run: | run: |
go test ./... go test ./...
- name: Build WithoutCGO - name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }} if: ${{ matrix.job.type!='WithCGO' }}
env: env:
NAME: Clash.Meta NAME: Clash.Meta
BINDIR: bin BINDIR: bin
@ -185,6 +199,17 @@ jobs:
ls -la ls -la
cd .. cd ..
- name: Rename
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
run: |
cd bin
ls -la
cp ../.github/rename-go120.sh ./
bash ./rename-go120.sh
rm ./rename-go120.sh
ls -la
cd ..
- name: Zip - name: Zip
if: ${{ success() }} if: ${{ success() }}
run: | run: |

View File

@ -1,28 +0,0 @@
name: CMFA auto update-dependencies trigger
on:
workflow_dispatch:
push:
tags:
- "v*"
pull_request_target:
branches:
- Alpha
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.MAINTAINER_APPID }}
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
- name: Trigger update-dependencies
run: |
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
-d '{"event_type": "core-updated"}'
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies

View File

@ -12,8 +12,6 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP
metadata.Type = C.HTTP metadata.Type = C.HTTP
metadata.RawSrcAddr = srcConn.RemoteAddr()
metadata.RawDstAddr = srcConn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr())) ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...) ApplyAdditions(metadata, additions...)
return conn, metadata return conn, metadata

View File

@ -10,8 +10,6 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.UDP metadata.NetWork = C.UDP
metadata.Type = source metadata.Type = source
metadata.RawSrcAddr = packet.LocalAddr()
metadata.RawDstAddr = metadata.UDPAddr()
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr())) ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
if p, ok := packet.(C.UDPPacketInAddr); ok { if p, ok := packet.(C.UDPPacketInAddr); ok {
ApplyAdditions(metadata, WithInAddr(p.InAddr())) ApplyAdditions(metadata, WithInAddr(p.InAddr()))

View File

@ -58,14 +58,15 @@ type simpleObfsOption struct {
} }
type v2rayObfsOption struct { type v2rayObfsOption struct {
Mode string `obfs:"mode"` Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"` Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"` TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"` Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
} }
type shadowTLSOption struct { type shadowTLSOption struct {
@ -259,10 +260,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
obfsMode = opts.Mode obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{ v2rayOption = &v2rayObfs.Option{
Host: opts.Host, Host: opts.Host,
Path: opts.Path, Path: opts.Path,
Headers: opts.Headers, Headers: opts.Headers,
Mux: opts.Mux, Mux: opts.Mux,
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
} }
if opts.TLS { if opts.TLS {

View File

@ -53,9 +53,10 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
if t.option.Network == "ws" { if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr) host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{ wsOpts := &trojan.WebsocketOption{
Host: host, Host: host,
Port: port, Port: port,
Path: t.option.WSOpts.Path, Path: t.option.WSOpts.Path,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
} }
if t.option.SNI != "" { if t.option.SNI != "" {

View File

@ -93,6 +93,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{}, Headers: http.Header{},
} }

View File

@ -91,6 +91,7 @@ type WSOptions struct {
Headers map[string]string `proxy:"headers,omitempty"` Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"` MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
} }
// StreamConnContext implements C.ProxyAdapter // StreamConnContext implements C.ProxyAdapter
@ -110,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{}, Headers: http.Header{},
} }

View File

@ -1,62 +0,0 @@
package outboundgroup
import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type ProxyGroup interface {
C.ProxyAdapter
Providers() []provider.ProxyProvider
Proxies() []C.Proxy
Now() string
}
func (f *Fallback) Providers() []provider.ProxyProvider {
return f.providers
}
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
return lb.providers
}
func (f *Fallback) Proxies() []C.Proxy {
return f.GetProxies(false)
}
func (lb *LoadBalance) Proxies() []C.Proxy {
return lb.GetProxies(false)
}
func (lb *LoadBalance) Now() string {
return ""
}
func (r *Relay) Providers() []provider.ProxyProvider {
return r.providers
}
func (r *Relay) Proxies() []C.Proxy {
return r.GetProxies(false)
}
func (r *Relay) Now() string {
return ""
}
func (s *Selector) Providers() []provider.ProxyProvider {
return s.providers
}
func (s *Selector) Proxies() []C.Proxy {
return s.GetProxies(false)
}
func (u *URLTest) Providers() []provider.ProxyProvider {
return u.providers
}
func (u *URLTest) Proxies() []C.Proxy {
return u.GetProxies(false)
}

View File

@ -18,7 +18,6 @@ import (
const ( const (
defaultURLTestTimeout = time.Second * 5 defaultURLTestTimeout = time.Second * 5
defaultURLTestURL = "https://www.gstatic.com/generate_204"
) )
type HealthCheckOption struct { type HealthCheckOption struct {
@ -149,11 +148,6 @@ func (hc *HealthCheck) stop() {
} }
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 {
return
}
_, _, _ = hc.singleDo.Do(func() (struct{}, error) { _, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String() id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id) log.Debugln("Start New Health Checking {%s}", id)
@ -229,7 +223,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
if len(url) == 0 { if len(url) == 0 {
interval = 0 interval = 0
expectedStatus = nil expectedStatus = nil
url = defaultURLTestURL
} }
return &HealthCheck{ return &HealthCheck{

View File

@ -68,6 +68,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
case "http": case "http":
if schema.Path != "" { if schema.Path != "" {
path := C.Path.Resolve(schema.Path) path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path) vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else { } else {
path := C.Path.GetPathByHash("proxies", schema.URL) path := C.Path.GetPathByHash("proxies", schema.URL)

View File

@ -1,34 +0,0 @@
package provider
import (
"time"
)
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (pp *proxySetProvider) UpdatedAt() time.Time {
return pp.Fetcher.UpdatedAt
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
pp.Fetcher.Destroy()
return nil
}
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@ -12,6 +12,7 @@ func DefaultValue[T any]() T {
type TypedValue[T any] struct { type TypedValue[T any] struct {
value atomic.Value value atomic.Value
_ noCopy
} }
func (t *TypedValue[T]) Load() T { func (t *TypedValue[T]) Load() T {
@ -55,3 +56,9 @@ func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t) v.Store(t)
return return
} }
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}

View File

@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn {
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false} return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
} }
func WarpConnWithBioReader(c net.Conn, br *bufio.Reader) net.Conn {
if br != nil && br.Buffered() > 0 {
if bc, ok := c.(*BufferedConn); ok && bc.r == br {
return bc
}
return &BufferedConn{br, NewExtendedConn(c), true}
}
return c
}
// Reader returns the internal bufio.Reader. // Reader returns the internal bufio.Reader.
func (c *BufferedConn) Reader() *bufio.Reader { func (c *BufferedConn) Reader() *bufio.Reader {
return c.r return c.r

49
common/net/cached.go Normal file
View File

@ -0,0 +1,49 @@
package net
import (
"net"
"github.com/Dreamacro/clash/common/buf"
)
var _ ExtendedConn = (*CachedConn)(nil)
type CachedConn struct {
ExtendedConn
data []byte
}
func NewCachedConn(c net.Conn, data []byte) *CachedConn {
return &CachedConn{NewExtendedConn(c), data}
}
func (c *CachedConn) Read(b []byte) (n int, err error) {
if len(c.data) > 0 {
n = copy(b, c.data)
c.data = c.data[n:]
return
}
return c.ExtendedConn.Read(b)
}
func (c *CachedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy
if len(c.data) > 0 {
return buf.As(c.data)
}
return nil
}
func (c *CachedConn) Upstream() any {
return c.ExtendedConn
}
func (c *CachedConn) ReaderReplaceable() bool {
if len(c.data) > 0 {
return false
}
return true
}
func (c *CachedConn) WriterReplaceable() bool {
return true
}

31
common/net/context.go Normal file
View File

@ -0,0 +1,31 @@
package net
import (
"context"
"net"
)
// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine.
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
var (
quit = make(chan struct{})
interrupt = make(chan error, 1)
)
go func() {
select {
case <-quit:
interrupt <- nil
case <-ctx.Done():
// Close the connection, discarding the error
_ = conn.Close()
interrupt <- ctx.Err()
}
}()
return func(inputErr *error) {
close(quit)
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil {
// Return context error to user.
inputErr = &ctxErr
}
}
}

View File

@ -1,9 +1,5 @@
package auth package auth
import (
"github.com/puzpuzpuz/xsync/v2"
)
type Authenticator interface { type Authenticator interface {
Verify(user string, pass string) bool Verify(user string, pass string) bool
Users() []string Users() []string
@ -15,12 +11,12 @@ type AuthUser struct {
} }
type inMemoryAuthenticator struct { type inMemoryAuthenticator struct {
storage *xsync.MapOf[string, string] storage map[string]string
usernames []string usernames []string
} }
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
realPass, ok := au.storage.Load(user) realPass, ok := au.storage[user]
return ok && realPass == pass return ok && realPass == pass
} }
@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
if len(users) == 0 { if len(users) == 0 {
return nil return nil
} }
au := &inMemoryAuthenticator{
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()} storage: make(map[string]string),
for _, user := range users { usernames: make([]string, 0, len(users)),
au.storage.Store(user.User, user.Pass) }
for _, user := range users {
au.storage[user.User] = user.Pass
au.usernames = append(au.usernames, user.User)
} }
usernames := make([]string, 0, len(users))
au.storage.Range(func(key string, value string) bool {
usernames = append(usernames, key)
return true
})
au.usernames = usernames
return au return au
} }

View File

@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
listenAddr = "255.255.255.255:68" listenAddr = "255.255.255.255:68"
} }
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true)) options := []dialer.Option{
dialer.WithInterface(ifaceName),
dialer.WithAddrReuse(true),
}
// fallback bind on windows, because syscall bind can not receive broadcast
if runtime.GOOS == "windows" {
options = append(options, dialer.WithFallbackBind(true))
}
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
} }

View File

@ -3,6 +3,7 @@ package dialer
import ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, err return nil, err
} }
var addr *netip.Prefix var addr netip.Prefix
switch network { switch network {
case "udp4", "tcp4": case "udp4", "tcp4":
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, iface.ErrAddrNotFound return nil, iface.ErrAddrNotFound
} }
func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() {
return nil
}
local := uint64(0)
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.ParseUint(port, 10, 16)
}
}
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
}
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
port = "0"
}
local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
if err != nil {
return "", err
}
return addr.String(), nil
}
func fallbackParseNetwork(network string, addr netip.Addr) string {
// fix fallbackBindIfaceToListenConfig() force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
}

View File

@ -5,55 +5,16 @@ package dialer
import ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings"
) )
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() { return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
return nil
}
local := uint64(0)
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.ParseUint(port, 10, 16)
}
}
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
} }
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address) return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
if err != nil {
port = "0"
}
local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
if err != nil {
return "", err
}
return addr.String(), nil
} }
func ParseNetwork(network string, addr netip.Addr) string { func ParseNetwork(network string, addr netip.Addr) string {
// fix bindIfaceToListenConfig() force bind to an ipv4 address return fallbackParseNetwork(network, addr)
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
} }

View File

@ -70,14 +70,15 @@ func DialContext(ctx context.Context, network, address string, options ...Option
} }
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
if DefaultSocketHook != nil {
return listenPacketHooked(ctx, network, address)
}
cfg := applyOptions(options...) cfg := applyOptions(options...)
lc := &net.ListenConfig{} lc := &net.ListenConfig{}
if cfg.interfaceName != "" { if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) bind := bindIfaceToListenConfig
if cfg.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
addr, err := bind(cfg.interfaceName, lc, network, address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,9 +114,6 @@ func GetTcpConcurrent() bool {
} }
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
if DefaultSocketHook != nil {
return dialContextHooked(ctx, network, destination, port)
}
address := net.JoinHostPort(destination.String(), port) address := net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer netDialer := opt.netDialer
@ -131,7 +129,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
dialer := netDialer.(*net.Dialer) dialer := netDialer.(*net.Dialer)
if opt.interfaceName != "" { if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -20,6 +20,7 @@ type NetDialer interface {
type option struct { type option struct {
interfaceName string interfaceName string
fallbackBind bool
addrReuse bool addrReuse bool
routingMark int routingMark int
network int network int
@ -38,6 +39,12 @@ func WithInterface(name string) Option {
} }
} }
func WithFallbackBind(fallback bool) Option {
return func(opt *option) {
opt.fallbackBind = fallback
}
}
func WithAddrReuse(reuse bool) Option { func WithAddrReuse(reuse bool) Option {
return func(opt *option) { return func(opt *option) {
opt.addrReuse = reuse opt.addrReuse = reuse

View File

@ -1,37 +0,0 @@
package dialer
import (
"context"
"net"
"net/netip"
"syscall"
)
type SocketControl func(network, address string, conn syscall.RawConn) error
var DefaultSocketHook SocketControl
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
dialer := &net.Dialer{
Control: DefaultSocketHook,
}
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
if err != nil {
return nil, err
}
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
return conn, nil
}
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
lc := &net.ListenConfig{
Control: DefaultSocketHook,
}
return lc.ListenPacket(ctx, network, address)
}

View File

@ -36,7 +36,7 @@ type Pool struct {
cycle bool cycle bool
mux sync.Mutex mux sync.Mutex
host *trie.DomainTrie[struct{}] host *trie.DomainTrie[struct{}]
ipnet *netip.Prefix ipnet netip.Prefix
store store store store
} }
@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr {
} }
// IPNet return raw ipnet // IPNet return raw ipnet
func (p *Pool) IPNet() *netip.Prefix { func (p *Pool) IPNet() netip.Prefix {
return p.ipnet return p.ipnet
} }
@ -153,7 +153,7 @@ func (p *Pool) restoreState() {
} }
type Options struct { type Options struct {
IPNet *netip.Prefix IPNet netip.Prefix
Host *trie.DomainTrie[struct{}] Host *trie.DomainTrie[struct{}]
// Size sets the maximum number of entries in memory // Size sets the maximum number of entries in memory
@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) {
hostAddr = options.IPNet.Masked().Addr() hostAddr = options.IPNet.Masked().Addr()
gateway = hostAddr.Next() gateway = hostAddr.Next()
first = gateway.Next().Next().Next() // default start with 198.18.0.4 first = gateway.Next().Next().Next() // default start with 198.18.0.4
last = nnip.UnMasked(*options.IPNet) last = nnip.UnMasked(options.IPNet)
) )
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) { if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {

View File

@ -51,7 +51,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
func TestPool_Basic(t *testing.T) { func TestPool_Basic(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.0/28") ipnet := netip.MustParsePrefix("192.168.0.0/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) {
func TestPool_BasicV6(t *testing.T) { func TestPool_BasicV6(t *testing.T) {
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118") ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) {
func TestPool_Case_Insensitive(t *testing.T) { func TestPool_Case_Insensitive(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/29") ipnet := netip.MustParsePrefix("192.168.0.1/29")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) {
func TestPool_CycleUsed(t *testing.T) { func TestPool_CycleUsed(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.16/28") ipnet := netip.MustParsePrefix("192.168.0.16/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) {
tree := trie.New[struct{}]() tree := trie.New[struct{}]()
tree.Insert("example.com", struct{}{}) tree.Insert("example.com", struct{}{})
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
Host: tree, Host: tree,
}) })
@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) {
func TestPool_MaxCacheSize(t *testing.T) { func TestPool_MaxCacheSize(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24") ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
func TestPool_DoubleMapping(t *testing.T) { func TestPool_DoubleMapping(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24") ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) {
func TestPool_Clone(t *testing.T) { func TestPool_Clone(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24") ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) {
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
newPool, _ := New(Options{ newPool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
newPool.CloneFrom(pool) newPool.CloneFrom(pool)
@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) {
func TestPool_Error(t *testing.T) { func TestPool_Error(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/31") ipnet := netip.MustParsePrefix("192.168.0.1/31")
_, err := New(Options{ _, err := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) {
func TestPool_FlushFileCache(t *testing.T) { func TestPool_FlushFileCache(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/28") ipnet := netip.MustParsePrefix("192.168.0.1/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) {
func TestPool_FlushMemoryCache(t *testing.T) { func TestPool_FlushMemoryCache(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/28") ipnet := netip.MustParsePrefix("192.168.0.1/28")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })

View File

@ -13,7 +13,7 @@ import (
type Interface struct { type Interface struct {
Index int Index int
Name string Name string
Addrs []*netip.Prefix Addrs []netip.Prefix
HardwareAddr net.HardwareAddr HardwareAddr net.HardwareAddr
} }
@ -43,7 +43,7 @@ func ResolveInterface(name string) (*Interface, error) {
continue continue
} }
ipNets := make([]*netip.Prefix, 0, len(addrs)) ipNets := make([]netip.Prefix, 0, len(addrs))
for _, addr := range addrs { for _, addr := range addrs {
ipNet := addr.(*net.IPNet) ipNet := addr.(*net.IPNet)
ip, _ := netip.AddrFromSlice(ipNet.IP) ip, _ := netip.AddrFromSlice(ipNet.IP)
@ -59,7 +59,7 @@ func ResolveInterface(name string) (*Interface, error) {
} }
pf := netip.PrefixFrom(ip, ones) pf := netip.PrefixFrom(ip, ones)
ipNets = append(ipNets, &pf) ipNets = append(ipNets, pf)
} }
r[iface.Name] = &Interface{ r[iface.Name] = &Interface{
@ -89,27 +89,27 @@ func FlushCache() {
interfaces.Reset() interfaces.Reset()
} }
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) { func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool { return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
return addr.Addr().Is4() return addr.Addr().Is4()
}) })
} }
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) { func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool { return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
return addr.Addr().Is6() return addr.Addr().Is6()
}) })
} }
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) { func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
var fallback *netip.Prefix var fallback netip.Prefix
for _, addr := range iface.Addrs { for _, addr := range iface.Addrs {
if !accept(addr) { if !accept(addr) {
continue continue
} }
if fallback == nil && !addr.Addr().IsLinkLocalUnicast() { if !fallback.IsValid() && !addr.Addr().IsLinkLocalUnicast() {
fallback = addr fallback = addr
if !destination.IsValid() { if !destination.IsValid() {
@ -122,8 +122,8 @@ func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *net
} }
} }
if fallback == nil { if !fallback.IsValid() {
return nil, ErrAddrNotFound return netip.Prefix{}, ErrAddrNotFound
} }
return fallback, nil return fallback, nil

View File

@ -1,16 +0,0 @@
package mmdb
import "github.com/oschwald/maxminddb-golang"
func InstallOverride(override *maxminddb.Reader) {
newReader := Reader{Reader: override}
switch override.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
}
reader = newReader
}

View File

@ -1,14 +0,0 @@
package process
import "github.com/Dreamacro/clash/constant"
type PackageNameResolver func(metadata *constant.Metadata) (string, error)
var DefaultPackageNameResolver PackageNameResolver
func FindPackageName(metadata *constant.Metadata) (string, error) {
if resolver := DefaultPackageNameResolver; resolver != nil {
return resolver(metadata)
}
return "", ErrPlatformNotSupport
}

View File

@ -13,10 +13,6 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
) )
const (
minInterval = time.Minute * 5
)
var ( var (
fileMode os.FileMode = 0o666 fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755 dirMode os.FileMode = 0o755
@ -28,7 +24,8 @@ type Fetcher[V any] struct {
resourceType string resourceType string
name string name string
vehicle types.Vehicle vehicle types.Vehicle
UpdatedAt time.Time UpdatedAt *time.Time
ticker *time.Ticker
done chan struct{} done chan struct{}
hash [16]byte hash [16]byte
parser Parser[V] parser Parser[V]
@ -59,7 +56,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path()) buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime() modTime := stat.ModTime()
f.UpdatedAt = modTime f.UpdatedAt = &modTime
isLocal = true isLocal = true
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) { if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name()) log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
@ -67,7 +64,6 @@ func (f *Fetcher[V]) Initial() (V, error) {
} }
} else { } else {
buf, err = f.vehicle.Read() buf, err = f.vehicle.Read()
f.UpdatedAt = time.Now()
} }
if err != nil { if err != nil {
@ -117,7 +113,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
f.hash = md5.Sum(buf) f.hash = md5.Sum(buf)
// pull contents automatically // pull contents automatically
if f.interval > 0 { if f.ticker != nil {
go f.pullLoop() go f.pullLoop()
} }
@ -133,7 +129,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
now := time.Now() now := time.Now()
hash := md5.Sum(buf) hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) { if bytes.Equal(f.hash[:], hash[:]) {
f.UpdatedAt = now f.UpdatedAt = &now
_ = os.Chtimes(f.vehicle.Path(), now, now) _ = os.Chtimes(f.vehicle.Path(), now, now)
return lo.Empty[V](), true, nil return lo.Empty[V](), true, nil
} }
@ -149,31 +145,23 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
} }
} }
f.UpdatedAt = now f.UpdatedAt = &now
f.hash = hash f.hash = hash
return contents, false, nil return contents, false, nil
} }
func (f *Fetcher[V]) Destroy() error { func (f *Fetcher[V]) Destroy() error {
if f.interval > 0 { if f.ticker != nil {
f.done <- struct{}{} f.done <- struct{}{}
} }
return nil return nil
} }
func (f *Fetcher[V]) pullLoop() { func (f *Fetcher[V]) pullLoop() {
initialInterval := f.interval - time.Since(f.UpdatedAt)
if initialInterval < minInterval {
initialInterval = minInterval
}
timer := time.NewTimer(initialInterval)
defer timer.Stop()
for { for {
select { select {
case <-timer.C: case <-f.ticker.C:
timer.Reset(f.interval)
elm, same, err := f.Update() elm, same, err := f.Update()
if err != nil { if err != nil {
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
@ -190,6 +178,7 @@ func (f *Fetcher[V]) pullLoop() {
f.OnUpdate(elm) f.OnUpdate(elm)
} }
case <-f.done: case <-f.done:
f.ticker.Stop()
return return
} }
} }
@ -208,12 +197,17 @@ func safeWrite(path string, buf []byte) error {
} }
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] { func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
return &Fetcher[V]{ return &Fetcher[V]{
name: name, name: name,
ticker: ticker,
vehicle: vehicle, vehicle: vehicle,
parser: parser, parser: parser,
done: make(chan struct{}, 8), done: make(chan struct{}, 1),
OnUpdate: onUpdate, OnUpdate: onUpdate,
interval: interval, interval: interval,
} }

View File

@ -43,7 +43,8 @@ type RealityConfig struct {
func aesgcmPreferred(ciphers []uint16) bool func aesgcmPreferred(ciphers []uint16) bool
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists { retry := 0
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
verifier := &realityVerifier{ verifier := &realityVerifier{
serverName: tlsConfig.ServerName, serverName: tlsConfig.ServerName,
} }
@ -80,7 +81,15 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16]) //log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:]) ecdheParams := uConn.HandshakeState.State13.EcdheParams
if ecdheParams == nil {
// WTF???
if retry > 2 {
return nil, errors.New("nil ecdheParams")
}
continue // retry
}
authKey := ecdheParams.SharedKey(realityConfig.PublicKey[:])
if authKey == nil { if authKey == nil {
return nil, errors.New("nil auth_key") return nil, errors.New("nil auth_key")
} }

View File

@ -21,7 +21,7 @@ type UClientHelloID struct {
var initRandomFingerprint UClientHelloID var initRandomFingerprint UClientHelloID
var initUtlsClient string var initUtlsClient string
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) net.Conn { func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn {
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{ utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
Client: fingerprint.Client, Client: fingerprint.Client,
Version: fingerprint.Version, Version: fingerprint.Version,

View File

@ -20,7 +20,6 @@ import (
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@ -59,6 +58,7 @@ type General struct {
TCPConcurrent bool `json:"tcp-concurrent"` TCPConcurrent bool `json:"tcp-concurrent"`
FindProcessMode P.FindProcessMode `json:"find-process-mode"` FindProcessMode P.FindProcessMode `json:"find-process-mode"`
Sniffing bool `json:"sniffing"` Sniffing bool `json:"sniffing"`
EBpf EBpf `json:"-"`
GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalClientFingerprint string `json:"global-client-fingerprint"`
GlobalUA string `json:"global-ua"` GlobalUA string `json:"global-ua"`
} }
@ -122,7 +122,7 @@ type DNS struct {
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"` GeoIPCode string `yaml:"geoip-code"`
IPCIDR []*netip.Prefix `yaml:"ipcidr"` IPCIDR []netip.Prefix `yaml:"ipcidr"`
Domain []string `yaml:"domain"` Domain []string `yaml:"domain"`
GeoSite []*router.DomainMatcher `yaml:"geosite"` GeoSite []*router.DomainMatcher `yaml:"geosite"`
} }
@ -193,34 +193,29 @@ type RawNTP struct {
} }
type RawDNS struct { type RawDNS struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable"`
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` PreferH3 bool `yaml:"prefer-h3"`
IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6 bool `yaml:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` IPv6Timeout uint `yaml:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts" json:"use-hosts"` UseHosts bool `yaml:"use-hosts"`
NameServer []string `yaml:"nameserver" json:"nameserver"` NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback" json:"fallback"` Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen" json:"listen"` Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"` EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"` FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]any `yaml:"nameserver-policy" json:"nameserver-policy"` NameServerPolicy map[string]any `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"` ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
} }
type RawFallbackFilter struct { type RawFallbackFilter struct {
GeoIP bool `yaml:"geoip" json:"geoip"` GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code" json:"geoip-code"` GeoIPCode string `yaml:"geoip-code"`
IPCIDR []string `yaml:"ipcidr" json:"ipcidr"` IPCIDR []string `yaml:"ipcidr"`
Domain []string `yaml:"domain" json:"domain"` Domain []string `yaml:"domain"`
GeoSite []string `yaml:"geosite" json:"geosite"` GeoSite []string `yaml:"geosite"`
}
type RawClashForAndroid struct {
AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"`
UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"`
} }
type RawTun struct { type RawTun struct {
@ -234,20 +229,22 @@ type RawTun struct {
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"` //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
Inet4RouteAddress []netip.Prefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"`
Inet6RouteAddress []netip.Prefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
} }
type RawTuicServer struct { type RawTuicServer struct {
@ -266,23 +263,23 @@ type RawTuicServer struct {
} }
type RawConfig struct { type RawConfig struct {
Port int `yaml:"port" json:"port"` Port int `yaml:"port"`
SocksPort int `yaml:"socks-port" json:"socks-port"` SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port" json:"redir-port"` RedirPort int `yaml:"redir-port"`
TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"` TProxyPort int `yaml:"tproxy-port"`
MixedPort int `yaml:"mixed-port" json:"mixed-port"` MixedPort int `yaml:"mixed-port"`
ShadowSocksConfig string `yaml:"ss-config"` ShadowSocksConfig string `yaml:"ss-config"`
VmessConfig string `yaml:"vmess-config"` VmessConfig string `yaml:"vmess-config"`
InboundTfo bool `yaml:"inbound-tfo"` InboundTfo bool `yaml:"inbound-tfo"`
InboundMPTCP bool `yaml:"inbound-mptcp"` InboundMPTCP bool `yaml:"inbound-mptcp"`
Authentication []string `yaml:"authentication" json:"authentication"` Authentication []string `yaml:"authentication"`
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"` SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
AllowLan bool `yaml:"allow-lan" json:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address" json:"bind-address"` BindAddress string `yaml:"bind-address"`
Mode T.TunnelMode `yaml:"mode" json:"mode"` Mode T.TunnelMode `yaml:"mode"`
UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"` UnifiedDelay bool `yaml:"unified-delay"`
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` LogLevel log.LogLevel `yaml:"log-level"`
IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6 bool `yaml:"ipv6"`
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalControllerTLS string `yaml:"external-controller-tls"`
ExternalUI string `yaml:"external-ui"` ExternalUI string `yaml:"external-ui"`
@ -292,20 +289,20 @@ type RawConfig struct {
Interface string `yaml:"interface-name"` Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"` RoutingMark int `yaml:"routing-mark"`
Tunnels []LC.Tunnel `yaml:"tunnels"` Tunnels []LC.Tunnel `yaml:"tunnels"`
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"` GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"` GeodataLoader string `yaml:"geodata-loader"`
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
GlobalClientFingerprint string `yaml:"global-client-fingerprint"` GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
GlobalUA string `yaml:"global-ua"` GlobalUA string `yaml:"global-ua"`
KeepAliveInterval int `yaml:"keep-alive-interval"` KeepAliveInterval int `yaml:"keep-alive-interval"`
Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]any `yaml:"hosts" json:"hosts"` Hosts map[string]any `yaml:"hosts"`
NTP RawNTP `yaml:"ntp" json:"ntp"` NTP RawNTP `yaml:"ntp"`
DNS RawDNS `yaml:"dns" json:"dns"` DNS RawDNS `yaml:"dns"`
Tun RawTun `yaml:"tun"` Tun RawTun `yaml:"tun"`
TuicServer RawTuicServer `yaml:"tuic-server"` TuicServer RawTuicServer `yaml:"tuic-server"`
EBpf EBpf `yaml:"ebpf"` EBpf EBpf `yaml:"ebpf"`
@ -319,8 +316,6 @@ type RawConfig struct {
SubRules map[string][]string `yaml:"sub-rules"` SubRules map[string][]string `yaml:"sub-rules"`
RawTLS TLS `yaml:"tls"` RawTLS TLS `yaml:"tls"`
Listeners []map[string]any `yaml:"listeners"` Listeners []map[string]any `yaml:"listeners"`
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
} }
type GeoXUrl struct { type GeoXUrl struct {
@ -468,9 +463,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
StoreSelected: true, StoreSelected: true,
}, },
GeoXUrl: GeoXUrl{ GeoXUrl: GeoXUrl{
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb", Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
}, },
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
} }
@ -488,6 +483,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
startTime := time.Now() startTime := time.Now()
config.Experimental = &rawCfg.Experimental config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile config.Profile = &rawCfg.Profile
config.IPTables = &rawCfg.IPTables
config.TLS = &rawCfg.RawTLS config.TLS = &rawCfg.RawTLS
general, err := parseGeneral(rawCfg) general, err := parseGeneral(rawCfg)
@ -548,6 +544,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.DNS = dnsCfg config.DNS = dnsCfg
err = parseTun(rawCfg.Tun, config.General)
if err != nil {
return nil, err
}
err = parseTuicServer(rawCfg.TuicServer, config.General) err = parseTuicServer(rawCfg.TuicServer, config.General)
if err != nil { if err != nil {
return nil, err return nil, err
@ -644,6 +645,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode, FindProcessMode: cfg.FindProcessMode,
EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalClientFingerprint: cfg.GlobalClientFingerprint,
GlobalUA: cfg.GlobalUA, GlobalUA: cfg.GlobalUA,
}, nil }, nil
@ -1047,7 +1049,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyName: proxyName, ProxyName: proxyName,
Interface: dialer.DefaultInterface,
Params: params, Params: params,
PreferH3: preferH3, PreferH3: preferH3,
}, },
@ -1149,15 +1150,15 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
return policy, nil return policy, nil
} }
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) { func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
var ipNets []*netip.Prefix var ipNets []netip.Prefix
for idx, ip := range ips { for idx, ip := range ips {
ipnet, err := netip.ParsePrefix(ip) ipnet, err := netip.ParsePrefix(ip)
if err != nil { if err != nil {
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
} }
ipNets = append(ipNets, &ipnet) ipNets = append(ipNets, ipnet)
} }
return ipNets, nil return ipNets, nil
@ -1225,7 +1226,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode, EnhancedMode: cfg.EnhancedMode,
FallbackFilter: FallbackFilter{ FallbackFilter: FallbackFilter{
IPCIDR: []*netip.Prefix{}, IPCIDR: []netip.Prefix{},
GeoSite: []*router.DomainMatcher{}, GeoSite: []*router.DomainMatcher{},
}, },
} }
@ -1299,7 +1300,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
} }
pool, err := fakeip.New(fakeip.Options{ pool, err := fakeip.New(fakeip.Options{
IPNet: &fakeIPRange, IPNet: fakeIPRange,
Size: 1000, Size: 1000,
Host: host, Host: host,
Persistence: rawCfg.Profile.StoreFakeIP, Persistence: rawCfg.Profile.StoreFakeIP,
@ -1362,22 +1363,24 @@ func parseTun(rawTun RawTun, general *General) error {
AutoDetectInterface: rawTun.AutoDetectInterface, AutoDetectInterface: rawTun.AutoDetectInterface,
RedirectToTun: rawTun.RedirectToTun, RedirectToTun: rawTun.RedirectToTun,
MTU: rawTun.MTU, MTU: rawTun.MTU,
Inet4Address: []netip.Prefix{tunAddressPrefix}, Inet4Address: []netip.Prefix{tunAddressPrefix},
Inet6Address: rawTun.Inet6Address, Inet6Address: rawTun.Inet6Address,
StrictRoute: rawTun.StrictRoute, StrictRoute: rawTun.StrictRoute,
Inet4RouteAddress: rawTun.Inet4RouteAddress, Inet4RouteAddress: rawTun.Inet4RouteAddress,
Inet6RouteAddress: rawTun.Inet6RouteAddress, Inet6RouteAddress: rawTun.Inet6RouteAddress,
IncludeUID: rawTun.IncludeUID, Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
IncludeUIDRange: rawTun.IncludeUIDRange, Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
ExcludeUID: rawTun.ExcludeUID, IncludeUID: rawTun.IncludeUID,
ExcludeUIDRange: rawTun.ExcludeUIDRange, IncludeUIDRange: rawTun.IncludeUIDRange,
IncludeAndroidUser: rawTun.IncludeAndroidUser, ExcludeUID: rawTun.ExcludeUID,
IncludePackage: rawTun.IncludePackage, ExcludeUIDRange: rawTun.ExcludeUIDRange,
ExcludePackage: rawTun.ExcludePackage, IncludeAndroidUser: rawTun.IncludeAndroidUser,
EndpointIndependentNat: rawTun.EndpointIndependentNat, IncludePackage: rawTun.IncludePackage,
UDPTimeout: rawTun.UDPTimeout, ExcludePackage: rawTun.ExcludePackage,
FileDescriptor: rawTun.FileDescriptor, EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout,
FileDescriptor: rawTun.FileDescriptor,
} }
return nil return nil

View File

@ -2,7 +2,7 @@ package constant
var ( var (
GeodataMode bool GeodataMode bool
GeoIpUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat" GeoIpUrl string
MmdbUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb" MmdbUrl string
GeoSiteUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat" GeoSiteUrl string
) )

View File

@ -147,9 +147,6 @@ type Metadata struct {
SpecialProxy string `json:"specialProxy"` SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"` SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"` RemoteDst string `json:"remoteDestination"`
RawSrcAddr net.Addr `json:"-"`
RawDstAddr net.Addr `json:"-"`
// Only domain rule // Only domain rule
SniffHost string `json:"sniffHost"` SniffHost string `json:"sniffHost"`
} }

View File

@ -8,7 +8,6 @@ import (
"net/netip" "net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -23,7 +22,7 @@ type client struct {
r *Resolver r *Resolver
port string port string
host string host string
iface atomic.TypedValue[string] iface string
proxyAdapter C.ProxyAdapter proxyAdapter C.ProxyAdapter
proxyName string proxyName string
addr string addr string
@ -48,10 +47,6 @@ func (c *client) Address() string {
return c.addr return c.addr
} }
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
return c.ExchangeContext(context.Background(), m)
}
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
var ( var (
ip netip.Addr ip netip.Addr
@ -78,8 +73,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
} }
var options []dialer.Option var options []dialer.Option
if c.iface.Load() != "" { if c.iface != "" {
options = append(options, dialer.WithInterface(c.iface.Load())) options = append(options, dialer.WithInterface(c.iface))
} }
conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port)) conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))

View File

@ -1,6 +1,3 @@
//go:build disabled
// +build disabled
package dns package dns
import ( import (
@ -11,11 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/dhcp" "github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -32,7 +26,7 @@ type dhcpClient struct {
ifaceInvalidate time.Time ifaceInvalidate time.Time
dnsInvalidate time.Time dnsInvalidate time.Time
ifaceAddr *netip.Prefix ifaceAddr netip.Prefix
done chan struct{} done chan struct{}
clients []dnsClient clients []dnsClient
err error err error
@ -49,13 +43,6 @@ func (d *dhcpClient) Address() string {
return strings.Join(addrs, ",") return strings.Join(addrs, ",")
} }
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
return d.ExchangeContext(ctx, m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
clients, err := d.resolve(ctx) clients, err := d.resolve(ctx)
if err != nil { if err != nil {
@ -89,7 +76,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
for _, item := range dns { for _, item := range dns {
nameserver = append(nameserver, NameServer{ nameserver = append(nameserver, NameServer{
Addr: net.JoinHostPort(item.String(), "53"), Addr: net.JoinHostPort(item.String(), "53"),
Interface: atomic.NewTypedValue(d.ifaceName), Interface: d.ifaceName,
}) })
} }

View File

@ -157,11 +157,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
return msg, err return msg, err
} }
// Exchange implements the Upstream interface for *dnsOverHTTPS.
func (doh *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) {
return doh.ExchangeContext(context.Background(), m)
}
// Close implements the Upstream interface for *dnsOverHTTPS. // Close implements the Upstream interface for *dnsOverHTTPS.
func (doh *dnsOverHTTPS) Close() (err error) { func (doh *dnsOverHTTPS) Close() (err error) {
doh.clientMu.Lock() doh.clientMu.Lock()

View File

@ -134,11 +134,6 @@ func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.M
return msg, err return msg, err
} }
// Exchange implements the Upstream interface for *dnsOverQUIC.
func (doq *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return doq.ExchangeContext(context.Background(), m)
}
// Close implements the Upstream interface for *dnsOverQUIC. // Close implements the Upstream interface for *dnsOverQUIC.
func (doq *dnsOverQUIC) Close() (err error) { func (doq *dnsOverQUIC) Close() (err error) {
doq.connMu.Lock() doq.connMu.Lock()

View File

@ -45,7 +45,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
} }
type ipnetFilter struct { type ipnetFilter struct {
ipnet *netip.Prefix ipnet netip.Prefix
} }
func (inf *ipnetFilter) Match(ip netip.Addr) bool { func (inf *ipnetFilter) Match(ip netip.Addr) bool {

View File

@ -1,79 +0,0 @@
package dns
import (
"context"
D "github.com/miekg/dns"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/resolver"
)
const SystemDNSPlaceholder = "system"
var systemResolver *Resolver
var isolateHandler handler
var _ dnsClient = (*dhcpClient)(nil)
type dhcpClient struct {
enable bool
}
func (d *dhcpClient) Address() string {
return SystemDNSPlaceholder
}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return d.ExchangeContext(context.Background(), m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if s := systemResolver; s != nil {
return s.ExchangeContext(ctx, m)
}
return nil, dhcp.ErrNotFound
}
func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
if h := isolateHandler; h != nil {
return handlerWithContext(context.Background(), h, msg)
}
return nil, D.ErrTime
}
func FlushCacheWithDefaultResolver() {
if r := resolver.DefaultResolver; r != nil {
r.(*Resolver).lruCache = cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true))
}
}
func UpdateSystemDNS(addr []string) {
if len(addr) == 0 {
systemResolver = nil
}
ns := make([]NameServer, 0, len(addr))
for _, d := range addr {
ns = append(ns, NameServer{Addr: d})
}
systemResolver = NewResolver(Config{Main: ns})
}
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
if resolver == nil {
isolateHandler = nil
return
}
isolateHandler = NewHandler(resolver, mapper)
}
func newDHCPClient(ifaceName string) *dhcpClient {
return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder}
}

View File

@ -39,16 +39,12 @@ type rcodeClient struct {
var _ dnsClient = rcodeClient{} var _ dnsClient = rcodeClient{}
func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) { func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
m.Response = true m.Response = true
m.Rcode = r.rcode m.Rcode = r.rcode
return m, nil return m, nil
} }
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
return r.Exchange(m)
}
func (r rcodeClient) Address() string { func (r rcodeClient) Address() string {
return r.addr return r.addr
} }

View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@ -23,7 +22,6 @@ import (
) )
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
Address() string Address() string
} }
@ -136,11 +134,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
return false return false
} }
// Exchange a batch of dns request, and it use cache
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return r.ExchangeContext(context.Background(), m)
}
// ExchangeContext a batch of dns request with context.Context, and it use cache // ExchangeContext a batch of dns request with context.Context, and it use cache
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 { if len(m.Question) == 0 {
@ -210,10 +203,10 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
} }
if matched := r.matchPolicy(m); len(matched) != 0 { if matched := r.matchPolicy(m); len(matched) != 0 {
result, cache, err = r.batchExchange(ctx, matched, m) result, cache, err = batchExchange(ctx, matched, m)
return return
} }
result, cache, err = r.batchExchange(ctx, r.main, m) result, cache, err = batchExchange(ctx, r.main, m)
return return
} }
@ -255,13 +248,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
return return
} }
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
defer cancel()
return batchExchange(ctx, clients, m)
}
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient { func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
if r.policy == nil { if r.policy == nil {
return nil return nil
@ -385,7 +371,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result { func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result, 1) ch := make(chan *result, 1)
go func() { go func() {
res, _, err := r.batchExchange(ctx, client, msg) res, _, err := batchExchange(ctx, client, msg)
ch <- &result{Msg: res, Error: err} ch <- &result{Msg: res, Error: err}
}() }()
return ch return ch
@ -402,7 +388,7 @@ func (r *Resolver) Invalid() bool {
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
Interface atomic.TypedValue[string] Interface string
ProxyAdapter C.ProxyAdapter ProxyAdapter C.ProxyAdapter
ProxyName string ProxyName string
Params map[string]string Params map[string]string
@ -412,7 +398,7 @@ type NameServer struct {
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool GeoIP bool
GeoIPCode string GeoIPCode string
IPCIDR []*netip.Prefix IPCIDR []netip.Prefix
Domain []string Domain []string
GeoSite []*router.DomainMatcher GeoSite []*router.DomainMatcher
} }

View File

@ -49,7 +49,6 @@ func (s *Server) SetHandler(handler handler) {
} }
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) { func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
UpdateIsolateHandler(resolver, mapper)
if addr == address && resolver != nil { if addr == address && resolver != nil {
handler := NewHandler(resolver, mapper) handler := NewHandler(resolver, mapper)
server.SetHandler(handler) server.SetHandler(handler)

View File

@ -1,23 +1,113 @@
package dns package dns
import ( import (
"context"
"fmt"
"net" "net"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
"golang.org/x/exp/slices"
) )
func loadSystemResolver() (clients []dnsClient, err error) { const (
nameservers, err := dnsReadConfig() SystemDnsFlushTime = 5 * time.Minute
SystemDnsDeleteTimes = 12 // 12*5 = 60min
)
type systemDnsClient struct {
disableTimes uint32
dnsClient
}
type systemClient struct {
mu sync.Mutex
dnsClients map[string]*systemDnsClient
lastFlush time.Time
}
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
c.mu.Lock()
defer c.mu.Unlock()
var err error
if time.Since(c.lastFlush) > SystemDnsFlushTime {
var nameservers []string
if nameservers, err = dnsReadConfig(); err == nil {
log.Debugln("[DNS] system dns update to %s", nameservers)
for _, addr := range nameservers {
if _, ok := c.dnsClients[addr]; !ok {
clients := transform(
[]NameServer{{
Addr: net.JoinHostPort(addr, "53"),
Net: "udp",
}},
nil,
)
if len(clients) > 0 {
c.dnsClients[addr] = &systemDnsClient{
disableTimes: 0,
dnsClient: clients[0],
}
}
}
}
available := 0
for nameserver, sdc := range c.dnsClients {
if slices.Contains(nameservers, nameserver) {
sdc.disableTimes = 0 // enable
available++
} else {
if sdc.disableTimes > SystemDnsDeleteTimes {
delete(c.dnsClients, nameserver) // drop too old dnsClient
} else {
sdc.disableTimes++
}
}
}
if available > 0 {
c.lastFlush = time.Now()
}
}
}
dnsClients := make([]dnsClient, 0, len(c.dnsClients))
for _, sdc := range c.dnsClients {
if sdc.disableTimes == 0 {
dnsClients = append(dnsClients, sdc.dnsClient)
}
}
if len(dnsClients) > 0 {
return dnsClients, nil
}
return nil, err
}
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
dnsClients, err := c.getDnsClients()
if err != nil { if err != nil {
return return
} }
if len(nameservers) == 0 { msg, _, err = batchExchange(ctx, dnsClients, m)
return return
} }
servers := make([]NameServer, 0, len(nameservers))
for _, addr := range nameservers { // Address implements dnsClient
servers = append(servers, NameServer{ func (c *systemClient) Address() string {
Addr: net.JoinHostPort(addr, "53"), dnsClients, _ := c.getDnsClients()
Net: "udp", addrs := make([]string, 0, len(dnsClients))
}) for _, c := range dnsClients {
} addrs = append(addrs, c.Address())
return transform(servers, nil), nil }
return fmt.Sprintf("system(%s)", strings.Join(addrs, ","))
}
var _ dnsClient = (*systemClient)(nil)
func newSystemClient() *systemClient {
return &systemClient{
dnsClients: map[string]*systemDnsClient{},
}
} }

View File

@ -107,16 +107,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))
continue continue
case "system": case "system":
clients, err := loadSystemResolver() ret = append(ret, newSystemClient())
if err != nil {
log.Errorln("[DNS:system] load system resolver failed: %s", err.Error())
continue
}
if len(clients) == 0 {
log.Errorln("[DNS:system] no nameserver found in system")
continue
}
ret = append(ret, clients...)
continue continue
case "rcode": case "rcode":
ret = append(ret, newRCodeClient(s.Addr)) ret = append(ret, newRCodeClient(s.Addr))
@ -289,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
DstPort: uint16(uintPort), DstPort: uint16(uintPort),
} }
if proxyAdapter == nil { if proxyAdapter == nil {
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
} }
if !proxyAdapter.SupportUDP() { if !proxyAdapter.SupportUDP() {
@ -299,14 +290,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...) return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
} }
var errIPNotFound = errors.New("couldn't find ip")
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
cache = true cache = true
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
defer fast.Close() defer fast.Close()
domain := msgToDomain(m) domain := msgToDomain(m)
var noIpMsg *D.Msg
for _, client := range clients { for _, client := range clients {
if _, isRCodeClient := client.(rcodeClient); isRCodeClient { if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
msg, err = client.Exchange(m) msg, err = client.ExchangeContext(ctx, m)
return msg, false, err return msg, false, err
} }
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
@ -320,13 +314,31 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
// so we would ignore RCode errors from RCode clients. // so we would ignore RCode errors from RCode clients.
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode]) return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
} }
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address()) if ips := msgToIP(m); len(m.Question) > 0 {
qType := m.Question[0].Qtype
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address())
switch qType {
case D.TypeAAAA:
if len(ips) == 0 {
noIpMsg = m
return nil, errIPNotFound
}
case D.TypeA:
if len(ips) == 0 {
noIpMsg = m
return nil, errIPNotFound
}
}
}
return m, nil return m, nil
}) })
} }
msg = fast.Wait() msg = fast.Wait()
if msg == nil { if msg == nil {
if noIpMsg != nil {
return noIpMsg, false, nil
}
err = errors.New("all DNS requests failed") err = errors.New("all DNS requests failed")
if fErr := fast.Error(); fErr != nil { if fErr := fast.Error(); fErr != nil {
err = fmt.Errorf("%w, first error: %w", err, fErr) err = fmt.Errorf("%w, first error: %w", err, fErr)

View File

@ -352,16 +352,17 @@ proxies: # socks5
plugin: v2ray-plugin plugin: v2ray-plugin
plugin-opts: plugin-opts:
mode: websocket # no QUIC now mode: websocket # no QUIC now
# tls: true # wss # tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果 # 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx # fingerprint: xxxx
# skip-cert-verify: true # skip-cert-verify: true
# host: bing.com # host: bing.com
# path: "/" # path: "/"
# mux: true # mux: true
# headers: # headers:
# custom: value # custom: value
# v2ray-http-upgrade: false
- name: "ss4-shadow-tls" - name: "ss4-shadow-tls"
type: ss type: ss
@ -434,11 +435,12 @@ proxies: # socks5
# servername: example.com # priority over wss host # servername: example.com # priority over wss host
# network: ws # network: ws
# ws-opts: # ws-opts:
# path: /path # path: /path
# headers: # headers:
# Host: v2ray.com # Host: v2ray.com
# max-early-data: 2048 # max-early-data: 2048
# early-data-header-name: Sec-WebSocket-Protocol # early-data-header-name: Sec-WebSocket-Protocol
# v2ray-http-upgrade: false
- name: "vmess-h2" - name: "vmess-h2"
type: vmess type: vmess
@ -566,6 +568,7 @@ proxies: # socks5
path: "/" path: "/"
headers: headers:
Host: example.com Host: example.com
# v2ray-http-upgrade: false
# Trojan # Trojan
- name: "trojan" - name: "trojan"
@ -606,9 +609,10 @@ proxies: # socks5
# fingerprint: xxxx # fingerprint: xxxx
udp: true udp: true
# ws-opts: # ws-opts:
# path: /path # path: /path
# headers: # headers:
# Host: example.com # Host: example.com
# v2ray-http-upgrade: false
- name: "trojan-xtls" - name: "trojan-xtls"
type: trojan type: trojan

3
go.mod
View File

@ -23,7 +23,7 @@ require (
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966 github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
github.com/metacubex/sing-shadowsocks v0.2.5 github.com/metacubex/sing-shadowsocks v0.2.5
github.com/metacubex/sing-shadowsocks2 v0.1.4 github.com/metacubex/sing-shadowsocks2 v0.1.4
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700 github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170
github.com/miekg/dns v1.1.56 github.com/miekg/dns v1.1.56
@ -101,6 +101,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.3.0 // indirect go.uber.org/mock v0.3.0 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect

6
go.sum
View File

@ -107,8 +107,8 @@ github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo= github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE= github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k= github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700 h1:JToLa8cxHrd6tOUHWCg9YM+o/4MXmjgagG909itmnyE= github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700/go.mod h1:atkIOs6Y5NeUzstK5SBvnrFo4z1JLuORhEfQECEVUpI= github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY= github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM= github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk= github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
@ -211,6 +211,8 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=

View File

@ -172,7 +172,7 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
listener.ReCreateHTTP(general.Port, tunnel.Tunnel) listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel) listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel) listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
// listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel) listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel) listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel) listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel) listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)

View File

@ -70,20 +70,22 @@ type tunSchema struct {
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` //Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"` ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"` ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"` IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
} }
type tuicServerSchema struct { type tuicServerSchema struct {
@ -148,6 +150,18 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.Inet6Address != nil { if p.Inet6Address != nil {
def.Inet6Address = *p.Inet6Address def.Inet6Address = *p.Inet6Address
} }
if p.Inet4RouteAddress != nil {
def.Inet4RouteAddress = *p.Inet4RouteAddress
}
if p.Inet6RouteAddress != nil {
def.Inet6RouteAddress = *p.Inet6RouteAddress
}
if p.Inet4RouteExcludeAddress != nil {
def.Inet4RouteExcludeAddress = *p.Inet4RouteExcludeAddress
}
if p.Inet6RouteExcludeAddress != nil {
def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress
}
if p.IncludeUID != nil { if p.IncludeUID != nil {
def.IncludeUID = *p.IncludeUID def.IncludeUID = *p.IncludeUID
} }

View File

@ -27,20 +27,22 @@ type Tun struct {
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
RedirectToTun []string `yaml:"-" json:"-"` RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
} }

View File

@ -1,7 +0,0 @@
package http
import "net"
func (l *Listener) Listener() net.Listener {
return l.listener
}

View File

@ -65,9 +65,6 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
} }
continue continue
} }
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
go HandleConn(conn, tunnel, c, additions...) go HandleConn(conn, tunnel, c, additions...)
} }
}() }()

View File

@ -18,22 +18,24 @@ type TunOption struct {
AutoRoute bool `inbound:"auto-route,omitempty"` AutoRoute bool `inbound:"auto-route,omitempty"`
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
MTU uint32 `inbound:"mtu,omitempty"` MTU uint32 `inbound:"mtu,omitempty"`
Inet4Address []string `inbound:"inet4_address,omitempty"` Inet4Address []string `inbound:"inet4_address,omitempty"`
Inet6Address []string `inbound:"inet6_address,omitempty"` Inet6Address []string `inbound:"inet6_address,omitempty"`
StrictRoute bool `inbound:"strict_route,omitempty"` StrictRoute bool `inbound:"strict_route,omitempty"`
Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"` Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"`
Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"`
IncludeUID []uint32 `inbound:"include_uid,omitempty"` Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"`
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"`
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` IncludeUID []uint32 `inbound:"include_uid,omitempty"`
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"` IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"` ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
IncludePackage []string `inbound:"include_package,omitempty"` ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
ExcludePackage []string `inbound:"exclude_package,omitempty"` IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` IncludePackage []string `inbound:"include_package,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"` ExcludePackage []string `inbound:"exclude_package,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"` EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"`
} }
func (o TunOption) Equal(config C.InboundConfig) bool { func (o TunOption) Equal(config C.InboundConfig) bool {
@ -72,32 +74,42 @@ func NewTun(options *TunOption) (*Tun, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress)
if err != nil {
return nil, err
}
inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress)
if err != nil {
return nil, err
}
return &Tun{ return &Tun{
Base: base, Base: base,
config: options, config: options,
tun: LC.Tun{ tun: LC.Tun{
Enable: true, Enable: true,
Device: options.Device, Device: options.Device,
Stack: stack, Stack: stack,
DNSHijack: options.DNSHijack, DNSHijack: options.DNSHijack,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
AutoDetectInterface: options.AutoDetectInterface, AutoDetectInterface: options.AutoDetectInterface,
MTU: options.MTU, MTU: options.MTU,
Inet4Address: inet4Address, Inet4Address: inet4Address,
Inet6Address: inet6Address, Inet6Address: inet6Address,
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: inet6RouteAddress,
IncludeUID: options.IncludeUID, Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
IncludeUIDRange: options.IncludeUIDRange, Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
ExcludeUID: options.ExcludeUID, IncludeUID: options.IncludeUID,
ExcludeUIDRange: options.ExcludeUIDRange, IncludeUIDRange: options.IncludeUIDRange,
IncludeAndroidUser: options.IncludeAndroidUser, ExcludeUID: options.ExcludeUID,
IncludePackage: options.IncludePackage, ExcludeUIDRange: options.ExcludeUIDRange,
ExcludePackage: options.ExcludePackage, IncludeAndroidUser: options.IncludeAndroidUser,
EndpointIndependentNat: options.EndpointIndependentNat, IncludePackage: options.IncludePackage,
UDPTimeout: options.UDPTimeout, ExcludePackage: options.ExcludePackage,
FileDescriptor: options.FileDescriptor, EndpointIndependentNat: options.EndpointIndependentNat,
UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor,
}, },
}, nil }, nil
} }

View File

@ -27,8 +27,6 @@ func HandleTcp(address string) (conn net.Conn, err error) {
metadata.Type = C.INNER metadata.Type = C.INNER
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSNormal
metadata.Process = C.ClashName metadata.Process = C.ClashName
metadata.RawSrcAddr = conn1.RemoteAddr()
metadata.RawDstAddr = conn1.LocalAddr()
if h, port, err := net.SplitHostPort(address); err == nil { if h, port, err := net.SplitHostPort(address); err == nil {
if port, err := strconv.ParseUint(port, 10, 16); err == nil { if port, err := strconv.ParseUint(port, 10, 16); err == nil {
metadata.DstPort = uint16(port) metadata.DstPort = uint16(port)

View File

@ -849,6 +849,14 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
return tunConf.Inet6RouteAddress[i].String() < tunConf.Inet6RouteAddress[j].String() return tunConf.Inet6RouteAddress[i].String() < tunConf.Inet6RouteAddress[j].String()
}) })
sort.Slice(tunConf.Inet4RouteExcludeAddress, func(i, j int) bool {
return tunConf.Inet4RouteExcludeAddress[i].String() < tunConf.Inet4RouteExcludeAddress[j].String()
})
sort.Slice(tunConf.Inet6RouteExcludeAddress, func(i, j int) bool {
return tunConf.Inet6RouteExcludeAddress[i].String() < tunConf.Inet6RouteExcludeAddress[j].String()
})
sort.Slice(tunConf.IncludeUID, func(i, j int) bool { sort.Slice(tunConf.IncludeUID, func(i, j int) bool {
return tunConf.IncludeUID[i] < tunConf.IncludeUID[j] return tunConf.IncludeUID[i] < tunConf.IncludeUID[j]
}) })
@ -882,6 +890,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
!slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) || !slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) ||
!slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) || !slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||
!slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) || !slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) ||
!slices.Equal(tunConf.Inet4RouteExcludeAddress, LastTunConf.Inet4RouteExcludeAddress) ||
!slices.Equal(tunConf.Inet6RouteExcludeAddress, LastTunConf.Inet6RouteExcludeAddress) ||
!slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) || !slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) ||
!slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) || !slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) ||
!slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) || !slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) ||

View File

@ -199,22 +199,24 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} }
tunOptions := tun.Options{ tunOptions := tun.Options{
Name: tunName, Name: tunName,
MTU: tunMTU, MTU: tunMTU,
Inet4Address: options.Inet4Address, Inet4Address: options.Inet4Address,
Inet6Address: options.Inet6Address, Inet6Address: options.Inet6Address,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
Inet4RouteAddress: options.Inet4RouteAddress, Inet4RouteAddress: options.Inet4RouteAddress,
Inet6RouteAddress: options.Inet6RouteAddress, Inet6RouteAddress: options.Inet6RouteAddress,
IncludeUID: includeUID, Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
ExcludeUID: excludeUID, Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
IncludeAndroidUser: options.IncludeAndroidUser, IncludeUID: includeUID,
IncludePackage: options.IncludePackage, ExcludeUID: excludeUID,
ExcludePackage: options.ExcludePackage, IncludeAndroidUser: options.IncludeAndroidUser,
FileDescriptor: options.FileDescriptor, IncludePackage: options.IncludePackage,
InterfaceMonitor: defaultInterfaceMonitor, ExcludePackage: options.ExcludePackage,
TableIndex: 2022, FileDescriptor: options.FileDescriptor,
InterfaceMonitor: defaultInterfaceMonitor,
TableIndex: 2022,
} }
err = l.buildAndroidRules(&tunOptions) err = l.buildAndroidRules(&tunOptions)

View File

@ -22,7 +22,7 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption {
type IPCIDR struct { type IPCIDR struct {
*Base *Base
ipnet *netip.Prefix ipnet netip.Prefix
adapter string adapter string
isSourceIP bool isSourceIP bool
noResolveIP bool noResolveIP bool
@ -63,7 +63,7 @@ func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error)
ipcidr := &IPCIDR{ ipcidr := &IPCIDR{
Base: &Base{}, Base: &Base{},
ipnet: &ipnet, ipnet: ipnet,
adapter: adapter, adapter: adapter,
} }

View File

@ -76,7 +76,11 @@ func ruleParse(ruleRaw string) (string, string, []string) {
} else if len(item) == 2 { } else if len(item) == 2 {
return item[0], item[1], nil return item[0], item[1], nil
} else if len(item) > 2 { } else if len(item) > 2 {
return item[0], item[1], item[2:] if item[0] == "NOT" || item[0] == "OR" || item[0] == "AND" || item[0] == "SUB-RULE" {
return item[0], strings.Join(item[1:len(item)], ","), nil
} else {
return item[0], item[1], item[2:]
}
} }
return "", "", nil return "", "", nil
@ -85,7 +89,7 @@ func ruleParse(ruleRaw string) (string, string, []string) {
func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy { func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy {
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
switch tp { switch tp {
case "MATCH", "SUB-RULE": case "MATCH":
return nil, fmt.Errorf("unsupported rule type on rule-set") return nil, fmt.Errorf("unsupported rule type on rule-set")
default: default:
return parse(tp, payload, target, params, nil) return parse(tp, payload, target, params, nil)

View File

@ -62,11 +62,15 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
case "http": case "http":
if schema.Path != "" { if schema.Path != "" {
path := C.Path.Resolve(schema.Path) path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path) vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else { } else {
path := C.Path.GetPathByHash("rules", schema.URL) path := C.Path.GetPathByHash("rules", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path) vehicle = resource.NewHTTPVehicle(schema.URL, path)
} }
default: default:
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
} }

View File

@ -1,25 +0,0 @@
package provider
import "time"
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (f *ruleSetProvider) UpdatedAt() time.Time {
return f.Fetcher.UpdatedAt
}
func (rp *ruleSetProvider) Close() error {
rp.Fetcher.Destroy()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@ -209,11 +209,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
if realityConfig == nil { if realityConfig == nil {
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists { if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
utlsConn := tlsC.UClient(pconn, cfg, fingerprint) utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil { if err := utlsConn.HandshakeContext(ctx); err != nil {
pconn.Close() pconn.Close()
return nil, err return nil, err
} }
state := utlsConn.(*tlsC.UConn).ConnectionState() state := utlsConn.ConnectionState()
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
utlsConn.Close() utlsConn.Close()
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)

View File

@ -55,10 +55,11 @@ type Option struct {
} }
type WebsocketOption struct { type WebsocketOption struct {
Host string Host string
Port string Port string
Path string Path string
Headers http.Header Headers http.Header
V2rayHttpUpgrade bool
} }
type Trojan struct { type Trojan struct {
@ -132,6 +133,7 @@ func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptio
Port: wsOptions.Port, Port: wsOptions.Port,
Path: wsOptions.Path, Path: wsOptions.Path,
Headers: wsOptions.Headers, Headers: wsOptions.Headers,
V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade,
TLS: true, TLS: true,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
ClientFingerprint: t.option.ClientFingerprint, ClientFingerprint: t.option.ClientFingerprint,

View File

@ -12,14 +12,15 @@ import (
// Option is options of websocket obfs // Option is options of websocket obfs
type Option struct { type Option struct {
Host string Host string
Port string Port string
Path string Path string
Headers map[string]string Headers map[string]string
TLS bool TLS bool
SkipCertVerify bool SkipCertVerify bool
Fingerprint string Fingerprint string
Mux bool Mux bool
V2rayHttpUpgrade bool
} }
// NewV2rayObfs return a HTTPObfs // NewV2rayObfs return a HTTPObfs
@ -30,10 +31,11 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
} }
config := &vmess.WebsocketConfig{ config := &vmess.WebsocketConfig{
Host: option.Host, Host: option.Host,
Port: option.Port, Port: option.Port,
Path: option.Path, Path: option.Path,
Headers: header, V2rayHttpUpgrade: option.V2rayHttpUpgrade,
Headers: header,
} }
if option.TLS { if option.TLS {

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"crypto/sha1"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
@ -20,6 +21,7 @@ import (
"github.com/Dreamacro/clash/common/buf" "github.com/Dreamacro/clash/common/buf"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/log"
"github.com/gobwas/ws" "github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil" "github.com/gobwas/ws/wsutil"
@ -56,6 +58,7 @@ type WebsocketConfig struct {
MaxEarlyData int MaxEarlyData int
EarlyDataHeaderName string EarlyDataHeaderName string
ClientFingerprint string ClientFingerprint string
V2rayHttpUpgrade bool
} }
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
@ -317,48 +320,80 @@ func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Co
} }
func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) { func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) {
dialer := ws.Dialer{
NetDial: func(ctx context.Context, network, addr string) (net.Conn, error) {
return conn, nil
},
TLSConfig: c.TLSConfig,
}
scheme := "ws"
if c.TLS {
scheme = "wss"
if len(c.ClientFingerprint) != 0 {
if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists {
utlsConn := tlsC.UClient(conn, c.TLSConfig, fingerprint)
if err := utlsConn.(*tlsC.UConn).BuildWebsocketHandshakeState(); err != nil {
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
}
dialer.TLSClient = func(conn net.Conn, hostname string) net.Conn {
return utlsConn
}
}
}
}
u, err := url.Parse(c.Path) u, err := url.Parse(c.Path)
if err != nil { if err != nil {
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
} }
uri := url.URL{ uri := url.URL{
Scheme: scheme, Scheme: "ws",
Host: net.JoinHostPort(c.Host, c.Port), Host: net.JoinHostPort(c.Host, c.Port),
Path: u.Path, Path: u.Path,
RawQuery: u.RawQuery, RawQuery: u.RawQuery,
} }
headers := http.Header{} if c.TLS {
headers.Set("User-Agent", "Go-http-client/1.1") // match golang's net/http uri.Scheme = "wss"
if c.Headers != nil { config := c.TLSConfig
for k := range c.Headers { if config == nil { // The config cannot be nil
headers.Add(k, c.Headers.Get(k)) config = &tls.Config{NextProtos: []string{"http/1.1"}}
} }
if config.ServerName == "" && !config.InsecureSkipVerify { // users must set either ServerName or InsecureSkipVerify in the config.
config = config.Clone()
config.ServerName = uri.Host
}
if len(c.ClientFingerprint) != 0 {
if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists {
utlsConn := tlsC.UClient(conn, config, fingerprint)
if err = utlsConn.BuildWebsocketHandshakeState(); err != nil {
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
}
conn = utlsConn
}
} else {
conn = tls.Client(conn, config)
}
if tlsConn, ok := conn.(interface {
HandshakeContext(ctx context.Context) error
}); ok {
if err = tlsConn.HandshakeContext(ctx); err != nil {
return nil, err
}
}
}
request := &http.Request{
Method: http.MethodGet,
URL: &uri,
Header: c.Headers.Clone(),
Host: c.Host,
}
request.Header.Set("Connection", "Upgrade")
request.Header.Set("Upgrade", "websocket")
if host := request.Header.Get("Host"); host != "" {
// For client requests, Host optionally overrides the Host
// header to send. If empty, the Request.Write method uses
// the value of URL.Host. Host may contain an international
// domain name.
request.Host = host
}
request.Header.Del("Host")
var secKey string
if !c.V2rayHttpUpgrade {
const nonceKeySize = 16
// NOTE: bts does not escape.
bts := make([]byte, nonceKeySize)
if _, err = fastrand.Read(bts); err != nil {
return nil, fmt.Errorf("rand read error: %w", err)
}
secKey = base64.StdEncoding.EncodeToString(bts)
request.Header.Set("Sec-WebSocket-Version", "13")
request.Header.Set("Sec-WebSocket-Key", secKey)
} }
if earlyData != nil { if earlyData != nil {
@ -366,39 +401,61 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
if c.EarlyDataHeaderName == "" { if c.EarlyDataHeaderName == "" {
uri.Path += earlyDataString uri.Path += earlyDataString
} else { } else {
headers.Set(c.EarlyDataHeaderName, earlyDataString) request.Header.Set(c.EarlyDataHeaderName, earlyDataString)
} }
} }
// gobwas/ws will check server's response "Sec-Websocket-Protocol" so must add Protocols to ws.Dialer if ctx.Done() != nil {
// if not will cause ws.ErrHandshakeBadSubProtocol done := N.SetupContextForConn(ctx, conn)
if secProtocol := headers.Get("Sec-WebSocket-Protocol"); len(secProtocol) > 0 { defer done(&err)
// gobwas/ws will set "Sec-Websocket-Protocol" according dialer.Protocols
// to avoid send repeatedly don't set it to headers
headers.Del("Sec-WebSocket-Protocol")
dialer.Protocols = []string{secProtocol}
} }
// gobwas/ws send "Host" directly in Upgrade() by `httpWriteHeader(bw, headerHost, u.Host)` err = request.Write(conn)
// if headers has "Host" will send repeatedly
if host := headers.Get("Host"); host != "" {
headers.Del("Host")
uri.Host = host
}
dialer.Header = ws.HandshakeHeaderHTTP(headers)
conn, reader, _, err := dialer.Dial(ctx, uri.String())
if err != nil { if err != nil {
return nil, fmt.Errorf("dial %s error: %w", uri.Host, err) return nil, err
}
bufferedConn := N.NewBufferedConn(conn)
response, err := http.ReadResponse(bufferedConn.Reader(), request)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusSwitchingProtocols ||
!strings.EqualFold(response.Header.Get("Connection"), "upgrade") ||
!strings.EqualFold(response.Header.Get("Upgrade"), "websocket") {
return nil, fmt.Errorf("unexpected status: %s", response.Status)
} }
conn = newWebsocketConn(conn, reader, ws.StateClientSide) if c.V2rayHttpUpgrade {
return bufferedConn, nil
}
if log.Level() == log.DEBUG { // we might not check this for performance
secAccept := response.Header.Get("Sec-Websocket-Accept")
const acceptSize = 28 // base64.StdEncoding.EncodedLen(sha1.Size)
if lenSecAccept := len(secAccept); lenSecAccept != acceptSize {
return nil, fmt.Errorf("unexpected Sec-Websocket-Accept length: %d", lenSecAccept)
}
if getSecAccept(secKey) != secAccept {
return nil, errors.New("unexpected Sec-Websocket-Accept")
}
}
conn = newWebsocketConn(conn, ws.StateClientSide)
// websocketConn can't correct handle ReadDeadline // websocketConn can't correct handle ReadDeadline
// so call N.NewDeadlineConn to add a safe wrapper // so call N.NewDeadlineConn to add a safe wrapper
return N.NewDeadlineConn(conn), nil return N.NewDeadlineConn(conn), nil
} }
func getSecAccept(secKey string) string {
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
const nonceSize = 24 // base64.StdEncoding.EncodedLen(nonceKeySize)
p := make([]byte, nonceSize+len(magic))
copy(p[:nonceSize], secKey)
copy(p[nonceSize:], magic)
sum := sha1.Sum(p)
return base64.StdEncoding.EncodeToString(sum[:])
}
func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig) (net.Conn, error) { func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
if u, err := url.Parse(c.Path); err == nil { if u, err := url.Parse(c.Path); err == nil {
if q := u.Query(); q.Get("ed") != "" { if q := u.Query(); q.Get("ed") != "" {
@ -419,19 +476,13 @@ func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig)
return streamWebsocketConn(ctx, conn, c, nil) return streamWebsocketConn(ctx, conn, c, nil)
} }
func newWebsocketConn(conn net.Conn, br *bufio.Reader, state ws.State) *websocketConn { func newWebsocketConn(conn net.Conn, state ws.State) *websocketConn {
controlHandler := wsutil.ControlFrameHandler(conn, state) controlHandler := wsutil.ControlFrameHandler(conn, state)
var reader io.Reader
if br != nil && br.Buffered() > 0 {
reader = br
} else {
reader = conn
}
return &websocketConn{ return &websocketConn{
Conn: conn, Conn: conn,
state: state, state: state,
reader: &wsutil.Reader{ reader: &wsutil.Reader{
Source: reader, Source: conn,
State: state, State: state,
SkipHeaderCheck: true, SkipHeaderCheck: true,
CheckUTF8: false, CheckUTF8: false,
@ -458,23 +509,53 @@ func decodeXray0rtt(requestHeader http.Header) []byte {
return nil return nil
} }
func IsWebSocketUpgrade(r *http.Request) bool {
return r.Header.Get("Upgrade") == "websocket"
}
func IsV2rayHttpUpdate(r *http.Request) bool {
return IsWebSocketUpgrade(r) && r.Header.Get("Sec-WebSocket-Key") == ""
}
func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Conn, error) { func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
wsConn, rw, _, err := ws.UpgradeHTTP(r, w) var conn net.Conn
var rw *bufio.ReadWriter
var err error
isRaw := IsV2rayHttpUpdate(r)
w.Header().Set("Connection", "upgrade")
w.Header().Set("Upgrade", "websocket")
if !isRaw {
w.Header().Set("Sec-Websocket-Accept", getSecAccept(r.Header.Get("Sec-WebSocket-Key")))
}
w.WriteHeader(http.StatusSwitchingProtocols)
if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher {
err = flusher.FlushError()
if err != nil {
return nil, fmt.Errorf("flush response: %w", err)
}
}
hijacker, canHijack := w.(http.Hijacker)
if !canHijack {
return nil, errors.New("invalid connection, maybe HTTP/2")
}
conn, rw, err = hijacker.Hijack()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("hijack failed: %w", err)
} }
conn := newWebsocketConn(wsConn, rw.Reader, ws.StateServerSide)
// rw.Writer was flushed, so we only need warp rw.Reader
conn = N.WarpConnWithBioReader(conn, rw.Reader)
if !isRaw {
conn = newWebsocketConn(conn, ws.StateServerSide)
// websocketConn can't correct handle ReadDeadline
// so call N.NewDeadlineConn to add a safe wrapper
conn = N.NewDeadlineConn(conn)
}
if edBuf := decodeXray0rtt(r.Header); len(edBuf) > 0 { if edBuf := decodeXray0rtt(r.Header); len(edBuf) > 0 {
return N.NewDeadlineConn(&websocketWithReaderConn{conn, io.MultiReader(bytes.NewReader(edBuf), conn)}), nil conn = N.NewCachedConn(conn, edBuf)
} }
return N.NewDeadlineConn(conn), nil
}
type websocketWithReaderConn struct { return conn, nil
*websocketConn
reader io.Reader
}
func (ws *websocketWithReaderConn) Read(b []byte) (n int, err error) {
return ws.reader.Read(b)
} }

View File

@ -1,5 +0,0 @@
package statistic
func (m *Manager) Total() (up, down int64) {
return m.uploadTotal.Load(), m.downloadTotal.Load()
}

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"path/filepath"
"runtime" "runtime"
"sync" "sync"
"time" "time"
@ -46,8 +47,6 @@ var (
findProcessMode P.FindProcessMode findProcessMode P.FindProcessMode
fakeIPRange netip.Prefix fakeIPRange netip.Prefix
procesCache string
) )
type tunnel struct{} type tunnel struct{}
@ -594,8 +593,8 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
configMux.RLock() configMux.RLock()
defer configMux.RUnlock() defer configMux.RUnlock()
var ( var (
resolved bool resolved bool
processFound bool attemptProcessLookup = true
) )
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok { if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
@ -619,17 +618,15 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
}() }()
} }
if !findProcessMode.Off() && !processFound && (findProcessMode.Always() || rule.ShouldFindProcess()) { if attemptProcessLookup && !findProcessMode.Off() && (findProcessMode.Always() || rule.ShouldFindProcess()) {
pkg, err := P.FindPackageName(metadata) attemptProcessLookup = false
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort))
if err != nil { if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err) log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else { } else {
metadata.Process = pkg metadata.Process = filepath.Base(path)
processFound = true metadata.ProcessPath = path
if procesCache != metadata.Process { metadata.Uid = uid
log.Debugln("[Process] %s from process %s", metadata.String(), metadata.Process)
}
procesCache = metadata.Process
} }
} }