Compare commits
30 Commits
android-op
...
Prerelease
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c3557e96b | ||
|
|
228990472d | ||
|
|
09e7866a5c | ||
|
|
665ba7f9f1 | ||
|
|
ee3038d5e4 | ||
|
|
885ee7a820 | ||
|
|
ef303b11f2 | ||
|
|
a82ce85707 | ||
|
|
5bfe7ba169 | ||
|
|
ceac5bfaa4 | ||
|
|
b0638cfc49 | ||
|
|
96220aa8ea | ||
|
|
8ff476a3a1 | ||
|
|
261b6e8dce | ||
|
|
2b9141e0e5 | ||
|
|
55255faa52 | ||
|
|
d42e3f74ad | ||
|
|
81a8a63861 | ||
|
|
c3a61e2db5 | ||
|
|
bffe47a974 | ||
|
|
4314b37d04 | ||
|
|
cf93f69f40 | ||
|
|
55f626424f | ||
|
|
431d52f250 | ||
|
|
c1f24d8f0e | ||
|
|
fc5a3cf80c | ||
|
|
e1e999180a | ||
|
|
8755618910 | ||
|
|
aede97571f | ||
|
|
01bc84db02 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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.
|
||||||
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -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
12
.github/rename-go120.sh
vendored
Normal 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
|
||||||
18
.github/workflows/android-branch-auto-sync.yml
vendored
18
.github/workflows/android-branch-auto-sync.yml
vendored
@@ -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"}'
|
||||||
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@@ -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: |
|
||||||
|
|||||||
28
.github/workflows/cmfa-update-deps-trigger.yml
vendored
28
.github/workflows/cmfa-update-deps-trigger.yml
vendored
@@ -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
|
|
||||||
|
|
||||||
@@ -66,6 +66,7 @@ type v2rayObfsOption struct {
|
|||||||
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 {
|
||||||
@@ -263,6 +264,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
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 {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
|
|||||||
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 != "" {
|
||||||
|
|||||||
@@ -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{},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {}
|
||||||
|
|||||||
@@ -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
49
common/net/cached.go
Normal 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
31
common/net/context.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,11 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
|||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -125,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -123,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"`
|
||||||
}
|
}
|
||||||
@@ -232,8 +231,10 @@ type RawTun struct {
|
|||||||
//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"`
|
||||||
|
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
|
||||||
|
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
|
||||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||||
@@ -462,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",
|
||||||
}
|
}
|
||||||
@@ -1048,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,
|
||||||
},
|
},
|
||||||
@@ -1150,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
|
||||||
@@ -1226,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{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1300,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,
|
||||||
@@ -1369,6 +1369,8 @@ func parseTun(rawTun RawTun, general *General) error {
|
|||||||
StrictRoute: rawTun.StrictRoute,
|
StrictRoute: rawTun.StrictRoute,
|
||||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||||
|
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
||||||
|
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
||||||
IncludeUID: rawTun.IncludeUID,
|
IncludeUID: rawTun.IncludeUID,
|
||||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||||
ExcludeUID: rawTun.ExcludeUID,
|
ExcludeUID: rawTun.ExcludeUID,
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
14
dns/dhcp.go
14
dns/dhcp.go
@@ -8,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,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
|
||||||
@@ -46,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 {
|
||||||
@@ -86,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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
114
dns/system.go
114
dns/system.go
@@ -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{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
dns/util.go
38
dns/util.go
@@ -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)
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ proxies: # socks5
|
|||||||
# 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
|
||||||
@@ -439,6 +440,7 @@ proxies: # socks5
|
|||||||
# 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"
|
||||||
@@ -609,6 +612,7 @@ proxies: # socks5
|
|||||||
# 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
3
go.mod
@@ -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
6
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ type tunSchema struct {
|
|||||||
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"`
|
||||||
|
Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||||
|
Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type Tun struct {
|
|||||||
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"`
|
||||||
|
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||||
|
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||||
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ type TunOption struct {
|
|||||||
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"`
|
||||||
|
Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"`
|
||||||
|
Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"`
|
||||||
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
|
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
|
||||||
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
|
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
|
||||||
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
|
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
|
||||||
@@ -72,6 +74,14 @@ 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,
|
||||||
@@ -88,6 +98,8 @@ func NewTun(options *TunOption) (*Tun, error) {
|
|||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
Inet4RouteAddress: inet4RouteAddress,
|
Inet4RouteAddress: inet4RouteAddress,
|
||||||
Inet6RouteAddress: inet6RouteAddress,
|
Inet6RouteAddress: inet6RouteAddress,
|
||||||
|
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||||
|
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||||
IncludeUID: options.IncludeUID,
|
IncludeUID: options.IncludeUID,
|
||||||
IncludeUIDRange: options.IncludeUIDRange,
|
IncludeUIDRange: options.IncludeUIDRange,
|
||||||
ExcludeUID: options.ExcludeUID,
|
ExcludeUID: options.ExcludeUID,
|
||||||
|
|||||||
@@ -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) ||
|
||||||
|
|||||||
@@ -207,6 +207,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
|||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||||
|
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
||||||
|
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
||||||
IncludeUID: includeUID,
|
IncludeUID: includeUID,
|
||||||
ExcludeUID: excludeUID,
|
ExcludeUID: excludeUID,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,8 +76,12 @@ 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 {
|
||||||
|
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 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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ type WebsocketOption struct {
|
|||||||
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,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type Option struct {
|
|||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
Mux bool
|
Mux bool
|
||||||
|
V2rayHttpUpgrade bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewV2rayObfs return a HTTPObfs
|
// NewV2rayObfs return a HTTPObfs
|
||||||
@@ -33,6 +34,7 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
|
|||||||
Host: option.Host,
|
Host: option.Host,
|
||||||
Port: option.Port,
|
Port: option.Port,
|
||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
|
V2rayHttpUpgrade: option.V2rayHttpUpgrade,
|
||||||
Headers: header,
|
Headers: header,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("flush response: %w", err)
|
||||||
}
|
}
|
||||||
conn := newWebsocketConn(wsConn, rw.Reader, ws.StateServerSide)
|
}
|
||||||
|
hijacker, canHijack := w.(http.Hijacker)
|
||||||
|
if !canHijack {
|
||||||
|
return nil, errors.New("invalid connection, maybe HTTP/2")
|
||||||
|
}
|
||||||
|
conn, rw, err = hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("hijack failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user