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
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
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
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
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
|
||||
20
.github/workflows/android-branch-auto-sync.yml
vendored
20
.github/workflows/android-branch-auto-sync.yml
vendored
@@ -48,4 +48,22 @@ jobs:
|
||||
|
||||
- name: Push changes
|
||||
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",
|
||||
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: "linux/386", id: "2" }
|
||||
- { type: "WithCGO", target: "linux/amd64", id: "3" }
|
||||
@@ -126,18 +132,26 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Setup Go
|
||||
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
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
|
||||
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
|
||||
if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
|
||||
run: |
|
||||
go test ./...
|
||||
|
||||
- name: Build WithoutCGO
|
||||
if: ${{ matrix.job.type=='WithoutCGO' }}
|
||||
if: ${{ matrix.job.type!='WithCGO' }}
|
||||
env:
|
||||
NAME: Clash.Meta
|
||||
BINDIR: bin
|
||||
@@ -185,6 +199,17 @@ jobs:
|
||||
ls -la
|
||||
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
|
||||
if: ${{ success() }}
|
||||
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
|
||||
|
||||
@@ -58,14 +58,15 @@ type simpleObfsOption struct {
|
||||
}
|
||||
|
||||
type v2rayObfsOption struct {
|
||||
Mode string `obfs:"mode"`
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
Mode string `obfs:"mode"`
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
|
||||
}
|
||||
|
||||
type shadowTLSOption struct {
|
||||
@@ -259,10 +260,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}
|
||||
obfsMode = opts.Mode
|
||||
v2rayOption = &v2rayObfs.Option{
|
||||
Host: opts.Host,
|
||||
Path: opts.Path,
|
||||
Headers: opts.Headers,
|
||||
Mux: opts.Mux,
|
||||
Host: opts.Host,
|
||||
Path: opts.Path,
|
||||
Headers: opts.Headers,
|
||||
Mux: opts.Mux,
|
||||
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
|
||||
}
|
||||
|
||||
if opts.TLS {
|
||||
|
||||
@@ -53,9 +53,10 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
|
||||
if t.option.Network == "ws" {
|
||||
host, port, _ := net.SplitHostPort(t.addr)
|
||||
wsOpts := &trojan.WebsocketOption{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: t.option.WSOpts.Path,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: t.option.WSOpts.Path,
|
||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||
}
|
||||
|
||||
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,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ type WSOptions struct {
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
||||
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
||||
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
|
||||
}
|
||||
|
||||
// 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,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ func DefaultValue[T any]() T {
|
||||
|
||||
type TypedValue[T any] struct {
|
||||
value atomic.Value
|
||||
_ noCopy
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Load() T {
|
||||
@@ -55,3 +56,9 @@ func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
||||
v.Store(t)
|
||||
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}
|
||||
}
|
||||
|
||||
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.
|
||||
func (c *BufferedConn) Reader() *bufio.Reader {
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type Authenticator interface {
|
||||
Verify(user string, pass string) bool
|
||||
Users() []string
|
||||
@@ -15,12 +11,12 @@ type AuthUser struct {
|
||||
}
|
||||
|
||||
type inMemoryAuthenticator struct {
|
||||
storage *xsync.MapOf[string, string]
|
||||
storage map[string]string
|
||||
usernames []string
|
||||
}
|
||||
|
||||
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
|
||||
realPass, ok := au.storage.Load(user)
|
||||
realPass, ok := au.storage[user]
|
||||
return ok && realPass == pass
|
||||
}
|
||||
|
||||
@@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
||||
if len(users) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
|
||||
for _, user := range users {
|
||||
au.storage.Store(user.User, user.Pass)
|
||||
au := &inMemoryAuthenticator{
|
||||
storage: make(map[string]string),
|
||||
usernames: make([]string, 0, len(users)),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
|
||||
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 (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
@@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *netip.Prefix
|
||||
var addr netip.Prefix
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
@@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
|
||||
|
||||
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 (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func bindIfaceToDialer(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
|
||||
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(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 bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
|
||||
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
|
||||
}
|
||||
|
||||
func ParseNetwork(network string, addr netip.Addr) string {
|
||||
// fix bindIfaceToListenConfig() force bind to an ipv4 address
|
||||
if !strings.HasSuffix(network, "4") &&
|
||||
!strings.HasSuffix(network, "6") &&
|
||||
addr.Unmap().Is6() {
|
||||
network += "6"
|
||||
}
|
||||
return network
|
||||
return fallbackParseNetwork(network, addr)
|
||||
}
|
||||
|
||||
@@ -74,7 +74,11 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,7 +129,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
|
||||
dialer := netDialer.(*net.Dialer)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type NetDialer interface {
|
||||
|
||||
type option struct {
|
||||
interfaceName string
|
||||
fallbackBind bool
|
||||
addrReuse bool
|
||||
routingMark 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 {
|
||||
return func(opt *option) {
|
||||
opt.addrReuse = reuse
|
||||
|
||||
@@ -36,7 +36,7 @@ type Pool struct {
|
||||
cycle bool
|
||||
mux sync.Mutex
|
||||
host *trie.DomainTrie[struct{}]
|
||||
ipnet *netip.Prefix
|
||||
ipnet netip.Prefix
|
||||
store store
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr {
|
||||
}
|
||||
|
||||
// IPNet return raw ipnet
|
||||
func (p *Pool) IPNet() *netip.Prefix {
|
||||
func (p *Pool) IPNet() netip.Prefix {
|
||||
return p.ipnet
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func (p *Pool) restoreState() {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
IPNet *netip.Prefix
|
||||
IPNet netip.Prefix
|
||||
Host *trie.DomainTrie[struct{}]
|
||||
|
||||
// Size sets the maximum number of entries in memory
|
||||
@@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) {
|
||||
hostAddr = options.IPNet.Masked().Addr()
|
||||
gateway = hostAddr.Next()
|
||||
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) {
|
||||
|
||||
@@ -51,7 +51,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
|
||||
func TestPool_Basic(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.0/28")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) {
|
||||
func TestPool_BasicV6(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) {
|
||||
func TestPool_Case_Insensitive(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/29")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) {
|
||||
func TestPool_CycleUsed(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.16/28")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) {
|
||||
tree := trie.New[struct{}]()
|
||||
tree.Insert("example.com", struct{}{})
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
Host: tree,
|
||||
})
|
||||
@@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) {
|
||||
func TestPool_MaxCacheSize(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
|
||||
@@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
|
||||
func TestPool_DoubleMapping(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
|
||||
@@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) {
|
||||
func TestPool_Clone(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
|
||||
@@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) {
|
||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
|
||||
newPool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
newPool.CloneFrom(pool)
|
||||
@@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) {
|
||||
func TestPool_Error(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/31")
|
||||
_, err := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
|
||||
@@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) {
|
||||
func TestPool_FlushFileCache(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) {
|
||||
func TestPool_FlushMemoryCache(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
type Interface struct {
|
||||
Index int
|
||||
Name string
|
||||
Addrs []*netip.Prefix
|
||||
Addrs []netip.Prefix
|
||||
HardwareAddr net.HardwareAddr
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipNets := make([]*netip.Prefix, 0, len(addrs))
|
||||
ipNets := make([]netip.Prefix, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
ipNet := addr.(*net.IPNet)
|
||||
ip, _ := netip.AddrFromSlice(ipNet.IP)
|
||||
@@ -59,7 +59,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
}
|
||||
|
||||
pf := netip.PrefixFrom(ip, ones)
|
||||
ipNets = append(ipNets, &pf)
|
||||
ipNets = append(ipNets, pf)
|
||||
}
|
||||
|
||||
r[iface.Name] = &Interface{
|
||||
@@ -89,27 +89,27 @@ func FlushCache() {
|
||||
interfaces.Reset()
|
||||
}
|
||||
|
||||
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
|
||||
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
|
||||
return addr.Addr().Is4()
|
||||
})
|
||||
}
|
||||
|
||||
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
|
||||
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
|
||||
return addr.Addr().Is6()
|
||||
})
|
||||
}
|
||||
|
||||
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) {
|
||||
var fallback *netip.Prefix
|
||||
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
|
||||
var fallback netip.Prefix
|
||||
|
||||
for _, addr := range iface.Addrs {
|
||||
if !accept(addr) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fallback == nil && !addr.Addr().IsLinkLocalUnicast() {
|
||||
if !fallback.IsValid() && !addr.Addr().IsLinkLocalUnicast() {
|
||||
fallback = addr
|
||||
|
||||
if !destination.IsValid() {
|
||||
@@ -122,8 +122,8 @@ func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *net
|
||||
}
|
||||
}
|
||||
|
||||
if fallback == nil {
|
||||
return nil, ErrAddrNotFound
|
||||
if !fallback.IsValid() {
|
||||
return netip.Prefix{}, ErrAddrNotFound
|
||||
}
|
||||
|
||||
return fallback, nil
|
||||
|
||||
@@ -43,7 +43,8 @@ type RealityConfig struct {
|
||||
func aesgcmPreferred(ciphers []uint16) bool
|
||||
|
||||
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{
|
||||
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])
|
||||
|
||||
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 {
|
||||
return nil, errors.New("nil auth_key")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type UClientHelloID struct {
|
||||
var initRandomFingerprint UClientHelloID
|
||||
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{
|
||||
Client: fingerprint.Client,
|
||||
Version: fingerprint.Version,
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
@@ -123,7 +122,7 @@ type DNS struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []*netip.Prefix `yaml:"ipcidr"`
|
||||
IPCIDR []netip.Prefix `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
||||
}
|
||||
@@ -230,20 +229,22 @@ type RawTun struct {
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,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"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,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"`
|
||||
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"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
}
|
||||
|
||||
type RawTuicServer struct {
|
||||
@@ -462,9 +463,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
StoreSelected: true,
|
||||
},
|
||||
GeoXUrl: GeoXUrl{
|
||||
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb",
|
||||
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.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",
|
||||
}
|
||||
@@ -1048,7 +1049,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyName: proxyName,
|
||||
Interface: dialer.DefaultInterface,
|
||||
Params: params,
|
||||
PreferH3: preferH3,
|
||||
},
|
||||
@@ -1150,15 +1150,15 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
|
||||
var ipNets []*netip.Prefix
|
||||
func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
|
||||
var ipNets []netip.Prefix
|
||||
|
||||
for idx, ip := range ips {
|
||||
ipnet, err := netip.ParsePrefix(ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
|
||||
}
|
||||
ipNets = append(ipNets, &ipnet)
|
||||
ipNets = append(ipNets, ipnet)
|
||||
}
|
||||
|
||||
return ipNets, nil
|
||||
@@ -1226,7 +1226,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
IPv6: cfg.IPv6,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
FallbackFilter: FallbackFilter{
|
||||
IPCIDR: []*netip.Prefix{},
|
||||
IPCIDR: []netip.Prefix{},
|
||||
GeoSite: []*router.DomainMatcher{},
|
||||
},
|
||||
}
|
||||
@@ -1300,7 +1300,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
}
|
||||
|
||||
pool, err := fakeip.New(fakeip.Options{
|
||||
IPNet: &fakeIPRange,
|
||||
IPNet: fakeIPRange,
|
||||
Size: 1000,
|
||||
Host: host,
|
||||
Persistence: rawCfg.Profile.StoreFakeIP,
|
||||
@@ -1363,22 +1363,24 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||
RedirectToTun: rawTun.RedirectToTun,
|
||||
|
||||
MTU: rawTun.MTU,
|
||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
MTU: rawTun.MTU,
|
||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
@@ -23,7 +22,7 @@ type client struct {
|
||||
r *Resolver
|
||||
port string
|
||||
host string
|
||||
iface atomic.TypedValue[string]
|
||||
iface string
|
||||
proxyAdapter C.ProxyAdapter
|
||||
proxyName string
|
||||
addr string
|
||||
@@ -48,10 +47,6 @@ func (c *client) Address() string {
|
||||
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) {
|
||||
var (
|
||||
ip netip.Addr
|
||||
@@ -78,8 +73,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
}
|
||||
|
||||
var options []dialer.Option
|
||||
if c.iface.Load() != "" {
|
||||
options = append(options, dialer.WithInterface(c.iface.Load()))
|
||||
if c.iface != "" {
|
||||
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))
|
||||
|
||||
14
dns/dhcp.go
14
dns/dhcp.go
@@ -8,11 +8,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/component/dhcp"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -29,7 +26,7 @@ type dhcpClient struct {
|
||||
ifaceInvalidate time.Time
|
||||
dnsInvalidate time.Time
|
||||
|
||||
ifaceAddr *netip.Prefix
|
||||
ifaceAddr netip.Prefix
|
||||
done chan struct{}
|
||||
clients []dnsClient
|
||||
err error
|
||||
@@ -46,13 +43,6 @@ func (d *dhcpClient) Address() string {
|
||||
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) {
|
||||
clients, err := d.resolve(ctx)
|
||||
if err != nil {
|
||||
@@ -86,7 +76,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
|
||||
for _, item := range dns {
|
||||
nameserver = append(nameserver, NameServer{
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (doh *dnsOverHTTPS) Close() (err error) {
|
||||
doh.clientMu.Lock()
|
||||
|
||||
@@ -134,11 +134,6 @@ func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
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.
|
||||
func (doq *dnsOverQUIC) Close() (err error) {
|
||||
doq.connMu.Lock()
|
||||
|
||||
@@ -45,7 +45,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
|
||||
}
|
||||
|
||||
type ipnetFilter struct {
|
||||
ipnet *netip.Prefix
|
||||
ipnet netip.Prefix
|
||||
}
|
||||
|
||||
func (inf *ipnetFilter) Match(ip netip.Addr) bool {
|
||||
|
||||
@@ -39,16 +39,12 @@ type rcodeClient struct {
|
||||
|
||||
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.Rcode = r.rcode
|
||||
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 {
|
||||
return r.addr
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
@@ -23,7 +22,6 @@ import (
|
||||
)
|
||||
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
Address() string
|
||||
}
|
||||
@@ -136,11 +134,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||
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
|
||||
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
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 {
|
||||
result, cache, err = r.batchExchange(ctx, matched, m)
|
||||
result, cache, err = batchExchange(ctx, matched, m)
|
||||
return
|
||||
}
|
||||
result, cache, err = r.batchExchange(ctx, r.main, m)
|
||||
result, cache, err = batchExchange(ctx, r.main, m)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -255,13 +248,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
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 {
|
||||
if r.policy == 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 {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
res, _, err := r.batchExchange(ctx, client, msg)
|
||||
res, _, err := batchExchange(ctx, client, msg)
|
||||
ch <- &result{Msg: res, Error: err}
|
||||
}()
|
||||
return ch
|
||||
@@ -402,7 +388,7 @@ func (r *Resolver) Invalid() bool {
|
||||
type NameServer struct {
|
||||
Net string
|
||||
Addr string
|
||||
Interface atomic.TypedValue[string]
|
||||
Interface string
|
||||
ProxyAdapter C.ProxyAdapter
|
||||
ProxyName string
|
||||
Params map[string]string
|
||||
@@ -412,7 +398,7 @@ type NameServer struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool
|
||||
GeoIPCode string
|
||||
IPCIDR []*netip.Prefix
|
||||
IPCIDR []netip.Prefix
|
||||
Domain []string
|
||||
GeoSite []*router.DomainMatcher
|
||||
}
|
||||
|
||||
116
dns/system.go
116
dns/system.go
@@ -1,23 +1,113 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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) {
|
||||
nameservers, err := dnsReadConfig()
|
||||
const (
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if len(nameservers) == 0 {
|
||||
return
|
||||
}
|
||||
servers := make([]NameServer, 0, len(nameservers))
|
||||
for _, addr := range nameservers {
|
||||
servers = append(servers, NameServer{
|
||||
Addr: net.JoinHostPort(addr, "53"),
|
||||
Net: "udp",
|
||||
})
|
||||
}
|
||||
return transform(servers, nil), nil
|
||||
msg, _, err = batchExchange(ctx, dnsClients, m)
|
||||
return
|
||||
}
|
||||
|
||||
// Address implements dnsClient
|
||||
func (c *systemClient) Address() string {
|
||||
dnsClients, _ := c.getDnsClients()
|
||||
addrs := make([]string, 0, len(dnsClients))
|
||||
for _, c := range dnsClients {
|
||||
addrs = append(addrs, c.Address())
|
||||
}
|
||||
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))
|
||||
continue
|
||||
case "system":
|
||||
clients, err := loadSystemResolver()
|
||||
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...)
|
||||
ret = append(ret, newSystemClient())
|
||||
continue
|
||||
case "rcode":
|
||||
ret = append(ret, newRCodeClient(s.Addr))
|
||||
@@ -289,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
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() {
|
||||
@@ -299,14 +290,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
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) {
|
||||
cache = true
|
||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||
defer fast.Close()
|
||||
domain := msgToDomain(m)
|
||||
var noIpMsg *D.Msg
|
||||
for _, client := range clients {
|
||||
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
||||
msg, err = client.Exchange(m)
|
||||
msg, err = client.ExchangeContext(ctx, m)
|
||||
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
|
||||
@@ -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.
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
msg = fast.Wait()
|
||||
if msg == nil {
|
||||
if noIpMsg != nil {
|
||||
return noIpMsg, false, nil
|
||||
}
|
||||
err = errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
||||
|
||||
@@ -352,16 +352,17 @@ proxies: # socks5
|
||||
plugin: v2ray-plugin
|
||||
plugin-opts:
|
||||
mode: websocket # no QUIC now
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
- name: "ss4-shadow-tls"
|
||||
type: ss
|
||||
@@ -434,11 +435,12 @@ proxies: # socks5
|
||||
# servername: example.com # priority over wss host
|
||||
# network: ws
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
@@ -566,6 +568,7 @@ proxies: # socks5
|
||||
path: "/"
|
||||
headers:
|
||||
Host: example.com
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
@@ -606,9 +609,10 @@ proxies: # socks5
|
||||
# fingerprint: xxxx
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
- name: "trojan-xtls"
|
||||
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-shadowsocks v0.2.5
|
||||
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-wireguard v0.0.0-20231001110902-321836559170
|
||||
github.com/miekg/dns v1.1.56
|
||||
@@ -101,6 +101,7 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // 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/text v0.13.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-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-tun v0.1.15-0.20231022153326-92d6e97f0700 h1:JToLa8cxHrd6tOUHWCg9YM+o/4MXmjgagG909itmnyE=
|
||||
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 h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
|
||||
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/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
|
||||
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/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
|
||||
@@ -70,20 +70,22 @@ type tunSchema struct {
|
||||
|
||||
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,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"`
|
||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,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"`
|
||||
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"`
|
||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
}
|
||||
|
||||
type tuicServerSchema struct {
|
||||
@@ -148,6 +150,18 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.Inet6Address != nil {
|
||||
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 {
|
||||
def.IncludeUID = *p.IncludeUID
|
||||
}
|
||||
|
||||
@@ -27,20 +27,22 @@ type Tun struct {
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||
RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,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"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,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"`
|
||||
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"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
}
|
||||
|
||||
@@ -18,22 +18,24 @@ type TunOption struct {
|
||||
AutoRoute bool `inbound:"auto-route,omitempty"`
|
||||
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
|
||||
|
||||
MTU uint32 `inbound:"mtu,omitempty"`
|
||||
Inet4Address []string `inbound:"inet4_address,omitempty"`
|
||||
Inet6Address []string `inbound:"inet6_address,omitempty"`
|
||||
StrictRoute bool `inbound:"strict_route,omitempty"`
|
||||
Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"`
|
||||
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
|
||||
IncludePackage []string `inbound:"include_package,omitempty"`
|
||||
ExcludePackage []string `inbound:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
||||
MTU uint32 `inbound:"mtu,omitempty"`
|
||||
Inet4Address []string `inbound:"inet4_address,omitempty"`
|
||||
Inet6Address []string `inbound:"inet6_address,omitempty"`
|
||||
StrictRoute bool `inbound:"strict_route,omitempty"`
|
||||
Inet4RouteAddress []string `inbound:"inet4_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"`
|
||||
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
|
||||
IncludePackage []string `inbound:"include_package,omitempty"`
|
||||
ExcludePackage []string `inbound:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
||||
}
|
||||
|
||||
func (o TunOption) Equal(config C.InboundConfig) bool {
|
||||
@@ -72,32 +74,42 @@ func NewTun(options *TunOption) (*Tun, error) {
|
||||
if err != nil {
|
||||
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{
|
||||
Base: base,
|
||||
config: options,
|
||||
tun: LC.Tun{
|
||||
Enable: true,
|
||||
Device: options.Device,
|
||||
Stack: stack,
|
||||
DNSHijack: options.DNSHijack,
|
||||
AutoRoute: options.AutoRoute,
|
||||
AutoDetectInterface: options.AutoDetectInterface,
|
||||
MTU: options.MTU,
|
||||
Inet4Address: inet4Address,
|
||||
Inet6Address: inet6Address,
|
||||
StrictRoute: options.StrictRoute,
|
||||
Inet4RouteAddress: inet4RouteAddress,
|
||||
Inet6RouteAddress: inet6RouteAddress,
|
||||
IncludeUID: options.IncludeUID,
|
||||
IncludeUIDRange: options.IncludeUIDRange,
|
||||
ExcludeUID: options.ExcludeUID,
|
||||
ExcludeUIDRange: options.ExcludeUIDRange,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||
UDPTimeout: options.UDPTimeout,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
Enable: true,
|
||||
Device: options.Device,
|
||||
Stack: stack,
|
||||
DNSHijack: options.DNSHijack,
|
||||
AutoRoute: options.AutoRoute,
|
||||
AutoDetectInterface: options.AutoDetectInterface,
|
||||
MTU: options.MTU,
|
||||
Inet4Address: inet4Address,
|
||||
Inet6Address: inet6Address,
|
||||
StrictRoute: options.StrictRoute,
|
||||
Inet4RouteAddress: inet4RouteAddress,
|
||||
Inet6RouteAddress: inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||
IncludeUID: options.IncludeUID,
|
||||
IncludeUIDRange: options.IncludeUIDRange,
|
||||
ExcludeUID: options.ExcludeUID,
|
||||
ExcludeUIDRange: options.ExcludeUIDRange,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||
UDPTimeout: options.UDPTimeout,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -849,6 +849,14 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
|
||||
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 {
|
||||
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.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||
|
||||
!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.IncludeUIDRange, LastTunConf.IncludeUIDRange) ||
|
||||
!slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) ||
|
||||
|
||||
@@ -199,22 +199,24 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
}
|
||||
|
||||
tunOptions := tun.Options{
|
||||
Name: tunName,
|
||||
MTU: tunMTU,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
AutoRoute: options.AutoRoute,
|
||||
StrictRoute: options.StrictRoute,
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
InterfaceMonitor: defaultInterfaceMonitor,
|
||||
TableIndex: 2022,
|
||||
Name: tunName,
|
||||
MTU: tunMTU,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
AutoRoute: options.AutoRoute,
|
||||
StrictRoute: options.StrictRoute,
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
InterfaceMonitor: defaultInterfaceMonitor,
|
||||
TableIndex: 2022,
|
||||
}
|
||||
|
||||
err = l.buildAndroidRules(&tunOptions)
|
||||
|
||||
@@ -22,7 +22,7 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption {
|
||||
|
||||
type IPCIDR struct {
|
||||
*Base
|
||||
ipnet *netip.Prefix
|
||||
ipnet netip.Prefix
|
||||
adapter string
|
||||
isSourceIP bool
|
||||
noResolveIP bool
|
||||
@@ -63,7 +63,7 @@ func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error)
|
||||
|
||||
ipcidr := &IPCIDR{
|
||||
Base: &Base{},
|
||||
ipnet: &ipnet,
|
||||
ipnet: ipnet,
|
||||
adapter: adapter,
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,11 @@ func ruleParse(ruleRaw string) (string, string, []string) {
|
||||
} else if len(item) == 2 {
|
||||
return item[0], item[1], nil
|
||||
} else if len(item) > 2 {
|
||||
return item[0], item[1], item[2:]
|
||||
if item[0] == "NOT" || item[0] == "OR" || item[0] == "AND" || item[0] == "SUB-RULE" {
|
||||
return item[0], strings.Join(item[1:len(item)], ","), nil
|
||||
} else {
|
||||
return item[0], item[1], item[2:]
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", nil
|
||||
@@ -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 {
|
||||
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "MATCH", "SUB-RULE":
|
||||
case "MATCH":
|
||||
return nil, fmt.Errorf("unsupported rule type on rule-set")
|
||||
default:
|
||||
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 fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
||||
if err := utlsConn.HandshakeContext(ctx); err != nil {
|
||||
pconn.Close()
|
||||
return nil, err
|
||||
}
|
||||
state := utlsConn.(*tlsC.UConn).ConnectionState()
|
||||
state := utlsConn.ConnectionState()
|
||||
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||
utlsConn.Close()
|
||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||
|
||||
@@ -55,10 +55,11 @@ type Option struct {
|
||||
}
|
||||
|
||||
type WebsocketOption struct {
|
||||
Host string
|
||||
Port string
|
||||
Path string
|
||||
Headers http.Header
|
||||
Host string
|
||||
Port string
|
||||
Path string
|
||||
Headers http.Header
|
||||
V2rayHttpUpgrade bool
|
||||
}
|
||||
|
||||
type Trojan struct {
|
||||
@@ -132,6 +133,7 @@ func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptio
|
||||
Port: wsOptions.Port,
|
||||
Path: wsOptions.Path,
|
||||
Headers: wsOptions.Headers,
|
||||
V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade,
|
||||
TLS: true,
|
||||
TLSConfig: tlsConfig,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
|
||||
@@ -12,14 +12,15 @@ import (
|
||||
|
||||
// Option is options of websocket obfs
|
||||
type Option struct {
|
||||
Host string
|
||||
Port string
|
||||
Path string
|
||||
Headers map[string]string
|
||||
TLS bool
|
||||
SkipCertVerify bool
|
||||
Fingerprint string
|
||||
Mux bool
|
||||
Host string
|
||||
Port string
|
||||
Path string
|
||||
Headers map[string]string
|
||||
TLS bool
|
||||
SkipCertVerify bool
|
||||
Fingerprint string
|
||||
Mux bool
|
||||
V2rayHttpUpgrade bool
|
||||
}
|
||||
|
||||
// NewV2rayObfs return a HTTPObfs
|
||||
@@ -30,10 +31,11 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
|
||||
}
|
||||
|
||||
config := &vmess.WebsocketConfig{
|
||||
Host: option.Host,
|
||||
Port: option.Port,
|
||||
Path: option.Path,
|
||||
Headers: header,
|
||||
Host: option.Host,
|
||||
Port: option.Port,
|
||||
Path: option.Path,
|
||||
V2rayHttpUpgrade: option.V2rayHttpUpgrade,
|
||||
Headers: header,
|
||||
}
|
||||
|
||||
if option.TLS {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
@@ -56,6 +58,7 @@ type WebsocketConfig struct {
|
||||
MaxEarlyData int
|
||||
EarlyDataHeaderName string
|
||||
ClientFingerprint string
|
||||
V2rayHttpUpgrade bool
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
||||
}
|
||||
|
||||
uri := url.URL{
|
||||
Scheme: scheme,
|
||||
Scheme: "ws",
|
||||
Host: net.JoinHostPort(c.Host, c.Port),
|
||||
Path: u.Path,
|
||||
RawQuery: u.RawQuery,
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Set("User-Agent", "Go-http-client/1.1") // match golang's net/http
|
||||
if c.Headers != nil {
|
||||
for k := range c.Headers {
|
||||
headers.Add(k, c.Headers.Get(k))
|
||||
if c.TLS {
|
||||
uri.Scheme = "wss"
|
||||
config := c.TLSConfig
|
||||
if config == nil { // The config cannot be nil
|
||||
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 {
|
||||
@@ -366,39 +401,61 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
|
||||
if c.EarlyDataHeaderName == "" {
|
||||
uri.Path += earlyDataString
|
||||
} 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 not will cause ws.ErrHandshakeBadSubProtocol
|
||||
if secProtocol := headers.Get("Sec-WebSocket-Protocol"); len(secProtocol) > 0 {
|
||||
// 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}
|
||||
if ctx.Done() != nil {
|
||||
done := N.SetupContextForConn(ctx, conn)
|
||||
defer done(&err)
|
||||
}
|
||||
|
||||
// gobwas/ws send "Host" directly in Upgrade() by `httpWriteHeader(bw, headerHost, u.Host)`
|
||||
// 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())
|
||||
err = request.Write(conn)
|
||||
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
|
||||
// so call N.NewDeadlineConn to add a safe wrapper
|
||||
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) {
|
||||
if u, err := url.Parse(c.Path); err == nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
var reader io.Reader
|
||||
if br != nil && br.Buffered() > 0 {
|
||||
reader = br
|
||||
} else {
|
||||
reader = conn
|
||||
}
|
||||
return &websocketConn{
|
||||
Conn: conn,
|
||||
state: state,
|
||||
reader: &wsutil.Reader{
|
||||
Source: reader,
|
||||
Source: conn,
|
||||
State: state,
|
||||
SkipHeaderCheck: true,
|
||||
CheckUTF8: false,
|
||||
@@ -458,23 +509,53 @@ func decodeXray0rtt(requestHeader http.Header) []byte {
|
||||
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) {
|
||||
wsConn, rw, _, err := ws.UpgradeHTTP(r, w)
|
||||
var conn net.Conn
|
||||
var rw *bufio.ReadWriter
|
||||
var err error
|
||||
isRaw := IsV2rayHttpUpdate(r)
|
||||
w.Header().Set("Connection", "upgrade")
|
||||
w.Header().Set("Upgrade", "websocket")
|
||||
if !isRaw {
|
||||
w.Header().Set("Sec-Websocket-Accept", getSecAccept(r.Header.Get("Sec-WebSocket-Key")))
|
||||
}
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher {
|
||||
err = flusher.FlushError()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("flush response: %w", err)
|
||||
}
|
||||
}
|
||||
hijacker, canHijack := w.(http.Hijacker)
|
||||
if !canHijack {
|
||||
return nil, errors.New("invalid connection, maybe HTTP/2")
|
||||
}
|
||||
conn, rw, err = hijacker.Hijack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("hijack failed: %w", err)
|
||||
}
|
||||
conn := newWebsocketConn(wsConn, rw.Reader, ws.StateServerSide)
|
||||
|
||||
// rw.Writer was flushed, so we only need warp rw.Reader
|
||||
conn = N.WarpConnWithBioReader(conn, rw.Reader)
|
||||
|
||||
if !isRaw {
|
||||
conn = newWebsocketConn(conn, ws.StateServerSide)
|
||||
// websocketConn can't correct handle ReadDeadline
|
||||
// so call N.NewDeadlineConn to add a safe wrapper
|
||||
conn = N.NewDeadlineConn(conn)
|
||||
}
|
||||
|
||||
if edBuf := decodeXray0rtt(r.Header); len(edBuf) > 0 {
|
||||
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 {
|
||||
*websocketConn
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (ws *websocketWithReaderConn) Read(b []byte) (n int, err error) {
|
||||
return ws.reader.Read(b)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user