Compare commits

...

101 Commits

Author SHA1 Message Date
Larvan2
582e94ff4d Merge branch 'Alpha' into Meta 2024-03-27 18:43:06 +08:00
落心
0b4662e4b7 fixed: invalid argument to Intn (#1133) 2024-03-26 14:19:33 +08:00
bobo liu
288899a473 chore: stylish d2ae94f2 (#1132) 2024-03-24 21:41:05 +08:00
wwqgtxx
5af7f4e847 chore: allow config table-index for tun
https://github.com/MetaCubeX/mihomo/issues/1128
2024-03-24 21:31:52 +08:00
wwqgtxx
d2ae94f20b fix: iface panic
https://github.com/MetaCubeX/mihomo/issues/1130
2024-03-24 21:24:50 +08:00
wwqgtxx
d56a439a74 fix: dns truncate not work 2024-03-23 22:30:19 +08:00
wwqgtxx
9c08e936f9 fix: unmap 4in6 ip in wireguard 2024-03-22 00:33:38 +08:00
wwqgtxx
284b01ca38 fix: wireguard client bind 2024-03-21 12:23:45 +08:00
wwqgtxx
2e94531c72 Revert "fix hysteria faketcp lookback in TUN mode (#601)"
This reverts commit fdaa6a22a4.
2024-03-20 18:13:34 +08:00
wwqgtxx
e1a5b93cce chore: rebuild wireguard server address resolve 2024-03-20 12:34:55 +08:00
wwqgtxx
7fd5902e6b chore: wireguard outbound only can set ip and ipv6 outside peers
https://github.com/MetaCubeX/mihomo/issues/522
2024-03-20 12:34:54 +08:00
wwqgtxx
143fe84b8e chore: update gvisor 2024-03-20 09:30:00 +08:00
wwqgtxx
80408855ac chore: update quic-go to 0.42.0 2024-03-19 15:18:00 +08:00
wwqgtxx
c80dd5d738 chore: retry DNS over TCP when receive a truncated UDP response
https://github.com/MetaCubeX/mihomo/issues/1117
2024-03-19 14:44:36 +08:00
wwqgtxx
b3db113b1b chore: allow disabled system hosts by environment variable DISABLE_SYSTEM_HOSTS 2024-03-13 15:32:26 +08:00
wwqgtxx
dceb8ee535 fix: resolve atomic.Value usages with interface types 2024-03-13 14:49:46 +08:00
wwqgtxx
31d3614060 chore: upgrade dependencies 2024-03-13 08:54:50 +08:00
wwqgtxx
5fdfde6a07 chore: ssh outbound add private-key-passphrase,host-key,host-key-algorithms
rename `privateKey` to `private-key` and support direct write private key value in config file
2024-03-13 08:30:41 +08:00
wwqgtxx
81c832ef9e chore: code cleanup 2024-03-12 15:14:56 +08:00
wwqgtxx
012e448562 fix: when hysteria2 set ports, port can be empty 2024-03-12 15:06:41 +08:00
xishang0128
44d8a14629 feat: add IP-ASN rule 2024-03-12 03:14:25 +08:00
wwqgtxx
7ad37ca0e3 fix: hysteria2 server domain resolve 2024-03-10 23:49:54 +08:00
xishang0128
f0ff6546e4 chore: Correct android update name 2024-03-10 20:38:30 +08:00
xishang0128
77c10d90f3 chore: Replace android timezone implementation
kanged from https://github.com/SagerNet/sing-box/blob/dev-next/include/tz_android.go
2024-03-10 02:24:28 +08:00
xishang0128
e0248faebd feat: Experimental supports dialer IP4P address convert
form https://github.com/heiher/natmap/wiki/faq
2024-03-10 02:24:20 +08:00
keakon
feedc9ec66 feat: implement port hopping (#1064)
* implement port hopping using sing and sing-quic

* 更新quic-go

* 更新sing

* Update go.sum

---------

Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2024-03-08 22:54:24 +08:00
H1JK
7754b46dc4 fix: MaxMind MMDB code character case 2024-03-08 22:52:36 +08:00
H1JK
90d0ef033b chore: Check regex rule expression when initializing 2024-03-08 22:52:36 +08:00
wwqgtxx
5702d28cda chore: rebuild ssh outbound 2024-03-08 22:43:41 +08:00
TreviD
0bb5568de9 feat: add ssh outbound (#1087)
* feat: add ssh outbound

* fix: Modify the way to get dstAddr

---------

Co-authored-by: trevid <trevidmy@gmail.com>
2024-03-08 22:43:41 +08:00
xishang0128
37b02b18f7 chore: Temporarily abandon pkg.tar.zst package building 2024-03-08 22:43:40 +08:00
xishang0128
cd9e9cd2c1 fix: fix timezone for Android 2024-03-08 01:39:43 +08:00
xishang0128
234a4bfc93 feat: add DOMAIN-REGEX rule 2024-03-07 23:32:07 +08:00
wwqgtxx
fad1a08378 chore: dns outbound support tcp 2024-03-07 13:12:40 +08:00
xishang0128
04886761a2 chore: Add max-failed-times 2024-03-07 03:35:11 +08:00
xishang0128
823f59b5c7 chore: Add dns-redirect options to iptables 2024-03-07 00:52:20 +08:00
wwqgtxx
974332c0cc chore: rebuild sync.Once visit code 2024-03-05 10:57:25 +08:00
wwqgtxx
8b9813079b chore: share RelayDnsPacket function code 2024-03-04 22:12:27 +08:00
xishang0128
fe4acebb8b chore: Supplement type 2024-03-04 20:02:09 +08:00
wwqgtxx
69bf434e2c chore: vlessPacketConn should wrap ThreadSafePacketConn 2024-03-04 19:14:40 +08:00
wwqgtxx
e867497315 chore: rebuild DNS outbound code 2024-03-04 19:00:19 +08:00
sduoduo233
3ec23c1fc5 feat: Add DNS outbound to hijack DNS packets (#1078) 2024-03-04 18:21:50 +08:00
H1JK
d27340867f chore: Add GeoIP result to metadata 2024-03-02 17:41:04 +08:00
wwqgtxx
7eb16a098a chore: upgrade dependencies 2024-02-28 11:14:10 +08:00
xishang0128
f0bc68585a chore: Update workflow 2024-02-27 23:11:56 +08:00
wwqgtxx
092e53586e ci: better build.yml 2024-02-27 15:21:30 +08:00
wwqgtxx
41e3571e1d ci: let go120 build work 2024-02-27 14:26:49 +08:00
xishang0128
e58294198c chore: Distinguish between abi1.0 and abi2.0 of loongarch64 2024-02-27 02:14:18 +08:00
Skyxim
0619c75276 fix: url format error when host is IPv6
[Bug] 使用IPV6地址+vmess+http伪装的配置引起内核panic MetaCubeX/mihomo#1063
2024-02-26 06:05:06 +00:00
xishang0128
78b4b11f26 chore: Update workflow 2024-02-25 20:42:01 +08:00
Larvan2
8d9eb1e534 chore: get HealCheckURL from pd if groupOption URL is empty 2024-02-25 14:28:27 +08:00
Larvan2
d2a5376cb8 revert: "modify default url"
This reverts commit 3d643cb95a.
2024-02-24 13:50:53 +08:00
Larvan2
f8295a02fd fix: update mmdb fail 2024-02-21 21:56:20 +08:00
Larvan2
3d833ef6a8 chore: don't panic when set deadline error 2024-02-21 21:00:33 +08:00
xishang0128
1c7e011f87 fix: api does not return configuration value 2024-02-21 17:14:08 +08:00
wwqgtxx
9e7eaf720f fix: ipv6 http host addr 2024-02-21 15:04:40 +08:00
xishang0128
6399347a63 chore: add some fields for override 2024-02-20 21:52:31 +08:00
wwqgtxx
23e3f12e88 chore: better timer using 2024-02-16 11:29:33 +08:00
wwqgtxx
985b884d85 chore: add power event code for windows 2024-02-16 11:25:10 +08:00
H1JK
93b48a94fc chore: Update workflow 2024-02-15 21:48:48 +08:00
wwqgtxx
080d316059 chore: update gvisor 2024-02-14 18:07:40 +08:00
wwqgtxx
0c384b1e42 fix: tproxy start error 2024-02-07 21:07:41 +08:00
wwqgtxx
324c0bde7d chore: update golang to 1.22 2024-02-07 18:23:18 +08:00
wwqgtxx
9e57e7d29b fix: fix lan-allowed-ips does not take effect 2024-02-07 18:22:54 +08:00
xishang0128
20658f6eac fix: lan-allowed-ips does not take effect 2024-02-05 22:40:06 +08:00
Larvan2
822ba5f0b5 ci: bump github-actions version 2024-02-05 14:03:21 +08:00
Larvan2
a034421a58 Merge branch 'Alpha' into Meta 2024-02-02 20:15:27 +08:00
wwqgtxx
e6011301b2 chore: rebuild slowdown code 2024-01-30 19:41:50 +08:00
tommy
947ad9b308 chore: store latency data more reasonably (#964) 2024-01-30 15:51:12 +08:00
wwqgtxx
d1337f39ed chore: slowdown wireguard dial retry 2024-01-30 15:29:48 +08:00
wwqgtxx
c6843d6a2c fix: exclude loopback on darwin 2024-01-30 12:28:52 +08:00
wwqgtxx
f2634250a6 fix: hy2's rawConn not closed 2024-01-30 12:11:05 +08:00
wwqgtxx
9bd70e1366 fix: tfo not working with smux/yamux 2024-01-24 17:52:45 +08:00
xishang0128
1025101954 chore: add timeout option 2024-01-24 12:45:35 +08:00
wwqgtxx
80d6ca8ef9 fix: h2mux udp not working 2024-01-24 11:54:22 +08:00
xishang0128
5858384631 chore: modify initial resource update 2024-01-24 11:33:37 +08:00
wwqgtxx
5c1404f78e chore: hysteria2 add udp-mtu option
default value is `1200-3` to match old version quic-go's capability
2024-01-22 20:56:28 +08:00
Larvan2
7be6751650 fix: trigger-cmfa-update 2024-01-21 16:57:21 +08:00
xishang0128
e86567ead2 chore: limit the default url 2024-01-20 19:43:10 +08:00
wwqgtxx
c1f0ed18ef chore: dscp support range too 2024-01-20 11:00:29 +08:00
wwqgtxx
0e1bdb07d4 chore: rewrite IntRanges constructor 2024-01-20 11:00:06 +08:00
pretze
25d6ad220d feat: add DSCP rule for Tproxy UDP packets (#996)
* feat: add `DSCP` rule for Tproxy UDP packets

* fix: fix compatibility issue with non_linux platform

* chore: remove redundant lines for DSCP
2024-01-20 10:19:42 +08:00
wwqgtxx
90ea6ab278 chore: update quic-go to 0.41.0 2024-01-20 09:48:20 +08:00
snakem982
36ea09eff4 fix: Converter SIP002 parameters parse (#976) 2024-01-19 21:20:11 +08:00
riolurs
460cc240b0 fix(ntp): simplify NTP service initialization and error handling 2024-01-17 16:14:25 +08:00
Larvan2
c34a0ef8f0 chore: trigger ci 2024-01-17 14:15:49 +08:00
Larvan2
061f83d92a docs: README.md 2024-01-17 13:31:21 +08:00
wwqgtxx
edf318bae0 chore: better IPSet code 2024-01-13 18:15:30 +08:00
wwqgtxx
e860497c0c chore: cleanup IPSet code 2024-01-13 11:44:02 +08:00
Ahmad Nazari
d2d8c0115c fix: flush dns cache in android and cmfa build. (#971) 2024-01-11 10:40:04 +08:00
wwqgtxx
5ec686df08 chore: update dependencies 2024-01-11 10:00:02 +08:00
wwqgtxx
7c54775ddc chore: ipcidr direct using go4.org/netipx 2024-01-11 09:33:59 +08:00
Larvan2
425bc692ad chore: replace IpCidrTrie with binary search 2024-01-09 16:37:34 +08:00
xishang0128
ffcd672ebf chore: return more information for the api 2024-01-07 23:32:22 +08:00
Cesaryuan
7e3e38d054 fix: SUB-RULE with PROCESS-NAME rule payload not working (#953) 2024-01-06 22:29:30 +08:00
Larvan2
6bc915526f fix: resolve IPv6 rule-set issue #959. 2024-01-06 17:54:09 +08:00
Vincent.Shi
4af94df142 chore: Redundant function calls. (#956) 2024-01-05 15:07:49 +08:00
wwqgtxx
2e12ceeaed chore: stop retry when couldn't find ip 2024-01-02 21:49:27 +08:00
wwqgtxx
33bc7914e9 chore: read waiter for pipe 2024-01-02 18:34:34 +08:00
Larvan2
0404e35be8 chore: update release note 2024-01-02 16:24:44 +08:00
Larvan2
fbaa323ad4 chore: generate release note automatically 2024-01-02 16:08:19 +08:00
122 changed files with 2965 additions and 1017 deletions

32
.github/genReleaseNote.sh vendored Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
while getopts "v:" opt; do
case $opt in
v)
version_range=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
if [ -z "$version_range" ]; then
echo "Please provide the version range using -v option. Example: ./genReleashNote.sh -v v1.14.1...v1.14.2"
exit 1
fi
echo "## What's Changed" > release.md
git log --pretty=format:"* %s by @%an" --grep="^feat" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "## BUG & Fix" >> release.md
git log --pretty=format:"* %s by @%an" --grep="^fix" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "## Maintenance" >> release.md
git log --pretty=format:"* %s by @%an" --grep="^chore\|^docs\|^refactor" -i $version_range | sort -f | uniq >> release.md
echo "" >> release.md
echo "**Full Changelog**: https://github.com/MetaCubeX/Clash.Meta/compare/$version_range" >> release.md

17
.github/mihomo.service vendored Normal file
View File

@@ -0,0 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
Restart=always
ExecStartPre=/usr/bin/sleep 2s
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target

View File

@@ -21,315 +21,335 @@ concurrency:
env: env:
REGISTRY: docker.io REGISTRY: docker.io
jobs: jobs:
Build: build:
permissions: write-all
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
job: jobs:
- { - { goos: darwin, goarch: arm64, output: arm64 }
type: "WithoutCGO", - { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible }
target: "linux-amd64 linux-amd64-compatible", - { goos: darwin, goarch: amd64, goamd64: v3, output: amd64 }
id: "1",
} - { goos: linux, goarch: '386', output: '386' }
- { - { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
type: "WithoutCGO", - { goos: linux, goarch: amd64, goamd64: v3, output: amd64 }
target: "linux-armv5 linux-armv6 linux-armv7", - { goos: linux, goarch: arm64, output: arm64 }
id: "2", - { goos: linux, goarch: arm, goarm: '7', output: armv7 }
} - { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat }
- { - { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat }
type: "WithoutCGO", - { goos: linux, goarch: mipsle, mips: hardfloat, output: mipsle-hardfloat }
target: "linux-arm64 linux-mips64 linux-mips64le", - { goos: linux, goarch: mipsle, mips: softfloat, output: mipsle-softfloat }
id: "3", - { goos: linux, goarch: mips64, output: mips64 }
} - { goos: linux, goarch: mips64le, output: mips64le }
- { - { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' }
type: "WithoutCGO", - { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2' }
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat", - { goos: linux, goarch: riscv64, output: riscv64 }
id: "4", - { goos: linux, goarch: s390x, output: s390x }
}
- { type: "WithoutCGO", target: "linux-386 linux-riscv64 linux-loong64", id: "5" } - { goos: windows, goarch: '386', output: '386' }
- { - { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
type: "WithoutCGO", - { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
target: "freebsd-386 freebsd-amd64 freebsd-arm64", - { goos: windows, goarch: arm, goarm: '7', output: armv7 }
id: "6", - { goos: windows, goarch: arm64, output: arm64 }
}
- { - { goos: freebsd, goarch: '386', output: '386' }
type: "WithoutCGO", - { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-compatible }
target: "windows-amd64-compatible windows-amd64 windows-386", - { goos: freebsd, goarch: amd64, goamd64: v3, output: amd64 }
id: "7", - { goos: freebsd, goarch: arm64, output: arm64 }
}
- { - { goos: android, goarch: '386', ndk: i686-linux-android34, output: '386' }
type: "WithoutCGO", - { goos: android, goarch: amd64, ndk: x86_64-linux-android34, output: amd64 }
target: "windows-arm64 windows-arm32v7", - { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
id: "8", - { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
}
- {
type: "WithoutCGO",
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. # 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" } - { goos: windows, goarch: '386', output: '386-go120', goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
# 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. # 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" } - { goos: darwin, goarch: arm64, output: arm64-go120, goversion: '1.20' }
# - { type: "WithCGO", target: "windows/*", id: "1" } - { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
# - { type: "WithCGO", target: "linux/386", id: "2" } - { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
# - { type: "WithCGO", target: "linux/amd64", id: "3" }
# - { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" } # only for test
# - { type: "WithCGO", target: "linux/arm,", id: "5" } - { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
# - { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" } - { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20', test: test }
# - { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" } - { goos: linux, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
# - { type: "WithCGO", target: "linux/mips64", id: "8" }
# - { type: "WithCGO", target: "linux/mips64le", id: "9" }
# - { type: "WithCGO", target: "darwin-10.16/*", id: "10" }
# - { type: "WithCGO", target: "android", id: "11" }
steps: steps:
- name: Check out code into the Go module directory - uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Set variables - name: Set up Go
run: echo "VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
shell: bash uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Set variables - name: Set up Go
if: ${{github.ref_name=='Alpha'}} if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV uses: actions/setup-go@v5
shell: bash with:
go-version: ${{ matrix.jobs.goversion }}
- name: Set variables - name: Set up Go1.21 loongarch abi1
if: ${{github.ref_name=='Beta'}} if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV run: |
shell: bash wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi1.tar.gz
sudo tar zxf go1.21.5.linux-amd64-abi1.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set variables - name: Set up Go1.21 loongarch abi2
if: ${{github.ref_name=='Meta'}} if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV run: |
shell: bash wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi2.tar.gz
sudo tar zxf go1.21.5.linux-amd64-abi2.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set variables - name: Set variables
if: ${{github.ref_name=='' || github.ref_type=='tag'}} if: ${{github.ref_name=='Alpha'}}
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash shell: bash
- name: Set ENV - name: Set variables
run: | if: ${{github.ref_name=='' || github.ref_type=='tag'}}
sudo timedatectl set-timezone "Asia/Shanghai" run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
echo "BUILDTIME=$(date)" >> $GITHUB_ENV shell: bash
shell: bash
- name: Set ENV - name: Set Time Variable
run: | run: |
echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "LDFLAGS=-X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV echo "CGO_ENABLED=0" >> $GITHUB_ENV
echo "GOTOOLCHAIN=local" >> $GITHUB_ENV echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV
shell: bash
- name: Setup Go - name: Setup NDK
if: ${{ matrix.job.type!='WithoutCGO-GO120' }} if: ${{ matrix.jobs.goos == 'android' }}
uses: actions/setup-go@v4 uses: nttld/setup-ndk@v1
with: id: setup-ndk
go-version: "1.21" with:
check-latest: true ndk-version: r26c
- name: Setup Go - name: Set NDK path
if: ${{ matrix.job.type=='WithoutCGO-GO120' }} if: ${{ matrix.jobs.goos == 'android' }}
uses: actions/setup-go@v4 run: |
with: echo "CC=${{steps.setup-ndk.outputs.ndk-path}}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{matrix.jobs.ndk}}-clang" >> $GITHUB_ENV
go-version: "1.20" echo "CGO_ENABLED=1" >> $GITHUB_ENV
check-latest: true echo "BUILDTAG=" >> $GITHUB_ENV
- name: Test - name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }} if: ${{ matrix.jobs.test == 'test' }}
run: | run: |
go test ./... go test ./...
- name: Build WithoutCGO - name: Build core
if: ${{ matrix.job.type!='WithCGO' }} env:
env: GOOS: ${{matrix.jobs.goos}}
NAME: mihomo GOARCH: ${{matrix.jobs.goarch}}
BINDIR: bin GOAMD64: ${{matrix.jobs.goamd64}}
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }} GOARM: ${{matrix.jobs.arm}}
GOMIPS: ${{matrix.jobs.mips}}
run: |
echo $CGO_ENABLED
go build -v -tags "with_gvisor" -trimpath -ldflags "${BUILDTAG} -X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid="
if [ "${{matrix.jobs.goos}}" = "windows" ]; then
cp mihomo.exe mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe
zip -r mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.zip mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe
else
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}
gzip -c mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}} > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.gz
rm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}
fi
- uses: nttld/setup-ndk@v1 - name: Create DEB package
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }}
id: setup-ndk run: |
with: sudo apt-get install dpkg
ndk-version: r26b if [ "${{matrix.jobs.abi}}" = "1" ]; then
add-to-path: true ARCH=loongarch64
else
ARCH=${{matrix.jobs.goarch}}
fi
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo
- name: Build Android cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/mihomo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
env: cp .github/mihomo.service mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
mkdir bin
CC=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang
CGO_ENABLED=1 CC=${CC} GOARCH=arm64 GOOS=android go build -tags ${TAGS} -trimpath -ldflags "${LDFLAGS}" -o bin/${NAME}-android-arm64
- name: Set up xgo cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }} mixed-port: 7890
run: | external-controller: 127.0.0.1:9090
docker pull techknowlogick/xgo:latest EOF
go install src.techknowlogick.com/xgo@latest
- name: Build by xgo cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }} Package: mihomo
env: Version: 1.18.2-${VERSION}
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} Section:
run: | Priority: extra
mkdir bin Architecture: ${ARCH}
xgo --targets="${{ matrix.job.target }}" --tags="${TAGS}" -ldflags="${LDFLAGS}" --out bin/${NAME} ./ Maintainer: MetaCubeX <none@example.com>
Homepage: https://wiki.metacubex.one/
Description: The universal proxy platform.
EOF
- name: Rename dpkg-deb -Z gzip --build mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}
if: ${{ matrix.job.type=='WithCGO' }}
run: |
cd bin
ls -la
cp ../.github/rename-cgo.sh ./
bash ./rename-cgo.sh
rm ./rename-cgo.sh
ls -la
cd ..
- name: Rename - name: Convert DEB to RPM
if: ${{ matrix.job.type=='WithoutCGO-GO120' }} if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }}
run: | run: |
cd bin sudo apt-get install -y alien
ls -la alien --to-rpm --scripts mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb
cp ../.github/rename-go120.sh ./ mv mihomo*.rpm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.rpm
bash ./rename-go120.sh
rm ./rename-go120.sh
ls -la
cd ..
- name: Zip # - name: Convert DEB to PKG
if: ${{ success() }} # if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') && !contains(matrix.jobs.goarch, 'loong64') }}
run: | # run: |
cd bin # docker pull archlinux
ls -la # docker run --rm -v ./:/mnt archlinux bash -c "
chmod +x * # pacman -Syu pkgfile base-devel --noconfirm
cp ../.github/release.sh ./ # curl -L https://github.com/helixarch/debtap/raw/master/debtap > /usr/bin/debtap
bash ./release.sh # chmod 755 /usr/bin/debtap
rm ./release.sh # debtap -u
ls -la # debtap -Q /mnt/mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb
cd .. # "
# mv mihomo*.pkg.tar.zst mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.pkg.tar.zst
- name: Save version - name: Save version
run: echo ${VERSION} > bin/version.txt run: |
shell: bash echo ${VERSION} > version.txt
shell: bash
- uses: actions/upload-artifact@v3 - name: Archive production artifacts
if: ${{ success() }} uses: actions/upload-artifact@v4
with: with:
name: artifact name: ${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}
path: bin/ path: |
mihomo*.gz
mihomo*.deb
mihomo*.rpm
mihomo*.zip
version.txt
Upload-Prerelease: Upload-Prerelease:
permissions: write-all permissions: write-all
if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }} if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
needs: [Build] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/download-artifact@v3 - name: Download all workflow run artifacts
with: uses: actions/download-artifact@v4
name: artifact with:
path: bin/ path: bin/
merge-multiple: true
- name: Display structure of downloaded files - name: Delete current release assets
run: ls -R uses: 8Mi-Tech/delete-release-assets-action@main
working-directory: bin with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Delete current release assets - name: Tag Repo
uses: 8Mi-Tech/delete-release-assets-action@main uses: richardsimko/update-tag@v1
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} tag_name: Prerelease-${{ github.ref_name }}
tag: Prerelease-${{ github.ref_name }} env:
deleteOnlyFromDrafts: false GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
Release created at ${{ env.BUILDTIME }}
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
<br>
[我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/mihomo/wiki/FAQ)
[二进制文件筛选 / Binary file selector](https://metacubex.github.io/Meta-Docs/startup/#_1)
[查看文档 / Docs](https://metacubex.github.io/Meta-Docs/)
EOF
- name: Set Env - name: Upload Prerelease
run: | uses: softprops/action-gh-release@v1
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV if: ${{ success() }}
shell: bash with:
tag_name: Prerelease-${{ github.ref_name }}
- name: Tag Repo files: |
uses: richardsimko/update-tag@v1.0.6 bin/*
with: prerelease: true
tag_name: Prerelease-${{ github.ref_name }} generate_release_notes: true
env: body_path: release.txt
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
Release created at ${{ env.BUILDTIME }}
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
<br>
[我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/mihomo/wiki/FAQ)
[查看文档 / Docs](https://metacubex.github.io/Meta-Docs/)
EOF
- name: Upload Prerelease
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: Prerelease-${{ github.ref_name }}
files: |
bin/*
prerelease: true
generate_release_notes: true
body_path: release.txt
Upload-Release: Upload-Release:
permissions: write-all permissions: write-all
if: ${{ github.ref_type=='tag' }} if: ${{ github.ref_type=='tag' }}
needs: [Build] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/download-artifact@v3 - name: Checkout
with: uses: actions/checkout@v4
name: artifact with:
path: bin/ fetch-depth: 0
- name: Display structure of downloaded files - name: Get tags
run: ls -R run: |
working-directory: bin echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
- name: Upload Release - name: Generate release notes
uses: softprops/action-gh-release@v1 run: |
if: ${{ success() }} cp ./.github/genReleaseNote.sh ./
with: bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
tag_name: ${{ github.ref_name }} rm ./genReleaseNote.sh
files: bin/*
generate_release_notes: true - uses: actions/download-artifact@v4
with:
path: bin/
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: ${{ github.ref_name }}
files: bin/*
generate_release_notes: true
body_path: release.md
Docker: Docker:
if: ${{ !startsWith(github.event_name, 'pull_request') }} if: ${{ !startsWith(github.event_name, 'pull_request') }}
permissions: write-all permissions: write-all
needs: [Build] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v4
with: with:
name: artifact
path: bin/ path: bin/
merge-multiple: true
- name: Display structure of downloaded files - name: Display structure of downloaded files
run: ls -R run: ls -R
working-directory: bin working-directory: bin
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Setup Docker buildx - name: Setup Docker buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
with: with:
version: latest version: latest
@@ -337,7 +357,7 @@ jobs:
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
- name: Extract Docker metadata - name: Extract Docker metadata
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ github.repository }} images: ${{ env.REGISTRY }}/${{ github.repository }}

View File

@@ -15,7 +15,7 @@ on:
- Alpha - Alpha
jobs: jobs:
# Send "core-updated" to MetaCubeX/MihomoForAndroid to trigger update-dependencies # Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
trigger-CMFA-update: trigger-CMFA-update:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -27,7 +27,7 @@ jobs:
- name: Trigger update-dependencies - name: Trigger update-dependencies
run: | run: |
curl -X POST https://api.github.com/repos/MetaCubeX/MihomoForAndroid/dispatches \ curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \ -H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \ -H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
-d '{"event_type": "core-updated"}' -d '{"event_type": "core-updated"}'

View File

@@ -12,7 +12,7 @@ COPY docker/file-name.sh /mihomo/file-name.sh
WORKDIR /mihomo WORKDIR /mihomo
COPY bin/ bin/ COPY bin/ bin/
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \ FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo" LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo"

View File

@@ -17,6 +17,7 @@ GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.
-w -s -buildid=' -w -s -buildid='
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64-compatible \
darwin-amd64 \ darwin-amd64 \
darwin-arm64 \ darwin-arm64 \
linux-amd64-compatible \ linux-amd64-compatible \

View File

@@ -9,7 +9,7 @@
<a href="https://goreportcard.com/report/github.com/MetaCubeX/mihomo"> <a href="https://goreportcard.com/report/github.com/MetaCubeX/mihomo">
<img src="https://goreportcard.com/badge/github.com/MetaCubeX/mihomo?style=flat-square"> <img src="https://goreportcard.com/badge/github.com/MetaCubeX/mihomo?style=flat-square">
</a> </a>
<img src="https://img.shields.io/github/go-mod/go-version/MetaCubeX/mihomo?style=flat-square"> <img src="https://img.shields.io/github/go-mod/go-version/MetaCubeX/mihomo/Alpha?style=flat-square">
<a href="https://github.com/MetaCubeX/mihomo/releases"> <a href="https://github.com/MetaCubeX/mihomo/releases">
<img src="https://img.shields.io/github/release/MetaCubeX/mihomo/all.svg?style=flat-square"> <img src="https://img.shields.io/github/release/MetaCubeX/mihomo/all.svg?style=flat-square">
</a> </a>

View File

@@ -3,7 +3,6 @@ package adapter
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@@ -163,6 +162,8 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
// implements C.Proxy // implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) {
var satisfied bool
defer func() { defer func() {
alive := err == nil alive := err == nil
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
@@ -185,6 +186,11 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
p.extra.Store(url, state) p.extra.Store(url, state)
} }
if !satisfied {
record.Delay = 0
alive = false
}
state.alive.Store(alive) state.alive.Store(alive)
state.history.Put(record) state.history.Put(record)
if state.history.Len() > defaultHistoriesNum { if state.history.Len() > defaultHistoriesNum {
@@ -253,15 +259,10 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
} }
} }
if expectedStatus != nil && !expectedStatus.Check(uint16(resp.StatusCode)) { satisfied = resp != nil && (expectedStatus == nil || expectedStatus.Check(uint16(resp.StatusCode)))
// maybe another value should be returned for differentiation
err = errors.New("response status is inconsistent with the expected status")
}
t = uint16(time.Since(start) / time.Millisecond) t = uint16(time.Since(start) / time.Millisecond)
return return
} }
func NewProxy(adapter C.ProxyAdapter) *Proxy { func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{ return &Proxy{
ProxyAdapter: adapter, ProxyAdapter: adapter,

View File

@@ -63,3 +63,9 @@ func WithInAddr(addr net.Addr) Addition {
} }
} }
} }
func WithDSCP(dscp uint8) Addition {
return func(metadata *C.Metadata) {
metadata.DSCP = dscp
}
}

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"net" "net"
"github.com/sagernet/tfo-go" "github.com/metacubex/tfo-go"
) )
var ( var (

159
adapter/outbound/dns.go Normal file
View File

@@ -0,0 +1,159 @@
package outbound
import (
"context"
"net"
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
type Dns struct {
*Base
}
type DnsOption struct {
BasicOption
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter
func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
left, right := N.Pipe()
go resolver.RelayDnsConn(context.Background(), right, 0)
return NewConn(left, d), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.RemoteAddress(), metadata.SourceAddrPort())
ctx, cancel := context.WithCancel(context.Background())
return newPacketConn(&dnsPacketConn{
response: make(chan dnsPacket, 1),
ctx: ctx,
cancel: cancel,
}, d), nil
}
type dnsPacket struct {
data []byte
put func()
addr net.Addr
}
// dnsPacketConn implements net.PacketConn
type dnsPacketConn struct {
response chan dnsPacket
ctx context.Context
cancel context.CancelFunc
}
func (d *dnsPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
select {
case packet := <-d.response:
return packet.data, packet.put, packet.addr, nil
case <-d.ctx.Done():
return nil, nil, nil, net.ErrClosed
}
}
func (d *dnsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
select {
case packet := <-d.response:
n = copy(p, packet.data)
if packet.put != nil {
packet.put()
}
return n, packet.addr, nil
case <-d.ctx.Done():
return 0, nil, net.ErrClosed
}
}
func (d *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
select {
case <-d.ctx.Done():
return 0, net.ErrClosed
default:
}
if len(p) > resolver.SafeDnsPacketSize {
// wtf???
return len(p), nil
}
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
buf := pool.Get(resolver.SafeDnsPacketSize)
put := func() { _ = pool.Put(buf) }
copy(buf, p) // avoid p be changed after WriteTo returned
go func() { // don't block the WriteTo function
buf, err = resolver.RelayDnsPacket(ctx, buf[:len(p)], buf)
if err != nil {
put()
return
}
packet := dnsPacket{
data: buf,
put: put,
addr: addr,
}
select {
case d.response <- packet:
break
case <-d.ctx.Done():
put()
}
}()
return len(p), nil
}
func (d *dnsPacketConn) Close() error {
d.cancel()
return nil
}
func (*dnsPacketConn) LocalAddr() net.Addr {
return &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 53,
Zone: "",
}
}
func (*dnsPacketConn) SetDeadline(t time.Time) error {
return nil
}
func (*dnsPacketConn) SetReadDeadline(t time.Time) error {
return nil
}
func (*dnsPacketConn) SetWriteDeadline(t time.Time) error {
return nil
}
func NewDnsWithOption(option DnsOption) *Dns {
return &Dns{
Base: &Base{
name: option.Name,
tp: C.Dns,
udp: true,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
}
}

View File

@@ -8,23 +8,30 @@ import (
"net" "net"
"runtime" "runtime"
"strconv" "strconv"
"time"
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common" tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
"github.com/metacubex/sing-quic/hysteria2" "github.com/metacubex/sing-quic/hysteria2"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/zhangyunhao116/fastrand"
) )
func init() { func init() {
hysteria2.SetCongestionController = tuicCommon.SetCongestionController hysteria2.SetCongestionController = tuicCommon.SetCongestionController
} }
const minHopInterval = 5
const defaultHopInterval = 30
type Hysteria2 struct { type Hysteria2 struct {
*Base *Base
@@ -37,7 +44,9 @@ type Hysteria2Option struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
Up string `proxy:"up,omitempty"` Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"` Down string `proxy:"down,omitempty"`
Password string `proxy:"password,omitempty"` Password string `proxy:"password,omitempty"`
@@ -50,6 +59,7 @@ type Hysteria2Option struct {
CustomCA string `proxy:"ca,omitempty"` CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"` CustomCAString string `proxy:"ca-str,omitempty"`
CWND int `proxy:"cwnd,omitempty"` CWND int `proxy:"cwnd,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"`
} }
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
@@ -117,12 +127,18 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
tlsConfig.NextProtos = option.ALPN tlsConfig.NextProtos = option.ALPN
} }
if option.UdpMTU == 0 {
// "1200" from quic-go's MaxDatagramSize
// "-3" from quic-go's DatagramFrame.MaxDataLen
option.UdpMTU = 1200 - 3
}
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()) singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
clientOptions := hysteria2.ClientOptions{ clientOptions := hysteria2.ClientOptions{
Context: context.TODO(), Context: context.TODO(),
Dialer: singDialer, Dialer: singDialer,
ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)), Logger: log.SingLogger,
SendBPS: StringToBps(option.Up), SendBPS: StringToBps(option.Up),
ReceiveBPS: StringToBps(option.Down), ReceiveBPS: StringToBps(option.Down),
SalamanderPassword: salamanderPassword, SalamanderPassword: salamanderPassword,
@@ -130,6 +146,38 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
UDPDisabled: false, UDPDisabled: false,
CWND: option.CWND, CWND: option.CWND,
UdpMTU: option.UdpMTU,
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
},
}
var ranges utils.IntRanges[uint16]
var serverAddress []string
if option.Ports != "" {
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
if err != nil {
return nil, err
}
ranges.Range(func(port uint16) bool {
serverAddress = append(serverAddress, net.JoinHostPort(option.Server, strconv.Itoa(int(port))))
return true
})
if len(serverAddress) > 0 {
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[fastrand.Intn(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
}
if option.HopInterval == 0 {
option.HopInterval = defaultHopInterval
} else if option.HopInterval < minHopInterval {
option.HopInterval = minHopInterval
}
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
}
}
if option.Port == 0 && len(serverAddress) == 0 {
return nil, errors.New("invalid port")
} }
client, err := hysteria2.NewClient(clientOptions) client, err := hysteria2.NewClient(clientOptions)

208
adapter/outbound/ssh.go Normal file
View File

@@ -0,0 +1,208 @@
package outbound
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"net"
"os"
"runtime"
"strconv"
"strings"
"sync"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/zhangyunhao116/fastrand"
"golang.org/x/crypto/ssh"
)
type Ssh struct {
*Base
option *SshOption
client *sshClient // using a standalone struct to avoid its inner loop invalidate the Finalizer
}
type SshOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username"`
Password string `proxy:"password,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
PrivateKeyPassphrase string `proxy:"private-key-passphrase,omitempty"`
HostKey []string `proxy:"host-key,omitempty"`
HostKeyAlgorithms []string `proxy:"host-key-algorithms,omitempty"`
}
func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions(opts...)...)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
client, err := s.client.connect(ctx, cDialer, s.addr)
if err != nil {
return nil, err
}
c, err := client.DialContext(ctx, "tcp", metadata.RemoteAddress())
if err != nil {
return nil, err
}
return NewConn(N.NewRefConn(c, s), s), nil
}
type sshClient struct {
config *ssh.ClientConfig
client *ssh.Client
cMutex sync.Mutex
}
func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
s.cMutex.Lock()
defer s.cMutex.Unlock()
if s.client != nil {
return s.client, nil
}
c, err := cDialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
clientConn, chans, reqs, err := ssh.NewClientConn(c, addr, s.config)
if err != nil {
return nil, err
}
client = ssh.NewClient(clientConn, chans, reqs)
s.client = client
go func() {
_ = client.Wait() // wait shutdown
_ = client.Close()
s.cMutex.Lock()
defer s.cMutex.Unlock()
if s.client == client {
s.client = nil
}
}()
return client, nil
}
func (s *sshClient) Close() error {
s.cMutex.Lock()
defer s.cMutex.Unlock()
if s.client != nil {
return s.client.Close()
}
return nil
}
func closeSsh(s *Ssh) {
_ = s.client.Close()
}
func NewSsh(option SshOption) (*Ssh, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
config := ssh.ClientConfig{
User: option.UserName,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyAlgorithms: option.HostKeyAlgorithms,
}
if option.PrivateKey != "" {
var b []byte
var err error
if strings.Contains(option.PrivateKey, "PRIVATE KEY") {
b = []byte(option.PrivateKey)
} else {
b, err = os.ReadFile(C.Path.Resolve(option.PrivateKey))
if err != nil {
return nil, err
}
}
var pKey ssh.Signer
if option.PrivateKeyPassphrase != "" {
pKey, err = ssh.ParsePrivateKeyWithPassphrase(b, []byte(option.PrivateKeyPassphrase))
} else {
pKey, err = ssh.ParsePrivateKey(b)
}
if err != nil {
return nil, err
}
config.Auth = append(config.Auth, ssh.PublicKeys(pKey))
}
if option.Password != "" {
config.Auth = append(config.Auth, ssh.Password(option.Password))
}
if len(option.HostKey) != 0 {
keys := make([]ssh.PublicKey, len(option.HostKey))
for i, hostKey := range option.HostKey {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hostKey))
if err != nil {
return nil, fmt.Errorf("parse host key :%s", key)
}
keys[i] = key
}
config.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
serverKey := key.Marshal()
for _, hostKey := range keys {
if bytes.Equal(serverKey, hostKey.Marshal()) {
return nil
}
}
return fmt.Errorf("host key mismatch, server send :%s %s", key.Type(), base64.StdEncoding.EncodeToString(serverKey))
}
}
version := "SSH-2.0-OpenSSH_"
if fastrand.Intn(2) == 0 {
version += "7." + strconv.Itoa(fastrand.Intn(10))
} else {
version += "8." + strconv.Itoa(fastrand.Intn(9))
}
config.ClientVersion = version
outbound := &Ssh{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Ssh,
udp: false,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: &sshClient{
config: &config,
},
}
runtime.SetFinalizer(outbound, closeSsh)
return outbound, nil
}

View File

@@ -373,7 +373,7 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
}, M.SocksaddrFromNet(metadata.UDPAddr())), }, M.SocksaddrFromNet(metadata.UDPAddr())),
), v), nil ), v), nil
} }
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(N.NewThreadSafePacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), v), nil
} }
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter

View File

@@ -13,10 +13,12 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/metacubex/mihomo/common/atomic"
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/slowdown"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/dns" "github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -36,8 +38,7 @@ type WireGuard struct {
device *device.Device device *device.Device
tunDevice wireguard.Device tunDevice wireguard.Device
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
startOnce sync.Once init func(ctx context.Context) error
startErr error
resolver *dns.Resolver resolver *dns.Resolver
refP *refProxyAdapter refP *refProxyAdapter
} }
@@ -46,6 +47,8 @@ type WireGuardOption struct {
BasicOption BasicOption
WireGuardPeerOption WireGuardPeerOption
Name string `proxy:"name"` Name string `proxy:"name"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PrivateKey string `proxy:"private-key"` PrivateKey string `proxy:"private-key"`
Workers int `proxy:"workers,omitempty"` Workers int `proxy:"workers,omitempty"`
MTU int `proxy:"mtu,omitempty"` MTU int `proxy:"mtu,omitempty"`
@@ -61,8 +64,6 @@ type WireGuardOption struct {
type WireGuardPeerOption struct { type WireGuardPeerOption struct {
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PublicKey string `proxy:"public-key,omitempty"` PublicKey string `proxy:"public-key,omitempty"`
PreSharedKey string `proxy:"pre-shared-key,omitempty"` PreSharedKey string `proxy:"pre-shared-key,omitempty"`
Reserved []uint8 `proxy:"reserved,omitempty"` Reserved []uint8 `proxy:"reserved,omitempty"`
@@ -97,7 +98,7 @@ func (option WireGuardPeerOption) Addr() M.Socksaddr {
return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)) return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
} }
func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) { func (option WireGuardOption) Prefixes() ([]netip.Prefix, error) {
localPrefixes := make([]netip.Prefix, 0, 2) localPrefixes := make([]netip.Prefix, 0, 2)
if len(option.Ip) > 0 { if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") { if !strings.Contains(option.Ip, "/") {
@@ -136,10 +137,23 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
dialer: proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()),
} }
runtime.SetFinalizer(outbound, closeWireGuard) runtime.SetFinalizer(outbound, closeWireGuard)
resolv := func(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
if address.Addr.IsValid() {
return address.AddrPort(), nil
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), outbound.prefer)
if err != nil {
return netip.AddrPort{}, err
}
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
addrPort := udpAddr.AddrPort()
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
}
var reserved [3]uint8 var reserved [3]uint8
if len(option.Reserved) > 0 { if len(option.Reserved) > 0 {
if len(option.Reserved) != 3 { if len(option.Reserved) != 3 {
@@ -157,9 +171,12 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
connectAddr = option.Addr() connectAddr = option.Addr()
} }
} }
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr, reserved) outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr.AddrPort(), reserved)
var localPrefixes []netip.Prefix localPrefixes, err := option.Prefixes()
if err != nil {
return nil, err
}
var privateKey string var privateKey string
{ {
@@ -169,95 +186,145 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
} }
privateKey = hex.EncodeToString(bytes) privateKey = hex.EncodeToString(bytes)
} }
ipcConf := "private_key=" + privateKey
if peersLen := len(option.Peers); peersLen > 0 { if len(option.Peers) > 0 {
localPrefixes = make([]netip.Prefix, 0, peersLen*2)
for i, peer := range option.Peers { for i, peer := range option.Peers {
var peerPublicKey, preSharedKey string bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
{ if err != nil {
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey) return nil, E.Cause(err, "decode public key for peer ", i)
if err != nil {
return nil, E.Cause(err, "decode public key for peer ", i)
}
peerPublicKey = hex.EncodeToString(bytes)
} }
peer.PublicKey = hex.EncodeToString(bytes)
if peer.PreSharedKey != "" { if peer.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey) bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
if err != nil { if err != nil {
return nil, E.Cause(err, "decode pre shared key for peer ", i) return nil, E.Cause(err, "decode pre shared key for peer ", i)
} }
preSharedKey = hex.EncodeToString(bytes) peer.PreSharedKey = hex.EncodeToString(bytes)
}
destination := peer.Addr()
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + destination.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
} }
if len(peer.AllowedIPs) == 0 { if len(peer.AllowedIPs) == 0 {
return nil, E.New("missing allowed_ips for peer ", i) return nil, E.New("missing allowed_ips for peer ", i)
} }
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP
}
if len(peer.Reserved) > 0 { if len(peer.Reserved) > 0 {
if len(peer.Reserved) != 3 { if len(peer.Reserved) != 3 {
return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved)) return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved))
} }
copy(reserved[:], option.Reserved)
outbound.bind.SetReservedForEndpoint(destination, reserved)
} }
prefixes, err := peer.Prefixes()
if err != nil {
return nil, err
}
localPrefixes = append(localPrefixes, prefixes...)
} }
} else { } else {
var peerPublicKey, preSharedKey string
{ {
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey) bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
if err != nil { if err != nil {
return nil, E.Cause(err, "decode peer public key") return nil, E.Cause(err, "decode peer public key")
} }
peerPublicKey = hex.EncodeToString(bytes) option.PublicKey = hex.EncodeToString(bytes)
} }
if option.PreSharedKey != "" { if option.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey) bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
if err != nil { if err != nil {
return nil, E.Cause(err, "decode pre shared key") return nil, E.Cause(err, "decode pre shared key")
} }
preSharedKey = hex.EncodeToString(bytes) option.PreSharedKey = hex.EncodeToString(bytes)
}
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + connectAddr.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
}
var err error
localPrefixes, err = option.Prefixes()
if err != nil {
return nil, err
}
var has4, has6 bool
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0"
}
if has6 {
ipcConf += "\nallowed_ip=::/0"
} }
} }
if option.PersistentKeepalive != 0 { var (
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive) initOk atomic.Bool
initMutex sync.Mutex
initErr error
)
outbound.init = func(ctx context.Context) error {
if initOk.Load() {
return nil
}
initMutex.Lock()
defer initMutex.Unlock()
// double check like sync.Once
if initOk.Load() {
return nil
}
if initErr != nil {
return initErr
}
outbound.bind.ResetReservedForEndpoint()
ipcConf := "private_key=" + privateKey
if len(option.Peers) > 0 {
for i, peer := range option.Peers {
destination, err := resolv(ctx, peer.Addr())
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain for peer ", i)
}
ipcConf += "\npublic_key=" + peer.PublicKey
ipcConf += "\nendpoint=" + destination.String()
if peer.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + peer.PreSharedKey
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP
}
if len(peer.Reserved) > 0 {
copy(reserved[:], option.Reserved)
outbound.bind.SetReservedForEndpoint(destination, reserved)
}
}
} else {
ipcConf += "\npublic_key=" + option.PublicKey
destination, err := resolv(ctx, connectAddr)
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain")
}
outbound.bind.SetConnectAddr(destination)
ipcConf += "\nendpoint=" + destination.String()
if option.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + option.PreSharedKey
}
var has4, has6 bool
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0"
}
if has6 {
ipcConf += "\nallowed_ip=::/0"
}
}
if option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
}
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
}
err = outbound.device.IpcSet(ipcConf)
if err != nil {
initErr = E.Cause(err, "setup wireguard")
return initErr
}
err = outbound.tunDevice.Start()
if err != nil {
initErr = err
return initErr
}
initOk.Store(true)
return nil
} }
mtu := option.MTU mtu := option.MTU
if mtu == 0 { if mtu == 0 {
mtu = 1408 mtu = 1408
@@ -265,7 +332,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if len(localPrefixes) == 0 { if len(localPrefixes) == 0 {
return nil, E.New("missing local address") return nil, E.New("missing local address")
} }
var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu)) outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
@@ -278,14 +344,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
}, },
}, option.Workers) }, option.Workers)
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
}
err = outbound.device.IpcSet(ipcConf)
if err != nil {
return nil, E.Cause(err, "setup wireguard")
}
//err = outbound.tunDevice.Start()
var has6 bool var has6 bool
for _, address := range localPrefixes { for _, address := range localPrefixes {
@@ -325,11 +383,8 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
options := w.Base.DialOptions(opts...) options := w.Base.DialOptions(opts...)
w.dialer.SetDialer(dialer.NewDialer(options...)) w.dialer.SetDialer(dialer.NewDialer(options...))
var conn net.Conn var conn net.Conn
w.startOnce.Do(func() { if err = w.init(ctx); err != nil {
w.startErr = w.tunDevice.Start() return nil, err
})
if w.startErr != nil {
return nil, w.startErr
} }
if !metadata.Resolved() || w.resolver != nil { if !metadata.Resolved() || w.resolver != nil {
r := resolver.DefaultResolver r := resolver.DefaultResolver
@@ -357,11 +412,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
options := w.Base.DialOptions(opts...) options := w.Base.DialOptions(opts...)
w.dialer.SetDialer(dialer.NewDialer(options...)) w.dialer.SetDialer(dialer.NewDialer(options...))
var pc net.PacketConn var pc net.PacketConn
w.startOnce.Do(func() { if err = w.init(ctx); err != nil {
w.startErr = w.tunDevice.Start() return nil, err
})
if w.startErr != nil {
return nil, w.startErr
} }
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -21,6 +21,8 @@ type Fallback struct {
testUrl string testUrl string
selected string selected string
expectedStatus string expectedStatus string
Hidden bool
Icon string
} }
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
@@ -90,6 +92,8 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
"testUrl": f.testUrl, "testUrl": f.testUrl,
"expectedStatus": f.expectedStatus, "expectedStatus": f.expectedStatus,
"fixed": f.selected, "fixed": f.selected,
"hidden": f.Hidden,
"icon": f.Icon,
}) })
} }
@@ -137,7 +141,7 @@ func (f *Fallback) Set(name string) error {
if !p.AliveForTestUrl(f.testUrl) { if !p.AliveForTestUrl(f.testUrl) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000)) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel() defer cancel()
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus) expectedStatus, _ := utils.NewUnsignedRanges[uint16](f.expectedStatus)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus) _, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
} }
@@ -160,10 +164,14 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType, option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers, providers,
}), }),
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL, testUrl: option.URL,
expectedStatus: option.ExpectedStatus, expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
} }
} }

View File

@@ -31,14 +31,18 @@ type GroupBase struct {
failedTesting atomic.Bool failedTesting atomic.Bool
proxies [][]C.Proxy proxies [][]C.Proxy
versions []atomic.Uint32 versions []atomic.Uint32
TestTimeout int
maxFailedTimes int
} }
type GroupBaseOption struct { type GroupBaseOption struct {
outbound.BaseOption outbound.BaseOption
filter string filter string
excludeFilter string excludeFilter string
excludeType string excludeType string
providers []provider.ProxyProvider TestTimeout int
maxFailedTimes int
providers []provider.ProxyProvider
} }
func NewGroupBase(opt GroupBaseOption) *GroupBase { func NewGroupBase(opt GroupBaseOption) *GroupBase {
@@ -66,6 +70,15 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
excludeTypeArray: excludeTypeArray, excludeTypeArray: excludeTypeArray,
providers: opt.providers, providers: opt.providers,
failedTesting: atomic.NewBool(false), failedTesting: atomic.NewBool(false),
TestTimeout: opt.TestTimeout,
maxFailedTimes: opt.maxFailedTimes,
}
if gb.TestTimeout == 0 {
gb.TestTimeout = 5000
}
if gb.maxFailedTimes == 0 {
gb.maxFailedTimes = 5
} }
gb.proxies = make([][]C.Proxy, len(opt.providers)) gb.proxies = make([][]C.Proxy, len(opt.providers))
@@ -240,13 +253,13 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
log.Debugln("ProxyGroup: %s first failed", gb.Name()) log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now() gb.failedTime = time.Now()
} else { } else {
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() { if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond {
gb.failedTimes = 0 gb.failedTimes = 0
return return
} }
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes) log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
if gb.failedTimes >= gb.maxFailedTimes() { if gb.failedTimes >= gb.maxFailedTimes {
log.Warnln("because %s failed multiple times, active health check", gb.Name()) log.Warnln("because %s failed multiple times, active health check", gb.Name())
gb.healthCheck() gb.healthCheck()
} }
@@ -275,20 +288,8 @@ func (gb *GroupBase) healthCheck() {
gb.failedTimes = 0 gb.failedTimes = 0
} }
func (gb *GroupBase) failedIntervalTime() int64 {
return 5 * time.Second.Milliseconds()
}
func (gb *GroupBase) onDialSuccess() { func (gb *GroupBase) onDialSuccess() {
if !gb.failedTesting.Load() { if !gb.failedTesting.Load() {
gb.failedTimes = 0 gb.failedTimes = 0
} }
} }
func (gb *GroupBase) maxFailedTimes() int {
return 5
}
func (gb *GroupBase) failedTimeoutInterval() time.Duration {
return 5 * time.Second
}

View File

@@ -29,6 +29,8 @@ type LoadBalance struct {
strategyFn strategyFn strategyFn strategyFn
testUrl string testUrl string
expectedStatus string expectedStatus string
Hidden bool
Icon string
} }
var errStrategy = errors.New("unsupported strategy") var errStrategy = errors.New("unsupported strategy")
@@ -236,6 +238,8 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
"all": all, "all": all,
"testUrl": lb.testUrl, "testUrl": lb.testUrl,
"expectedStatus": lb.expectedStatus, "expectedStatus": lb.expectedStatus,
"hidden": lb.Hidden,
"icon": lb.Icon,
}) })
} }
@@ -262,11 +266,15 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType, option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers, providers,
}), }),
strategyFn: strategyFn, strategyFn: strategyFn,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL, testUrl: option.URL,
expectedStatus: option.ExpectedStatus, expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}, nil }, nil
} }

View File

@@ -28,6 +28,8 @@ type GroupCommonOption struct {
Use []string `group:"use,omitempty"` Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"` URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"` Interval int `group:"interval,omitempty"`
TestTimeout int `group:"timeout,omitempty"`
MaxFailedTimes int `group:"max-failed-times,omitempty"`
Lazy bool `group:"lazy,omitempty"` Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"` Filter string `group:"filter,omitempty"`
@@ -37,6 +39,8 @@ type GroupCommonOption struct {
IncludeAll bool `group:"include-all,omitempty"` IncludeAll bool `group:"include-all,omitempty"`
IncludeAllProxies bool `group:"include-all-proxies,omitempty"` IncludeAllProxies bool `group:"include-all-proxies,omitempty"`
IncludeAllProviders bool `group:"include-all-providers,omitempty"` IncludeAllProviders bool `group:"include-all-providers,omitempty"`
Hidden bool `group:"hidden,omitempty"`
Icon string `group:"icon,omitempty"`
} }
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) { func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) {
@@ -61,24 +65,19 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
groupOption.IncludeAllProviders = true groupOption.IncludeAllProviders = true
groupOption.IncludeAllProxies = true groupOption.IncludeAllProxies = true
} }
var GroupUse []string
var GroupProxies []string
if groupOption.IncludeAllProviders { if groupOption.IncludeAllProviders {
GroupUse = append(GroupUse, AllProviders...) groupOption.Use = append(groupOption.Use, AllProviders...)
} else {
GroupUse = groupOption.Use
} }
if groupOption.IncludeAllProxies { if groupOption.IncludeAllProxies {
GroupProxies = append(groupOption.Proxies, AllProxies...) groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
} else {
GroupProxies = groupOption.Proxies
} }
if len(GroupProxies) == 0 && len(GroupUse) == 0 { if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
} }
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus) expectedStatus, err := utils.NewUnsignedRanges[uint16](groupOption.ExpectedStatus)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, fmt.Errorf("%s: %w", groupName, err)
} }
@@ -88,15 +87,9 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
status = "*" status = "*"
} }
groupOption.ExpectedStatus = status groupOption.ExpectedStatus = status
testUrl := groupOption.URL
if groupOption.URL == "" { if len(groupOption.Proxies) != 0 {
groupOption.URL = C.DefaultTestURL ps, err := getProxies(proxyMap, groupOption.Proxies)
testUrl = groupOption.URL
}
if len(GroupProxies) != 0 {
ps, err := getProxies(proxyMap, GroupProxies)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, fmt.Errorf("%s: %w", groupName, err)
} }
@@ -110,9 +103,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
if groupOption.Interval == 0 { if groupOption.Interval == 0 {
groupOption.Interval = 300 groupOption.Interval = 300
} }
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
} }
hc := provider.NewHealthCheck(ps, testUrl, uint(groupOption.Interval), groupOption.Lazy, expectedStatus) hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
@@ -123,18 +119,28 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
providersMap[groupName] = pd providersMap[groupName] = pd
} }
if len(GroupUse) != 0 { if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, GroupUse) list, err := getProviders(providersMap, groupOption.Use)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, fmt.Errorf("%s: %w", groupName, err)
} }
// different proxy groups use different test URL if groupOption.URL == "" {
addTestUrlToProviders(list, testUrl, expectedStatus, groupOption.Filter, uint(groupOption.Interval)) for _, p := range list {
if p.HealthCheckURL() != "" {
groupOption.URL = p.HealthCheckURL()
}
break
}
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
}
// different proxy groups use different test URL
addTestUrlToProviders(list, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
providers = append(providers, list...) providers = append(providers, list...)
} else {
groupOption.Filter = ""
} }
var group C.ProxyAdapter var group C.ProxyAdapter

View File

@@ -3,6 +3,7 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -12,6 +13,8 @@ import (
type Relay struct { type Relay struct {
*GroupBase *GroupBase
Hidden bool
Icon string
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@@ -106,8 +109,10 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"type": r.Type().String(), "type": r.Type().String(),
"all": all, "all": all,
"hidden": r.Hidden,
"icon": r.Icon,
}) })
} }
@@ -155,7 +160,11 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
"", "",
"", "",
"", "",
5000,
5,
providers, providers,
}), }),
Hidden: option.Hidden,
Icon: option.Icon,
} }
} }

View File

@@ -15,6 +15,8 @@ type Selector struct {
*GroupBase *GroupBase
disableUDP bool disableUDP bool
selected string selected string
Hidden bool
Icon string
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@@ -57,9 +59,11 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"type": s.Type().String(), "type": s.Type().String(),
"now": s.Now(), "now": s.Now(),
"all": all, "all": all,
"hidden": s.Hidden,
"icon": s.Icon,
}) })
} }
@@ -110,9 +114,13 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType, option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers, providers,
}), }),
selected: "COMPATIBLE", selected: "COMPATIBLE",
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
Hidden: option.Hidden,
Icon: option.Icon,
} }
} }

View File

@@ -33,6 +33,8 @@ type URLTest struct {
expectedStatus string expectedStatus string
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
Hidden bool
Icon string
fastNode C.Proxy fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy] fastSingle *singledo.Single[C.Proxy]
} }
@@ -174,6 +176,8 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
"testUrl": u.testUrl, "testUrl": u.testUrl,
"expectedStatus": u.expectedStatus, "expectedStatus": u.expectedStatus,
"fixed": u.selected, "fixed": u.selected,
"hidden": u.Hidden,
"icon": u.Icon,
}) })
} }
@@ -231,12 +235,16 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType, option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers, providers,
}), }),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL, testUrl: option.URL,
expectedStatus: option.ExpectedStatus, expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
} }
for _, option := range options { for _, option := range options {

View File

@@ -120,6 +120,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break break
} }
proxy = outbound.NewDirectWithOption(*directOption) proxy = outbound.NewDirectWithOption(*directOption)
case "dns":
dnsOptions := &outbound.DnsOption{}
err = decoder.Decode(mapping, dnsOptions)
if err != nil {
break
}
proxy = outbound.NewDnsWithOption(*dnsOptions)
case "reject": case "reject":
rejectOption := &outbound.RejectOption{} rejectOption := &outbound.RejectOption{}
err = decoder.Decode(mapping, rejectOption) err = decoder.Decode(mapping, rejectOption)
@@ -127,6 +134,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break break
} }
proxy = outbound.NewRejectWithOption(*rejectOption) proxy = outbound.NewRejectWithOption(*rejectOption)
case "ssh":
sshOption := &outbound.SshOption{}
err = decoder.Decode(mapping, sshOption)
if err != nil {
break
}
proxy, err = outbound.NewSsh(*sshOption)
default: default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
} }

View File

@@ -16,10 +16,6 @@ import (
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
) )
const (
defaultURLTestTimeout = time.Second * 5
)
type HealthCheckOption struct { type HealthCheckOption struct {
URL string URL string
Interval uint Interval uint
@@ -42,6 +38,7 @@ type HealthCheck struct {
lastTouch atomic.TypedValue[time.Time] lastTouch atomic.TypedValue[time.Time]
done chan struct{} done chan struct{}
singleDo *singledo.Single[struct{}] singleDo *singledo.Single[struct{}]
timeout time.Duration
} }
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
@@ -198,7 +195,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
p := proxy p := proxy
b.Go(p.Name(), func() (bool, error) { b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), hc.timeout)
defer cancel() defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid) log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus) _, _ = p.URLTest(ctx, url, expectedStatus)
@@ -212,15 +209,19 @@ func (hc *HealthCheck) close() {
hc.done <- struct{}{} hc.done <- struct{}{}
} }
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck { func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
if url == "" { if url == "" {
// expectedStatus = nil expectedStatus = nil
url = C.DefaultTestURL interval = 0
}
if timeout == 0 {
timeout = 5000
} }
return &HealthCheck{ return &HealthCheck{
proxies: proxies, proxies: proxies,
url: url, url: url,
timeout: time.Duration(timeout) * time.Millisecond,
extra: map[string]*extraOption{}, extra: map[string]*extraOption{},
interval: time.Duration(interval) * time.Second, interval: time.Duration(interval) * time.Second,
lazy: lazy, lazy: lazy,

View File

@@ -22,19 +22,22 @@ type healthCheckSchema struct {
Enable bool `provider:"enable"` Enable bool `provider:"enable"`
URL string `provider:"url"` URL string `provider:"url"`
Interval int `provider:"interval"` Interval int `provider:"interval"`
TestTimeout int `provider:"timeout,omitempty"`
Lazy bool `provider:"lazy,omitempty"` Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"` ExpectedStatus string `provider:"expected-status,omitempty"`
} }
type OverrideSchema struct { type OverrideSchema struct {
UDP *bool `provider:"udp,omitempty"` UDP *bool `provider:"udp,omitempty"`
Up *string `provider:"up,omitempty"` Up *string `provider:"up,omitempty"`
Down *string `provider:"down,omitempty"` Down *string `provider:"down,omitempty"`
DialerProxy *string `provider:"dialer-proxy,omitempty"` DialerProxy *string `provider:"dialer-proxy,omitempty"`
SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"` SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"`
Interface *string `provider:"interface-name,omitempty"` Interface *string `provider:"interface-name,omitempty"`
RoutingMark *int `provider:"routing-mark,omitempty"` RoutingMark *int `provider:"routing-mark,omitempty"`
IPVersion *string `provider:"ip-version,omitempty"` IPVersion *string `provider:"ip-version,omitempty"`
AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
} }
type proxyProviderSchema struct { type proxyProviderSchema struct {
@@ -63,7 +66,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
return nil, err return nil, err
} }
expectedStatus, err := utils.NewIntRanges[uint16](schema.HealthCheck.ExpectedStatus) expectedStatus, err := utils.NewUnsignedRanges[uint16](schema.HealthCheck.ExpectedStatus)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -75,7 +78,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
} }
hcInterval = uint(schema.HealthCheck.Interval) hcInterval = uint(schema.HealthCheck.Interval)
} }
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus) hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus)
var vehicle types.Vehicle var vehicle types.Vehicle
switch schema.Type { switch schema.Type {

View File

@@ -46,18 +46,13 @@ type proxySetProvider struct {
} }
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if pp.healthCheck.expectedStatus != nil {
expectedStatus = pp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": pp.Name(), "name": pp.Name(),
"type": pp.Type().String(), "type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(), "vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(), "proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url, "testUrl": pp.healthCheck.url,
"expectedStatus": expectedStatus, "expectedStatus": pp.healthCheck.expectedStatus.String(),
"updatedAt": pp.UpdatedAt, "updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo, "subscriptionInfo": pp.subscriptionInfo,
}) })
@@ -106,6 +101,10 @@ func (pp *proxySetProvider) Touch() {
pp.healthCheck.touch() pp.healthCheck.touch()
} }
func (pp *proxySetProvider) HealthCheckURL() string {
return pp.healthCheck.url
}
func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) { func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval) pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
} }
@@ -217,18 +216,13 @@ type compatibleProvider struct {
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if cp.healthCheck.expectedStatus != nil {
expectedStatus = cp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": cp.Name(), "name": cp.Name(),
"type": cp.Type().String(), "type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(), "vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(), "proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url, "testUrl": cp.healthCheck.url,
"expectedStatus": expectedStatus, "expectedStatus": cp.healthCheck.expectedStatus.String(),
}) })
} }
@@ -271,6 +265,10 @@ func (cp *compatibleProvider) Touch() {
cp.healthCheck.touch() cp.healthCheck.touch()
} }
func (cp *compatibleProvider) HealthCheckURL() string {
return cp.healthCheck.url
}
func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) { func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval) cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
} }
@@ -399,6 +397,14 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if override.IPVersion != nil { if override.IPVersion != nil {
mapping["ip-version"] = *override.IPVersion mapping["ip-version"] = *override.IPVersion
} }
if override.AdditionalPrefix != nil {
name := mapping["name"].(string)
mapping["name"] = *override.AdditionalPrefix + name
}
if override.AdditionalSuffix != nil {
name := mapping["name"].(string)
mapping["name"] = name + *override.AdditionalSuffix
}
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {

21
android_tz.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89
package main
// #include <time.h>
import "C"
import "time"
func init() {
var currentT C.time_t
var currentTM C.struct_tm
C.time(&currentT)
C.localtime_r(&currentT, &currentTM)
tzOffset := int(currentTM.tm_gmtoff)
tz := C.GoString(currentTM.tm_zone)
time.Local = time.FixedZone(tz, tzOffset)
}

View File

@@ -15,20 +15,31 @@ type TypedValue[T any] struct {
value atomic.Value value atomic.Value
} }
// tValue is a struct with determined type to resolve atomic.Value usages with interface types
// https://github.com/golang/go/issues/22550
//
// The intention to have an atomic value store for errors. However, running this code panics:
// panic: sync/atomic: store of inconsistently typed value into Value
// This is because atomic.Value requires that the underlying concrete type be the same (which is a reasonable expectation for its implementation).
// When going through the atomic.Value.Store method call, the fact that both these are of the error interface is lost.
type tValue[T any] struct {
value T
}
func (t *TypedValue[T]) Load() T { func (t *TypedValue[T]) Load() T {
value := t.value.Load() value := t.value.Load()
if value == nil { if value == nil {
return DefaultValue[T]() return DefaultValue[T]()
} }
return value.(T) return value.(tValue[T]).value
} }
func (t *TypedValue[T]) Store(value T) { func (t *TypedValue[T]) Store(value T) {
t.value.Store(value) t.value.Store(tValue[T]{value})
} }
func (t *TypedValue[T]) Swap(new T) T { func (t *TypedValue[T]) Swap(new T) T {
old := t.value.Swap(new) old := t.value.Swap(tValue[T]{new})
if old == nil { if old == nil {
return DefaultValue[T]() return DefaultValue[T]()
} }
@@ -36,7 +47,7 @@ func (t *TypedValue[T]) Swap(new T) T {
} }
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool { func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
return t.value.CompareAndSwap(old, new) return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new})
} }
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) { func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {

View File

@@ -68,7 +68,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure")) hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
proxies = append(proxies, hysteria) proxies = append(proxies, hysteria)
case "hysteria2":
case "hysteria2", "hy2":
urlHysteria2, err := url.Parse(line) urlHysteria2, err := url.Parse(line)
if err != nil { if err != nil {
continue continue
@@ -79,7 +80,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria2 := make(map[string]any, 20) hysteria2 := make(map[string]any, 20)
hysteria2["name"] = name hysteria2["name"] = name
hysteria2["type"] = scheme hysteria2["type"] = "hysteria2"
hysteria2["server"] = urlHysteria2.Hostname() hysteria2["server"] = urlHysteria2.Hostname()
if port := urlHysteria2.Port(); port != "" { if port := urlHysteria2.Port(); port != "" {
hysteria2["port"] = port hysteria2["port"] = port
@@ -101,6 +102,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria2["up"] = query.Get("up") hysteria2["up"] = query.Get("up")
proxies = append(proxies, hysteria2) proxies = append(proxies, hysteria2)
case "tuic": case "tuic":
// A temporary unofficial TUIC share link standard // A temporary unofficial TUIC share link standard
// Modified from https://github.com/daeuniverse/dae/discussions/182 // Modified from https://github.com/daeuniverse/dae/discussions/182
@@ -143,7 +145,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
proxies = append(proxies, tuic) proxies = append(proxies, tuic)
case "trojan": case "trojan":
urlTrojan, err := url.Parse(line) urlTrojan, err := url.Parse(line)
if err != nil { if err != nil {
@@ -405,14 +407,27 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if query.Get("udp-over-tcp") == "true" || query.Get("uot") == "1" { if query.Get("udp-over-tcp") == "true" || query.Get("uot") == "1" {
ss["udp-over-tcp"] = true ss["udp-over-tcp"] = true
} }
if strings.Contains(query.Get("plugin"), "obfs") { plugin := query.Get("plugin")
obfsParams := strings.Split(query.Get("plugin"), ";") if strings.Contains(plugin, ";") {
ss["plugin"] = "obfs" pluginInfo, _ := url.ParseQuery("pluginName=" + strings.ReplaceAll(plugin, ";", "&"))
ss["plugin-opts"] = map[string]any{ pluginName := pluginInfo.Get("pluginName")
"host": obfsParams[2][10:], if strings.Contains(pluginName, "obfs") {
"mode": obfsParams[1][5:], ss["plugin"] = "obfs"
ss["plugin-opts"] = map[string]any{
"mode": pluginInfo.Get("obfs"),
"host": pluginInfo.Get("obfs-host"),
}
} else if strings.Contains(pluginName, "v2ray-plugin") {
ss["plugin"] = "v2ray-plugin"
ss["plugin-opts"] = map[string]any{
"mode": pluginInfo.Get("mode"),
"host": pluginInfo.Get("host"),
"path": pluginInfo.Get("path"),
"tls": strings.Contains(plugin, "tls"),
}
} }
} }
proxies = append(proxies, ss) proxies = append(proxies, ss)
case "ssr": case "ssr":

View File

@@ -26,6 +26,11 @@ type Conn struct {
resultCh chan *connReadResult resultCh chan *connReadResult
} }
func IsConn(conn any) bool {
_, ok := conn.(*Conn)
return ok
}
func NewConn(conn net.Conn) *Conn { func NewConn(conn net.Conn) *Conn {
c := &Conn{ c := &Conn{
ExtendedConn: bufio.NewExtendedConn(conn), ExtendedConn: bufio.NewExtendedConn(conn),

View File

@@ -0,0 +1,222 @@
package deadline
import (
"io"
"net"
"os"
"sync"
"time"
"github.com/sagernet/sing/common/buf"
N "github.com/sagernet/sing/common/network"
)
type pipeAddr struct{}
func (pipeAddr) Network() string { return "pipe" }
func (pipeAddr) String() string { return "pipe" }
type pipe struct {
wrMu sync.Mutex // Serialize Write operations
// Used by local Read to interact with remote Write.
// Successful receive on rdRx is always followed by send on rdTx.
rdRx <-chan []byte
rdTx chan<- int
// Used by local Write to interact with remote Read.
// Successful send on wrTx is always followed by receive on wrRx.
wrTx chan<- []byte
wrRx <-chan int
once sync.Once // Protects closing localDone
localDone chan struct{}
remoteDone <-chan struct{}
readDeadline pipeDeadline
writeDeadline pipeDeadline
readWaitOptions N.ReadWaitOptions
}
// Pipe creates a synchronous, in-memory, full duplex
// network connection; both ends implement the Conn interface.
// Reads on one end are matched with writes on the other,
// copying data directly between the two; there is no internal
// buffering.
func Pipe() (net.Conn, net.Conn) {
cb1 := make(chan []byte)
cb2 := make(chan []byte)
cn1 := make(chan int)
cn2 := make(chan int)
done1 := make(chan struct{})
done2 := make(chan struct{})
p1 := &pipe{
rdRx: cb1, rdTx: cn1,
wrTx: cb2, wrRx: cn2,
localDone: done1, remoteDone: done2,
readDeadline: makePipeDeadline(),
writeDeadline: makePipeDeadline(),
}
p2 := &pipe{
rdRx: cb2, rdTx: cn2,
wrTx: cb1, wrRx: cn1,
localDone: done2, remoteDone: done1,
readDeadline: makePipeDeadline(),
writeDeadline: makePipeDeadline(),
}
return p1, p2
}
func (*pipe) LocalAddr() net.Addr { return pipeAddr{} }
func (*pipe) RemoteAddr() net.Addr { return pipeAddr{} }
func (p *pipe) Read(b []byte) (int, error) {
n, err := p.read(b)
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
err = &net.OpError{Op: "read", Net: "pipe", Err: err}
}
return n, err
}
func (p *pipe) read(b []byte) (n int, err error) {
switch {
case isClosedChan(p.localDone):
return 0, io.ErrClosedPipe
case isClosedChan(p.remoteDone):
return 0, io.EOF
case isClosedChan(p.readDeadline.wait()):
return 0, os.ErrDeadlineExceeded
}
select {
case bw := <-p.rdRx:
nr := copy(b, bw)
p.rdTx <- nr
return nr, nil
case <-p.localDone:
return 0, io.ErrClosedPipe
case <-p.remoteDone:
return 0, io.EOF
case <-p.readDeadline.wait():
return 0, os.ErrDeadlineExceeded
}
}
func (p *pipe) Write(b []byte) (int, error) {
n, err := p.write(b)
if err != nil && err != io.ErrClosedPipe {
err = &net.OpError{Op: "write", Net: "pipe", Err: err}
}
return n, err
}
func (p *pipe) write(b []byte) (n int, err error) {
switch {
case isClosedChan(p.localDone):
return 0, io.ErrClosedPipe
case isClosedChan(p.remoteDone):
return 0, io.ErrClosedPipe
case isClosedChan(p.writeDeadline.wait()):
return 0, os.ErrDeadlineExceeded
}
p.wrMu.Lock() // Ensure entirety of b is written together
defer p.wrMu.Unlock()
for once := true; once || len(b) > 0; once = false {
select {
case p.wrTx <- b:
nw := <-p.wrRx
b = b[nw:]
n += nw
case <-p.localDone:
return n, io.ErrClosedPipe
case <-p.remoteDone:
return n, io.ErrClosedPipe
case <-p.writeDeadline.wait():
return n, os.ErrDeadlineExceeded
}
}
return n, nil
}
func (p *pipe) SetDeadline(t time.Time) error {
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
return io.ErrClosedPipe
}
p.readDeadline.set(t)
p.writeDeadline.set(t)
return nil
}
func (p *pipe) SetReadDeadline(t time.Time) error {
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
return io.ErrClosedPipe
}
p.readDeadline.set(t)
return nil
}
func (p *pipe) SetWriteDeadline(t time.Time) error {
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
return io.ErrClosedPipe
}
p.writeDeadline.set(t)
return nil
}
func (p *pipe) Close() error {
p.once.Do(func() { close(p.localDone) })
return nil
}
var _ N.ReadWaiter = (*pipe)(nil)
func (p *pipe) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
p.readWaitOptions = options
return false
}
func (p *pipe) WaitReadBuffer() (buffer *buf.Buffer, err error) {
buffer, err = p.waitReadBuffer()
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
err = &net.OpError{Op: "read", Net: "pipe", Err: err}
}
return
}
func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
switch {
case isClosedChan(p.localDone):
return nil, io.ErrClosedPipe
case isClosedChan(p.remoteDone):
return nil, io.EOF
case isClosedChan(p.readDeadline.wait()):
return nil, os.ErrDeadlineExceeded
}
select {
case bw := <-p.rdRx:
buffer = p.readWaitOptions.NewBuffer()
var nr int
nr, err = buffer.Write(bw)
if err != nil {
buffer.Release()
return
}
p.readWaitOptions.PostReturn(buffer)
p.rdTx <- nr
return
case <-p.localDone:
return nil, io.ErrClosedPipe
case <-p.remoteDone:
return nil, io.EOF
case <-p.readDeadline.wait():
return nil, os.ErrDeadlineExceeded
}
}
func IsPipe(conn any) bool {
_, ok := conn.(*pipe)
return ok
}

View File

@@ -3,10 +3,9 @@ package net
import ( import (
"net" "net"
"sync" "sync"
"sync/atomic"
"unsafe"
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
"github.com/metacubex/mihomo/common/once"
) )
type earlyConn struct { type earlyConn struct {
@@ -44,8 +43,7 @@ func (conn *earlyConn) Upstream() any {
} }
func (conn *earlyConn) Success() bool { func (conn *earlyConn) Success() bool {
// atomic visit sync.Once.done return once.Done(&conn.resOnce) && conn.resErr == nil
return atomic.LoadUint32((*uint32)(unsafe.Pointer(&conn.resOnce))) == 1 && conn.resErr == nil
} }
func (conn *earlyConn) ReaderReplaceable() bool { func (conn *earlyConn) ReaderReplaceable() bool {

View File

@@ -23,6 +23,12 @@ type ExtendedReader = network.ExtendedReader
var WriteBuffer = bufio.WriteBuffer var WriteBuffer = bufio.WriteBuffer
func NewDeadlineConn(conn net.Conn) ExtendedConn { func NewDeadlineConn(conn net.Conn) ExtendedConn {
if deadline.IsPipe(conn) || deadline.IsPipe(network.UnwrapReader(conn)) {
return NewExtendedConn(conn) // pipe always have correctly deadline implement
}
if deadline.IsConn(conn) || deadline.IsConn(network.UnwrapReader(conn)) {
return NewExtendedConn(conn) // was a *deadline.Conn
}
return deadline.NewConn(conn) return deadline.NewConn(conn)
} }
@@ -35,6 +41,8 @@ func NeedHandshake(conn any) bool {
type CountFunc = network.CountFunc type CountFunc = network.CountFunc
var Pipe = deadline.Pipe
// Relay copies between left and right bidirectionally. // Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) { func Relay(leftConn, rightConn net.Conn) {
defer runtime.KeepAlive(leftConn) defer runtime.KeepAlive(leftConn)

26
common/once/once_go120.go Normal file
View File

@@ -0,0 +1,26 @@
//go:build !go1.22
package once
import (
"sync"
"sync/atomic"
"unsafe"
)
type Once struct {
done uint32
m sync.Mutex
}
func Done(once *sync.Once) bool {
// atomic visit sync.Once.done
return atomic.LoadUint32((*uint32)(unsafe.Pointer(once))) == 1
}
func Reset(once *sync.Once) {
o := (*Once)(unsafe.Pointer(once))
o.m.Lock()
defer o.m.Unlock()
atomic.StoreUint32(&o.done, 0)
}

26
common/once/once_go122.go Normal file
View File

@@ -0,0 +1,26 @@
//go:build go1.22
package once
import (
"sync"
"sync/atomic"
"unsafe"
)
type Once struct {
done atomic.Uint32
m sync.Mutex
}
func Done(once *sync.Once) bool {
// atomic visit sync.Once.done
return (*atomic.Uint32)(unsafe.Pointer(once)).Load() == 1
}
func Reset(once *sync.Once) {
o := (*Once)(unsafe.Pointer(once))
o.m.Lock()
defer o.m.Unlock()
o.done.Store(0)
}

View File

@@ -13,22 +13,24 @@ type IntRanges[T constraints.Integer] []Range[T]
var errIntRanges = errors.New("intRanges error") var errIntRanges = errors.New("intRanges error")
func NewIntRanges[T constraints.Integer](expected string) (IntRanges[T], error) { func newIntRanges[T constraints.Integer](expected string, parseFn func(string) (T, error)) (IntRanges[T], error) {
// example: 200 or 200/302 or 200-400 or 200/204/401-429/501-503 // example: 200 or 200/302 or 200-400 or 200/204/401-429/501-503
expected = strings.TrimSpace(expected) expected = strings.TrimSpace(expected)
if len(expected) == 0 || expected == "*" { if len(expected) == 0 || expected == "*" {
return nil, nil return nil, nil
} }
// support: 200,302 or 200,204,401-429,501-503
expected = strings.ReplaceAll(expected, ",", "/")
list := strings.Split(expected, "/") list := strings.Split(expected, "/")
if len(list) > 28 { if len(list) > 28 {
return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges) return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges)
} }
return NewIntRangesFromList[T](list) return newIntRangesFromList[T](list, parseFn)
} }
func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], error) { func newIntRangesFromList[T constraints.Integer](list []string, parseFn func(string) (T, error)) (IntRanges[T], error) {
var ranges IntRanges[T] var ranges IntRanges[T]
for _, s := range list { for _, s := range list {
if s == "" { if s == "" {
@@ -41,16 +43,16 @@ func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], e
return nil, errIntRanges return nil, errIntRanges
} }
start, err := strconv.ParseInt(strings.Trim(status[0], "[ ]"), 10, 64) start, err := parseFn(strings.Trim(status[0], "[ ]"))
if err != nil { if err != nil {
return nil, errIntRanges return nil, errIntRanges
} }
switch statusLen { switch statusLen {
case 1: case 1: // Port range
ranges = append(ranges, NewRange(T(start), T(start))) ranges = append(ranges, NewRange(T(start), T(start)))
case 2: case 2: // Single port
end, err := strconv.ParseUint(strings.Trim(status[1], "[ ]"), 10, 64) end, err := parseFn(strings.Trim(status[1], "[ ]"))
if err != nil { if err != nil {
return nil, errIntRanges return nil, errIntRanges
} }
@@ -62,6 +64,38 @@ func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], e
return ranges, nil return ranges, nil
} }
func parseUnsigned[T constraints.Unsigned](s string) (T, error) {
if val, err := strconv.ParseUint(s, 10, 64); err == nil {
return T(val), nil
} else {
return 0, err
}
}
func NewUnsignedRanges[T constraints.Unsigned](expected string) (IntRanges[T], error) {
return newIntRanges(expected, parseUnsigned[T])
}
func NewUnsignedRangesFromList[T constraints.Unsigned](list []string) (IntRanges[T], error) {
return newIntRangesFromList(list, parseUnsigned[T])
}
func parseSigned[T constraints.Signed](s string) (T, error) {
if val, err := strconv.ParseInt(s, 10, 64); err == nil {
return T(val), nil
} else {
return 0, err
}
}
func NewSignedRanges[T constraints.Signed](expected string) (IntRanges[T], error) {
return newIntRanges(expected, parseSigned[T])
}
func NewSignedRangesFromList[T constraints.Signed](list []string) (IntRanges[T], error) {
return newIntRangesFromList(list, parseSigned[T])
}
func (ranges IntRanges[T]) Check(status T) bool { func (ranges IntRanges[T]) Check(status T) bool {
if len(ranges) == 0 { if len(ranges) == 0 {
return true return true
@@ -76,7 +110,7 @@ func (ranges IntRanges[T]) Check(status T) bool {
return false return false
} }
func (ranges IntRanges[T]) ToString() string { func (ranges IntRanges[T]) String() string {
if len(ranges) == 0 { if len(ranges) == 0 {
return "*" return "*"
} }
@@ -98,3 +132,17 @@ func (ranges IntRanges[T]) ToString() string {
return strings.Join(terms, "/") return strings.Join(terms, "/")
} }
func (ranges IntRanges[T]) Range(f func(t T) bool) {
if len(ranges) == 0 {
return
}
for _, r := range ranges {
for i := r.Start(); i <= r.End(); i++ {
if !f(i) {
return
}
}
}
}

View File

@@ -0,0 +1,66 @@
package cidr
import (
"fmt"
"net/netip"
"unsafe"
"go4.org/netipx"
)
type IpCidrSet struct {
// must same with netipx.IPSet
rr []netipx.IPRange
}
func NewIpCidrSet() *IpCidrSet {
return &IpCidrSet{}
}
func (set *IpCidrSet) AddIpCidrForString(ipCidr string) error {
prefix, err := netip.ParsePrefix(ipCidr)
if err != nil {
return err
}
return set.AddIpCidr(prefix)
}
func (set *IpCidrSet) AddIpCidr(ipCidr netip.Prefix) (err error) {
if r := netipx.RangeOfPrefix(ipCidr); r.IsValid() {
set.rr = append(set.rr, r)
} else {
err = fmt.Errorf("not valid ipcidr range: %s", ipCidr)
}
return
}
func (set *IpCidrSet) IsContainForString(ipString string) bool {
ip, err := netip.ParseAddr(ipString)
if err != nil {
return false
}
return set.IsContain(ip)
}
func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
return set.toIPSet().Contains(ip.WithZone(""))
}
func (set *IpCidrSet) Merge() error {
var b netipx.IPSetBuilder
b.AddSet(set.toIPSet())
i, err := b.IPSet()
if err != nil {
return err
}
set.fromIPSet(i)
return nil
}
func (set *IpCidrSet) toIPSet() *netipx.IPSet {
return (*netipx.IPSet)(unsafe.Pointer(set))
}
func (set *IpCidrSet) fromIPSet(i *netipx.IPSet) {
*set = *(*IpCidrSet)(unsafe.Pointer(i))
}

View File

@@ -0,0 +1,107 @@
package cidr
import (
"testing"
)
func TestIpv4(t *testing.T) {
tests := []struct {
name string
ipCidr string
ip string
expected bool
}{
{
name: "Test Case 1",
ipCidr: "149.154.160.0/20",
ip: "149.154.160.0",
expected: true,
},
{
name: "Test Case 2",
ipCidr: "192.168.0.0/16",
ip: "10.0.0.1",
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
set := &IpCidrSet{}
set.AddIpCidrForString(test.ipCidr)
result := set.IsContainForString(test.ip)
if result != test.expected {
t.Errorf("Expected result: %v, got: %v", test.expected, result)
}
})
}
}
func TestIpv6(t *testing.T) {
tests := []struct {
name string
ipCidr string
ip string
expected bool
}{
{
name: "Test Case 1",
ipCidr: "2409:8000::/20",
ip: "2409:8087:1e03:21::27",
expected: true,
},
{
name: "Test Case 2",
ipCidr: "240e::/16",
ip: "240e:964:ea02:100:1800::71",
expected: true,
},
}
// Add more test cases as needed
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
set := &IpCidrSet{}
set.AddIpCidrForString(test.ipCidr)
result := set.IsContainForString(test.ip)
if result != test.expected {
t.Errorf("Expected result: %v, got: %v", test.expected, result)
}
})
}
}
func TestMerge(t *testing.T) {
tests := []struct {
name string
ipCidr1 string
ipCidr2 string
ipCidr3 string
expectedLen int
}{
{
name: "Test Case 1",
ipCidr1: "2409:8000::/20",
ipCidr2: "2409:8000::/21",
ipCidr3: "2409:8000::/48",
expectedLen: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
set := &IpCidrSet{}
set.AddIpCidrForString(test.ipCidr1)
set.AddIpCidrForString(test.ipCidr2)
set.Merge()
rangesLen := len(set.rr)
if rangesLen != test.expectedLen {
t.Errorf("Expected len: %v, got: %v", test.expectedLen, rangesLen)
}
})
}
}

View File

@@ -14,6 +14,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
if err != nil { if err != nil {
return nil, err return nil, err
} }
destination = destination.Unmap()
var addr netip.Prefix var addr netip.Prefix
switch network { switch network {
@@ -23,7 +24,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
addr, err = ifaceObj.PickIPv6Addr(destination) addr, err = ifaceObj.PickIPv6Addr(destination)
default: default:
if destination.IsValid() { if destination.IsValid() {
if destination.Is4() || destination.Is4In6() { if destination.Is4() {
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
} else { } else {
addr, err = ifaceObj.PickIPv6Addr(destination) addr, err = ifaceObj.PickIPv6Addr(destination)

View File

@@ -7,18 +7,26 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/log"
)
const (
DefaultTCPTimeout = 5 * time.Second
DefaultUDPTimeout = DefaultTCPTimeout
) )
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
var ( var (
dialMux sync.Mutex dialMux sync.Mutex
IP4PEnable bool
actualSingleStackDialContext = serialSingleStackDialContext actualSingleStackDialContext = serialSingleStackDialContext
actualDualStackDialContext = serialDualStackDialContext actualDualStackDialContext = serialDualStackDialContext
tcpConcurrent = false tcpConcurrent = false
@@ -123,7 +131,13 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
return dialContextHooked(ctx, network, destination, port) return dialContextHooked(ctx, network, destination, port)
} }
address := net.JoinHostPort(destination.String(), port) var address string
if IP4PEnable {
NewDestination, NewPort := lookupIP4P(destination.String(), port)
address = net.JoinHostPort(NewDestination, NewPort)
} else {
address = net.JoinHostPort(destination.String(), port)
}
netDialer := opt.netDialer netDialer := opt.netDialer
switch netDialer.(type) { switch netDialer.(type) {
@@ -378,3 +392,21 @@ func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...) opt := applyOptions(options...)
return Dialer{Opt: *opt} return Dialer{Opt: *opt}
} }
func GetIP4PEnable(enableIP4PConvert bool) {
IP4PEnable = enableIP4PConvert
}
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
func lookupIP4P(addr string, port string) (string, string) {
ip := net.ParseIP(addr)
if ip[0] == 0x20 && ip[1] == 0x01 &&
ip[2] == 0x00 && ip[3] == 0x00 {
addr = net.IPv4(ip[12], ip[13], ip[14], ip[15]).String()
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr, port))
return addr, port
}
return addr, port
}

View File

@@ -2,10 +2,11 @@ package dialer
import ( import (
"context" "context"
"github.com/sagernet/tfo-go"
"io" "io"
"net" "net"
"time" "time"
"github.com/metacubex/tfo-go"
) )
type tfoConn struct { type tfoConn struct {
@@ -66,14 +67,14 @@ func (c *tfoConn) Close() error {
func (c *tfoConn) LocalAddr() net.Addr { func (c *tfoConn) LocalAddr() net.Addr {
if c.Conn == nil { if c.Conn == nil {
return nil return &net.TCPAddr{}
} }
return c.Conn.LocalAddr() return c.Conn.LocalAddr()
} }
func (c *tfoConn) RemoteAddr() net.Addr { func (c *tfoConn) RemoteAddr() net.Addr {
if c.Conn == nil { if c.Conn == nil {
return nil return &net.TCPAddr{}
} }
return c.Conn.RemoteAddr() return c.Conn.RemoteAddr()
} }
@@ -123,7 +124,7 @@ func (c *tfoConn) WriterReplaceable() bool {
} }
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) { func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false} dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{ return &tfoConn{
dialed: make(chan bool, 1), dialed: make(chan bool, 1),

View File

@@ -14,8 +14,11 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
var initGeoSite bool var (
var initGeoIP int initGeoSite bool
initGeoIP int
initASN bool
)
func InitGeoSite() error { func InitGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
@@ -113,7 +116,7 @@ func InitGeoIP() error {
} }
if initGeoIP != 2 { if initGeoIP != 2 {
if !mmdb.Verify() { if !mmdb.Verify(C.Path.MMDB()) {
log.Warnln("MMDB invalid, remove and download") log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil { if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
@@ -126,3 +129,27 @@ func InitGeoIP() error {
} }
return nil return nil
} }
func InitASN() error {
if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) {
log.Infoln("Can't find ASN.mmdb, start download")
if err := mmdb.DownloadASN(C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN.mmdb: %s", err.Error())
}
log.Infoln("Download ASN.mmdb finish")
initASN = false
}
if !initASN {
if !mmdb.Verify(C.Path.ASN()) {
log.Warnln("ASN invalid, remove and download")
if err := os.Remove(C.Path.ASN()); err != nil {
return fmt.Errorf("can't remove invalid ASN: %s", err.Error())
}
if err := mmdb.DownloadASN(C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN: %s", err.Error())
}
}
initASN = true
}
return nil
}

View File

@@ -1,12 +1,11 @@
package router package router
import ( import (
"encoding/binary"
"fmt" "fmt"
"net" "net/netip"
"sort"
"strings" "strings"
"github.com/metacubex/mihomo/component/cidr"
"github.com/metacubex/mihomo/component/geodata/strmatcher" "github.com/metacubex/mihomo/component/geodata/strmatcher"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
) )
@@ -121,208 +120,37 @@ func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
return isMatched return isMatched
} }
// CIDRList is an alias of []*CIDR to provide sort.Interface.
type CIDRList []*CIDR
// Len implements sort.Interface.
func (l *CIDRList) Len() int {
return len(*l)
}
// Less implements sort.Interface.
func (l *CIDRList) Less(i int, j int) bool {
ci := (*l)[i]
cj := (*l)[j]
if len(ci.Ip) < len(cj.Ip) {
return true
}
if len(ci.Ip) > len(cj.Ip) {
return false
}
for k := 0; k < len(ci.Ip); k++ {
if ci.Ip[k] < cj.Ip[k] {
return true
}
if ci.Ip[k] > cj.Ip[k] {
return false
}
}
return ci.Prefix < cj.Prefix
}
// Swap implements sort.Interface.
func (l *CIDRList) Swap(i int, j int) {
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
}
type ipv6 struct {
a uint64
b uint64
}
type GeoIPMatcher struct { type GeoIPMatcher struct {
countryCode string countryCode string
reverseMatch bool reverseMatch bool
ip4 []uint32 cidrSet *cidr.IpCidrSet
prefix4 []uint8
ip6 []ipv6
prefix6 []uint8
}
func normalize4(ip uint32, prefix uint8) uint32 {
return (ip >> (32 - prefix)) << (32 - prefix)
}
func normalize6(ip ipv6, prefix uint8) ipv6 {
if prefix <= 64 {
ip.a = (ip.a >> (64 - prefix)) << (64 - prefix)
ip.b = 0
} else {
ip.b = (ip.b >> (128 - prefix)) << (128 - prefix)
}
return ip
} }
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error { func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
ip4Count := 0
ip6Count := 0
for _, cidr := range cidrs { for _, cidr := range cidrs {
ip := cidr.Ip addr, ok := netip.AddrFromSlice(cidr.Ip)
switch len(ip) { if !ok {
case 4: return fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
ip4Count++ }
case 16: err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
ip6Count++ if err != nil {
default: return fmt.Errorf("error when loading GeoIP: %w", err)
return fmt.Errorf("unexpect ip length: %d", len(ip))
} }
} }
return m.cidrSet.Merge()
cidrList := CIDRList(cidrs)
sort.Sort(&cidrList)
m.ip4 = make([]uint32, 0, ip4Count)
m.prefix4 = make([]uint8, 0, ip4Count)
m.ip6 = make([]ipv6, 0, ip6Count)
m.prefix6 = make([]uint8, 0, ip6Count)
for _, cidr := range cidrs {
ip := cidr.Ip
prefix := uint8(cidr.Prefix)
switch len(ip) {
case 4:
m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix))
m.prefix4 = append(m.prefix4, prefix)
case 16:
ip6 := ipv6{
a: binary.BigEndian.Uint64(ip[0:8]),
b: binary.BigEndian.Uint64(ip[8:16]),
}
ip6 = normalize6(ip6, prefix)
m.ip6 = append(m.ip6, ip6)
m.prefix6 = append(m.prefix6, prefix)
}
}
return nil
} }
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) { func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
m.reverseMatch = isReverseMatch m.reverseMatch = isReverseMatch
} }
func (m *GeoIPMatcher) match4(ip uint32) bool {
if len(m.ip4) == 0 {
return false
}
if ip < m.ip4[0] {
return false
}
size := uint32(len(m.ip4))
l := uint32(0)
r := size
for l < r {
x := ((l + r) >> 1)
if ip < m.ip4[x] {
r = x
continue
}
nip := normalize4(ip, m.prefix4[x])
if nip == m.ip4[x] {
return true
}
l = x + 1
}
return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1]
}
func less6(a ipv6, b ipv6) bool {
return a.a < b.a || (a.a == b.a && a.b < b.b)
}
func (m *GeoIPMatcher) match6(ip ipv6) bool {
if len(m.ip6) == 0 {
return false
}
if less6(ip, m.ip6[0]) {
return false
}
size := uint32(len(m.ip6))
l := uint32(0)
r := size
for l < r {
x := (l + r) / 2
if less6(ip, m.ip6[x]) {
r = x
continue
}
if normalize6(ip, m.prefix6[x]) == m.ip6[x] {
return true
}
l = x + 1
}
return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1]
}
// Match returns true if the given ip is included by the GeoIP. // Match returns true if the given ip is included by the GeoIP.
func (m *GeoIPMatcher) Match(ip net.IP) bool { func (m *GeoIPMatcher) Match(ip netip.Addr) bool {
switch len(ip) { match := m.cidrSet.IsContain(ip)
case 4: if m.reverseMatch {
if m.reverseMatch { return !match
return !m.match4(binary.BigEndian.Uint32(ip))
}
return m.match4(binary.BigEndian.Uint32(ip))
case 16:
if m.reverseMatch {
return !m.match6(ipv6{
a: binary.BigEndian.Uint64(ip[0:8]),
b: binary.BigEndian.Uint64(ip[8:16]),
})
}
return m.match6(ipv6{
a: binary.BigEndian.Uint64(ip[0:8]),
b: binary.BigEndian.Uint64(ip[8:16]),
})
default:
return false
} }
return match
} }
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code. // GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
@@ -344,6 +172,7 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
m := &GeoIPMatcher{ m := &GeoIPMatcher{
countryCode: geoip.CountryCode, countryCode: geoip.CountryCode,
reverseMatch: geoip.ReverseMatch, reverseMatch: geoip.ReverseMatch,
cidrSet: cidr.NewIpCidrSet(),
} }
if err := m.Init(geoip.Cidr); err != nil { if err := m.Init(geoip.Cidr); err != nil {
return nil, err return nil, err
@@ -369,8 +198,7 @@ func NewGeoIPMatcher(geoip *GeoIP) (*GeoIPMatcher, error) {
return matcher, nil return matcher, nil
} }
func (m *MultiGeoIPMatcher) ApplyIp(ip net.IP) bool { func (m *MultiGeoIPMatcher) ApplyIp(ip netip.Addr) bool {
for _, matcher := range m.matchers { for _, matcher := range m.matchers {
if matcher.Match(ip) { if matcher.Match(ip) {
return true return true

View File

@@ -3,19 +3,37 @@ package geodata
import ( import (
"errors" "errors"
"fmt" "fmt"
"golang.org/x/sync/singleflight"
"strings" "strings"
"golang.org/x/sync/singleflight"
"github.com/metacubex/mihomo/component/geodata/router" "github.com/metacubex/mihomo/component/geodata/router"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
var geoLoaderName = "memconservative" var (
var geoSiteMatcher = "succinct" geoMode bool
AutoUpdate bool
UpdateInterval int
geoLoaderName = "memconservative"
geoSiteMatcher = "succinct"
)
// geoLoaderName = "standard" // geoLoaderName = "standard"
func GeodataMode() bool {
return geoMode
}
func GeoAutoUpdate() bool {
return AutoUpdate
}
func GeoUpdateInterval() int {
return UpdateInterval
}
func LoaderName() string { func LoaderName() string {
return geoLoaderName return geoLoaderName
} }
@@ -24,6 +42,16 @@ func SiteMatcherName() string {
return geoSiteMatcher return geoSiteMatcher
} }
func SetGeodataMode(newGeodataMode bool) {
geoMode = newGeodataMode
}
func SetGeoAutoUpdate(newAutoUpdate bool) {
AutoUpdate = newAutoUpdate
}
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
UpdateInterval = newGeoUpdateInterval
}
func SetLoader(newLoader string) { func SetLoader(newLoader string) {
if newLoader == "memc" { if newLoader == "memc" {
newLoader = "memconservative" newLoader = "memconservative"

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"net" "net"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/metacubex/mihomo/common/singledo" "github.com/metacubex/mihomo/common/singledo"
@@ -38,28 +37,26 @@ func ResolveInterface(name string) (*Interface, error) {
if err != nil { if err != nil {
continue continue
} }
// if not available device like Meta, dummy0, docker0, etc.
if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&net.FlagRunning == 0) {
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) var pf netip.Prefix
ip, _ := netip.AddrFromSlice(ipNet.IP) switch ipNet := addr.(type) {
case *net.IPNet:
//unavailable IPv6 Address ip, _ := netip.AddrFromSlice(ipNet.IP)
if ip.Is6() && strings.HasPrefix(ip.String(), "fe80") { ones, bits := ipNet.Mask.Size()
continue if bits == 32 {
} ip = ip.Unmap()
}
ones, bits := ipNet.Mask.Size() pf = netip.PrefixFrom(ip, ones)
if bits == 32 { case *net.IPAddr:
ip, _ := netip.AddrFromSlice(ipNet.IP)
ip = ip.Unmap() ip = ip.Unmap()
pf = netip.PrefixFrom(ip, ip.BitLen())
}
if pf.IsValid() {
ipNets = append(ipNets, pf)
} }
pf := netip.PrefixFrom(ip, ones)
ipNets = append(ipNets, pf)
} }
r[iface.Name] = &Interface{ r[iface.Name] = &Interface{

View File

@@ -8,6 +8,7 @@ import (
"sync" "sync"
"time" "time"
mihomoOnce "github.com/metacubex/mihomo/common/once"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -24,56 +25,58 @@ const (
) )
var ( var (
reader Reader IPreader IPReader
once sync.Once ASNreader ASNReader
IPonce sync.Once
ASNonce sync.Once
) )
func LoadFromBytes(buffer []byte) { func LoadFromBytes(buffer []byte) {
once.Do(func() { IPonce.Do(func() {
mmdb, err := maxminddb.FromBytes(buffer) mmdb, err := maxminddb.FromBytes(buffer)
if err != nil { if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error()) log.Fatalln("Can't load mmdb: %s", err.Error())
} }
reader = Reader{Reader: mmdb} IPreader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType { switch mmdb.Metadata.DatabaseType {
case "sing-geoip": case "sing-geoip":
reader.databaseType = typeSing IPreader.databaseType = typeSing
case "Meta-geoip0": case "Meta-geoip0":
reader.databaseType = typeMetaV0 IPreader.databaseType = typeMetaV0
default: default:
reader.databaseType = typeMaxmind IPreader.databaseType = typeMaxmind
} }
}) })
} }
func Verify() bool { func Verify(path string) bool {
instance, err := maxminddb.Open(C.Path.MMDB()) instance, err := maxminddb.Open(path)
if err == nil { if err == nil {
instance.Close() instance.Close()
} }
return err == nil return err == nil
} }
func Instance() Reader { func IPInstance() IPReader {
once.Do(func() { IPonce.Do(func() {
mmdbPath := C.Path.MMDB() mmdbPath := C.Path.MMDB()
log.Debugln("Load MMDB file: %s", mmdbPath) log.Infoln("Load MMDB file: %s", mmdbPath)
mmdb, err := maxminddb.Open(mmdbPath) mmdb, err := maxminddb.Open(mmdbPath)
if err != nil { if err != nil {
log.Fatalln("Can't load MMDB: %s", err.Error()) log.Fatalln("Can't load MMDB: %s", err.Error())
} }
reader = Reader{Reader: mmdb} IPreader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType { switch mmdb.Metadata.DatabaseType {
case "sing-geoip": case "sing-geoip":
reader.databaseType = typeSing IPreader.databaseType = typeSing
case "Meta-geoip0": case "Meta-geoip0":
reader.databaseType = typeMetaV0 IPreader.databaseType = typeMetaV0
default: default:
reader.databaseType = typeMaxmind IPreader.databaseType = typeMaxmind
} }
}) })
return reader return IPreader
} }
func DownloadMMDB(path string) (err error) { func DownloadMMDB(path string) (err error) {
@@ -94,3 +97,44 @@ func DownloadMMDB(path string) (err error) {
return err return err
} }
func ASNInstance() ASNReader {
ASNonce.Do(func() {
ASNPath := C.Path.ASN()
log.Infoln("Load ASN file: %s", ASNPath)
asn, err := maxminddb.Open(ASNPath)
if err != nil {
log.Fatalln("Can't load ASN: %s", err.Error())
}
ASNreader = ASNReader{Reader: asn}
})
return ASNreader
}
func DownloadASN(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func ReloadIP() {
mihomoOnce.Reset(&IPonce)
}
func ReloadASN() {
mihomoOnce.Reset(&ASNonce)
}

View File

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

View File

@@ -3,9 +3,9 @@ package mmdb
import ( import (
"fmt" "fmt"
"net" "net"
"strings"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
"github.com/sagernet/sing/common"
) )
type geoip2Country struct { type geoip2Country struct {
@@ -14,12 +14,21 @@ type geoip2Country struct {
} `maxminddb:"country"` } `maxminddb:"country"`
} }
type Reader struct { type IPReader struct {
*maxminddb.Reader *maxminddb.Reader
databaseType databaseType
} }
func (r Reader) LookupCode(ipAddress net.IP) []string { type ASNReader struct {
*maxminddb.Reader
}
type ASNResult struct {
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
}
func (r IPReader) LookupCode(ipAddress net.IP) []string {
switch r.databaseType { switch r.databaseType {
case typeMaxmind: case typeMaxmind:
var country geoip2Country var country geoip2Country
@@ -27,7 +36,7 @@ func (r Reader) LookupCode(ipAddress net.IP) []string {
if country.Country.IsoCode == "" { if country.Country.IsoCode == "" {
return []string{} return []string{}
} }
return []string{country.Country.IsoCode} return []string{strings.ToLower(country.Country.IsoCode)}
case typeSing: case typeSing:
var code string var code string
@@ -44,9 +53,11 @@ func (r Reader) LookupCode(ipAddress net.IP) []string {
case string: case string:
return []string{record} return []string{record}
case []any: // lookup returned type of slice is []any case []any: // lookup returned type of slice is []any
return common.Map(record, func(it any) string { result := make([]string, 0, len(record))
return it.(string) for _, item := range record {
}) result = append(result, item.(string))
}
return result
} }
return []string{} return []string{}
@@ -54,3 +65,9 @@ func (r Reader) LookupCode(ipAddress net.IP) []string {
panic(fmt.Sprint("unknown geoip database type:", r.databaseType)) panic(fmt.Sprint("unknown geoip database type:", r.databaseType))
} }
} }
func (r ASNReader) LookupASN(ip net.IP) ASNResult {
var result ASNResult
r.Lookup(ip, &result)
return result
}

22
component/power/event.go Normal file
View File

@@ -0,0 +1,22 @@
package power
type Type uint8
const (
SUSPEND Type = iota
RESUME
RESUMEAUTOMATIC // Because the user is not present, most applications should do nothing.
)
func (t Type) String() string {
switch t {
case SUSPEND:
return "SUSPEND"
case RESUME:
return "RESUME"
case RESUMEAUTOMATIC:
return "RESUMEAUTOMATIC"
default:
return ""
}
}

View File

@@ -0,0 +1,9 @@
//go:build !windows
package power
import "errors"
func NewEventListener(cb func(Type)) (func(), error) {
return nil, errors.New("not support on this platform")
}

View File

@@ -0,0 +1,74 @@
package power
// modify from https://github.com/golang/go/blob/b634f6fdcbebee23b7da709a243f3db217b64776/src/runtime/os_windows.go#L257
import (
"runtime"
"unsafe"
"golang.org/x/sys/windows"
)
var (
libPowrProf = windows.NewLazySystemDLL("powrprof.dll")
powerRegisterSuspendResumeNotification = libPowrProf.NewProc("PowerRegisterSuspendResumeNotification")
powerUnregisterSuspendResumeNotification = libPowrProf.NewProc("PowerUnregisterSuspendResumeNotification")
)
func NewEventListener(cb func(Type)) (func(), error) {
if err := powerRegisterSuspendResumeNotification.Find(); err != nil {
return nil, err // Running on Windows 7, where we don't need it anyway.
}
if err := powerUnregisterSuspendResumeNotification.Find(); err != nil {
return nil, err // Running on Windows 7, where we don't need it anyway.
}
// Defines the type of event
const (
PBT_APMSUSPEND uint32 = 4
PBT_APMRESUMESUSPEND uint32 = 7
PBT_APMRESUMEAUTOMATIC uint32 = 18
)
const (
_DEVICE_NOTIFY_CALLBACK = 2
)
type _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct {
callback uintptr
context uintptr
}
var fn interface{} = func(context uintptr, changeType uint32, setting uintptr) uintptr {
switch changeType {
case PBT_APMSUSPEND:
cb(SUSPEND)
case PBT_APMRESUMESUSPEND:
cb(RESUME)
case PBT_APMRESUMEAUTOMATIC:
cb(RESUMEAUTOMATIC)
}
return 0
}
params := _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{
callback: windows.NewCallback(fn),
}
handle := uintptr(0)
_, _, err := powerRegisterSuspendResumeNotification.Call(
_DEVICE_NOTIFY_CALLBACK,
uintptr(unsafe.Pointer(&params)),
uintptr(unsafe.Pointer(&handle)),
)
if err != nil {
return nil, err
}
return func() {
_, _, _ = powerUnregisterSuspendResumeNotification.Call(
uintptr(unsafe.Pointer(&handle)),
)
runtime.KeepAlive(params)
runtime.KeepAlive(handle)
}, nil
}

View File

@@ -0,0 +1,34 @@
package proxydialer
import (
"context"
"net"
"net/netip"
"github.com/metacubex/mihomo/component/slowdown"
C "github.com/metacubex/mihomo/constant"
)
type SlowDownDialer struct {
C.Dialer
Slowdown *slowdown.SlowDown
}
func (d SlowDownDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return slowdown.Do(d.Slowdown, ctx, func() (net.Conn, error) {
return d.Dialer.DialContext(ctx, network, address)
})
}
func (d SlowDownDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
return slowdown.Do(d.Slowdown, ctx, func() (net.PacketConn, error) {
return d.Dialer.ListenPacket(ctx, network, address, rAddrPort)
})
}
func NewSlowDownDialer(d C.Dialer, sd *slowdown.SlowDown) SlowDownDialer {
return SlowDownDialer{
Dialer: d,
Slowdown: sd,
}
}

View File

@@ -0,0 +1,33 @@
package proxydialer
import (
"context"
"net"
"github.com/metacubex/mihomo/component/slowdown"
M "github.com/sagernet/sing/common/metadata"
)
type SlowDownSingDialer struct {
SingDialer
Slowdown *slowdown.SlowDown
}
func (d SlowDownSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return slowdown.Do(d.Slowdown, ctx, func() (net.Conn, error) {
return d.SingDialer.DialContext(ctx, network, destination)
})
}
func (d SlowDownSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return slowdown.Do(d.Slowdown, ctx, func() (net.PacketConn, error) {
return d.SingDialer.ListenPacket(ctx, destination)
})
}
func NewSlowDownSingDialer(d SingDialer, sd *slowdown.SlowDown) SlowDownSingDialer {
return SlowDownSingDialer{
SingDialer: d,
Slowdown: sd,
}
}

View File

@@ -3,6 +3,8 @@ package resolver
import ( import (
"errors" "errors"
"net/netip" "net/netip"
"os"
"strconv"
"strings" "strings"
_ "unsafe" _ "unsafe"
@@ -11,6 +13,8 @@ import (
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"
) )
var DisableSystemHosts, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_HOSTS"))
type Hosts struct { type Hosts struct {
*trie.DomainTrie[HostValue] *trie.DomainTrie[HostValue]
} }
@@ -47,7 +51,7 @@ func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
return &hostValue, false return &hostValue, false
} }
if !isDomain { if !isDomain && !DisableSystemHosts {
addr, _ := lookupStaticHost(domain) addr, _ := lookupStaticHost(domain)
if hostValue, err := NewHostValue(addr); err == nil { if hostValue, err := NewHostValue(addr); err == nil {
return &hostValue, true return &hostValue, true

View File

@@ -0,0 +1,88 @@
package resolver
import (
"context"
"encoding/binary"
"io"
"net"
"time"
"github.com/metacubex/mihomo/common/pool"
D "github.com/miekg/dns"
)
const DefaultDnsReadTimeout = time.Second * 10
const DefaultDnsRelayTimeout = time.Second * 5
const SafeDnsPacketSize = 2 * 1024 // safe size which is 1232 from https://dnsflagday.net/2020/, so 2048 is enough
func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration) error {
buff := pool.Get(pool.UDPBufferSize)
defer func() {
_ = pool.Put(buff)
_ = conn.Close()
}()
for {
if readTimeout > 0 {
_ = conn.SetReadDeadline(time.Now().Add(readTimeout))
}
length := uint16(0)
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
break
}
if int(length) > len(buff) {
break
}
n, err := io.ReadFull(conn, buff[:length])
if err != nil {
break
}
err = func() error {
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
defer cancel()
inData := buff[:n]
msg, err := RelayDnsPacket(ctx, inData, buff)
if err != nil {
return err
}
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
if err != nil {
return err
}
_, err = conn.Write(msg)
if err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
}
return nil
}
func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) {
msg := &D.Msg{}
if err := msg.Unpack(payload); err != nil {
return nil, err
}
r, err := ServeMsg(ctx, msg)
if err != nil {
m := new(D.Msg)
m.SetRcode(msg, D.RcodeServerFailure)
return m.PackBuffer(target)
}
r.SetRcode(msg, r.Rcode)
r.Compress = true
return r.PackBuffer(target)
}

View File

@@ -13,10 +13,6 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
) )
const (
minInterval = time.Minute * 5
)
var ( var (
fileMode os.FileMode = 0o666 fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755 dirMode os.FileMode = 0o755
@@ -164,8 +160,8 @@ func (f *Fetcher[V]) Destroy() error {
func (f *Fetcher[V]) pullLoop() { func (f *Fetcher[V]) pullLoop() {
initialInterval := f.interval - time.Since(f.UpdatedAt) initialInterval := f.interval - time.Since(f.UpdatedAt)
if initialInterval < minInterval { if initialInterval > f.interval {
initialInterval = minInterval initialInterval = f.interval
} }
timer := time.NewTimer(initialInterval) timer := time.NewTimer(initialInterval)

View File

@@ -0,0 +1,101 @@
// modify from https://github.com/jpillora/backoff/blob/v1.0.0/backoff.go
package slowdown
import (
"math"
"math/rand"
"sync/atomic"
"time"
)
// Backoff is a time.Duration counter, starting at Min. After every call to
// the Duration method the current timing is multiplied by Factor, but it
// never exceeds Max.
//
// Backoff is not generally concurrent-safe, but the ForAttempt method can
// be used concurrently.
type Backoff struct {
attempt atomic.Uint64
// Factor is the multiplying factor for each increment step
Factor float64
// Jitter eases contention by randomizing backoff steps
Jitter bool
// Min and Max are the minimum and maximum values of the counter
Min, Max time.Duration
}
// Duration returns the duration for the current attempt before incrementing
// the attempt counter. See ForAttempt.
func (b *Backoff) Duration() time.Duration {
d := b.ForAttempt(float64(b.attempt.Add(1) - 1))
return d
}
const maxInt64 = float64(math.MaxInt64 - 512)
// ForAttempt returns the duration for a specific attempt. This is useful if
// you have a large number of independent Backoffs, but don't want use
// unnecessary memory storing the Backoff parameters per Backoff. The first
// attempt should be 0.
//
// ForAttempt is concurrent-safe.
func (b *Backoff) ForAttempt(attempt float64) time.Duration {
// Zero-values are nonsensical, so we use
// them to apply defaults
min := b.Min
if min <= 0 {
min = 100 * time.Millisecond
}
max := b.Max
if max <= 0 {
max = 10 * time.Second
}
if min >= max {
// short-circuit
return max
}
factor := b.Factor
if factor <= 0 {
factor = 2
}
//calculate this duration
minf := float64(min)
durf := minf * math.Pow(factor, attempt)
if b.Jitter {
durf = rand.Float64()*(durf-minf) + minf
}
//ensure float64 wont overflow int64
if durf > maxInt64 {
return max
}
dur := time.Duration(durf)
//keep within bounds
if dur < min {
return min
}
if dur > max {
return max
}
return dur
}
// Reset restarts the current attempt counter at zero.
func (b *Backoff) Reset() {
b.attempt.Store(0)
}
// Attempt returns the current attempt counter value.
func (b *Backoff) Attempt() float64 {
return float64(b.attempt.Load())
}
// Copy returns a backoff with equals constraints as the original
func (b *Backoff) Copy() *Backoff {
return &Backoff{
Factor: b.Factor,
Jitter: b.Jitter,
Min: b.Min,
Max: b.Max,
}
}

View File

@@ -0,0 +1,51 @@
package slowdown
import (
"context"
"sync/atomic"
"time"
)
type SlowDown struct {
errTimes atomic.Int64
backoff Backoff
}
func (s *SlowDown) Wait(ctx context.Context) (err error) {
timer := time.NewTimer(s.backoff.Duration())
defer timer.Stop()
select {
case <-timer.C:
case <-ctx.Done():
err = ctx.Err()
}
return
}
func New() *SlowDown {
return &SlowDown{
backoff: Backoff{
Min: 10 * time.Millisecond,
Max: 1 * time.Second,
Factor: 2,
Jitter: true,
},
}
}
func Do[T any](s *SlowDown, ctx context.Context, fn func() (T, error)) (t T, err error) {
if s.errTimes.Load() > 10 {
err = s.Wait(ctx)
if err != nil {
return
}
}
t, err = fn()
if err != nil {
s.errTimes.Add(1)
return
}
s.errTimes.Store(0)
s.backoff.Reset()
return
}

View File

@@ -185,6 +185,10 @@ func addIpv6Cidr(trie *IpCidrTrie, ip net.IP, groupSize int) {
} }
for i := 2; i < groupSize; i += 2 { for i := 2; i < groupSize; i += 2 {
if ip[i] == 0 && ip[i+1] == 0 {
node.Mark = true
}
if node.Mark { if node.Mark {
return return
} }

View File

@@ -74,6 +74,14 @@ func TestIpv6AddFail(t *testing.T) {
assert.IsType(t, new(net.ParseError), err) assert.IsType(t, new(net.ParseError), err)
} }
func TestIpv6SearchSub(t *testing.T) {
trie := NewIpCidrTrie()
assert.NoError(t, trie.AddIpCidrForString("240e::/18"))
assert.Equal(t, true, trie.IsContainForString("240e:964:ea02:100:1800::71"))
}
func TestIpv6Search(t *testing.T) { func TestIpv6Search(t *testing.T) {
trie := NewIpCidrTrie() trie := NewIpCidrTrie()

View File

@@ -152,6 +152,7 @@ type IPTables struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
InboundInterface string `yaml:"inbound-interface" json:"inbound-interface"` InboundInterface string `yaml:"inbound-interface" json:"inbound-interface"`
Bypass []string `yaml:"bypass" json:"bypass"` Bypass []string `yaml:"bypass" json:"bypass"`
DnsRedirect bool `yaml:"dns-redirect" json:"dns-redirect"`
} }
type Sniffer struct { type Sniffer struct {
@@ -168,6 +169,7 @@ type Experimental struct {
Fingerprints []string `yaml:"fingerprints"` Fingerprints []string `yaml:"fingerprints"`
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"` QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"` QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"`
IP4PEnable bool `yaml:"dialer-ip4p-convert"`
} }
// Config is mihomo config manager // Config is mihomo config manager
@@ -263,6 +265,7 @@ type RawTun struct {
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
TableIndex int `yaml:"table-index" json:"table-index"`
} }
type RawTuicServer struct { type RawTuicServer struct {
@@ -346,6 +349,7 @@ type RawConfig struct {
type GeoXUrl struct { type GeoXUrl struct {
GeoIp string `yaml:"geoip" json:"geoip"` GeoIp string `yaml:"geoip" json:"geoip"`
Mmdb string `yaml:"mmdb" json:"mmdb"` Mmdb string `yaml:"mmdb" json:"mmdb"`
ASN string `yaml:"asn" json:"asn"`
GeoSite string `yaml:"geosite" json:"geosite"` GeoSite string `yaml:"geosite" json:"geosite"`
} }
@@ -440,6 +444,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Enable: false, Enable: false,
InboundInterface: "lo", InboundInterface: "lo",
Bypass: []string{}, Bypass: []string{},
DnsRedirect: true,
}, },
NTP: RawNTP{ NTP: RawNTP{
Enable: false, Enable: false,
@@ -492,6 +497,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
}, },
GeoXUrl: GeoXUrl{ GeoXUrl: GeoXUrl{
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb", Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
ASN: "https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb",
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat", 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", GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
}, },
@@ -607,6 +613,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
func parseGeneral(cfg *RawConfig) (*General, error) { func parseGeneral(cfg *RawConfig) (*General, error) {
geodata.SetGeodataMode(cfg.GeodataMode)
geodata.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
geodata.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
geodata.SetLoader(cfg.GeodataLoader) geodata.SetLoader(cfg.GeodataLoader)
geodata.SetSiteMatcher(cfg.GeositeMatcher) geodata.SetSiteMatcher(cfg.GeositeMatcher)
C.GeoAutoUpdate = cfg.GeoAutoUpdate C.GeoAutoUpdate = cfg.GeoAutoUpdate
@@ -614,6 +623,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
C.GeoIpUrl = cfg.GeoXUrl.GeoIp C.GeoIpUrl = cfg.GeoXUrl.GeoIp
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
C.MmdbUrl = cfg.GeoXUrl.Mmdb C.MmdbUrl = cfg.GeoXUrl.Mmdb
C.ASNUrl = cfg.GeoXUrl.ASN
C.GeodataMode = cfg.GeodataMode C.GeodataMode = cfg.GeodataMode
C.UA = cfg.GlobalUA C.UA = cfg.GlobalUA
if cfg.KeepAliveInterval != 0 { if cfg.KeepAliveInterval != 0 {
@@ -775,7 +785,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
} }
ps = append(ps, proxies[v]) ps = append(ps, proxies[v])
} }
hc := provider.NewHealthCheck(ps, "", 0, true, nil) hc := provider.NewHealthCheck(ps, "", 5000, 0, true, nil)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
providersMap[provider.ReservedName] = pd providersMap[provider.ReservedName] = pd
@@ -1439,6 +1449,7 @@ func parseTun(rawTun RawTun, general *General) error {
EndpointIndependentNat: rawTun.EndpointIndependentNat, EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout, UDPTimeout: rawTun.UDPTimeout,
FileDescriptor: rawTun.FileDescriptor, FileDescriptor: rawTun.FileDescriptor,
TableIndex: rawTun.TableIndex,
} }
return nil return nil
@@ -1473,7 +1484,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
if len(snifferRaw.Sniff) != 0 { if len(snifferRaw.Sniff) != 0 {
for sniffType, sniffConfig := range snifferRaw.Sniff { for sniffType, sniffConfig := range snifferRaw.Sniff {
find := false find := false
ports, err := utils.NewIntRangesFromList[uint16](sniffConfig.Ports) ports, err := utils.NewUnsignedRangesFromList[uint16](sniffConfig.Ports)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1500,7 +1511,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
// Deprecated: Use Sniff instead // Deprecated: Use Sniff instead
log.Warnln("Deprecated: Use Sniff instead") log.Warnln("Deprecated: Use Sniff instead")
} }
globalPorts, err := utils.NewIntRangesFromList[uint16](snifferRaw.Ports) globalPorts, err := utils.NewUnsignedRangesFromList[uint16](snifferRaw.Ports)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -6,6 +6,7 @@ import (
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard" _ "github.com/metacubex/mihomo/component/geodata/standard"
"github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
@@ -33,6 +34,7 @@ func UpdateGeoDatabases() error {
} }
} else { } else {
defer mmdb.ReloadIP()
data, err := downloadForBytes(C.MmdbUrl) data, err := downloadForBytes(C.MmdbUrl)
if err != nil { if err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err) return fmt.Errorf("can't download MMDB database file: %w", err)
@@ -43,11 +45,32 @@ func UpdateGeoDatabases() error {
return fmt.Errorf("invalid MMDB database file: %s", err) return fmt.Errorf("invalid MMDB database file: %s", err)
} }
_ = instance.Close() _ = instance.Close()
mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
if err = saveFile(data, C.Path.MMDB()); err != nil { if err = saveFile(data, C.Path.MMDB()); err != nil {
return fmt.Errorf("can't save MMDB database file: %w", err) return fmt.Errorf("can't save MMDB database file: %w", err)
} }
} }
if C.ASNEnable {
defer mmdb.ReloadASN()
data, err := downloadForBytes(C.ASNUrl)
if err != nil {
return fmt.Errorf("can't download ASN database file: %w", err)
}
instance, err := maxminddb.FromBytes(data)
if err != nil {
return fmt.Errorf("invalid ASN database file: %s", err)
}
_ = instance.Close()
mmdb.ASNInstance().Reader.Close()
if err = saveFile(data, C.Path.ASN()); err != nil {
return fmt.Errorf("can't save ASN database file: %w", err)
}
}
data, err := downloadForBytes(C.GeoSiteUrl) data, err := downloadForBytes(C.GeoSiteUrl)
if err != nil { if err != nil {
return fmt.Errorf("can't download GeoSite database file: %w", err) return fmt.Errorf("can't download GeoSite database file: %w", err)

View File

@@ -21,6 +21,7 @@ const (
RejectDrop RejectDrop
Compatible Compatible
Pass Pass
Dns
Relay Relay
Selector Selector
@@ -40,12 +41,13 @@ const (
Hysteria2 Hysteria2
WireGuard WireGuard
Tuic Tuic
Ssh
) )
const ( const (
DefaultTCPTimeout = 5 * time.Second DefaultTCPTimeout = dialer.DefaultTCPTimeout
DefaultUDPTimeout = dialer.DefaultUDPTimeout
DefaultDropTime = 12 * DefaultTCPTimeout DefaultDropTime = 12 * DefaultTCPTimeout
DefaultUDPTimeout = DefaultTCPTimeout
DefaultTLSTimeout = DefaultTCPTimeout DefaultTLSTimeout = DefaultTCPTimeout
DefaultTestURL = "https://www.gstatic.com/generate_204" DefaultTestURL = "https://www.gstatic.com/generate_204"
) )
@@ -184,6 +186,8 @@ func (at AdapterType) String() string {
return "Compatible" return "Compatible"
case Pass: case Pass:
return "Pass" return "Pass"
case Dns:
return "Dns"
case Shadowsocks: case Shadowsocks:
return "Shadowsocks" return "Shadowsocks"
case ShadowsocksR: case ShadowsocksR:
@@ -219,7 +223,8 @@ func (at AdapterType) String() string {
return "URLTest" return "URLTest"
case LoadBalance: case LoadBalance:
return "LoadBalance" return "LoadBalance"
case Ssh:
return "Ssh"
default: default:
return "Unknown" return "Unknown"
} }

View File

@@ -1,10 +1,12 @@
package constant package constant
var ( var (
ASNEnable bool
GeodataMode bool GeodataMode bool
GeoAutoUpdate bool GeoAutoUpdate bool
GeoUpdateInterval int GeoUpdateInterval int
GeoIpUrl string GeoIpUrl string
MmdbUrl string MmdbUrl string
GeoSiteUrl string GeoSiteUrl string
ASNUrl string
) )

View File

@@ -133,6 +133,8 @@ type Metadata struct {
Type Type `json:"type"` Type Type `json:"type"`
SrcIP netip.Addr `json:"sourceIP"` SrcIP netip.Addr `json:"sourceIP"`
DstIP netip.Addr `json:"destinationIP"` DstIP netip.Addr `json:"destinationIP"`
DstGeoIP []string `json:"destinationGeoIP"` // can be nil if never queried, empty slice if got no result
DstIPASN string `json:"destinationIPASN"`
SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output
DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output
InIP netip.Addr `json:"inboundIP"` InIP netip.Addr `json:"inboundIP"`
@@ -147,6 +149,7 @@ type Metadata struct {
SpecialProxy string `json:"specialProxy"` SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"` SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"` RemoteDst string `json:"remoteDestination"`
DSCP uint8 `json:"dscp"`
RawSrcAddr net.Addr `json:"-"` RawSrcAddr net.Addr `json:"-"`
RawDstAddr net.Addr `json:"-"` RawDstAddr net.Addr `json:"-"`

View File

@@ -15,6 +15,7 @@ const Name = "mihomo"
var ( var (
GeositeName = "GeoSite.dat" GeositeName = "GeoSite.dat"
GeoipName = "GeoIP.dat" GeoipName = "GeoIP.dat"
ASNName = "ASN.mmdb"
) )
// Path is used to get the configuration path // Path is used to get the configuration path
@@ -112,6 +113,25 @@ func (p *path) MMDB() string {
return P.Join(p.homeDir, "geoip.metadb") return P.Join(p.homeDir, "geoip.metadb")
} }
func (p *path) ASN() string {
files, err := os.ReadDir(p.homeDir)
if err != nil {
return ""
}
for _, fi := range files {
if fi.IsDir() {
// 目录则直接跳过
continue
} else {
if strings.EqualFold(fi.Name(), "ASN.mmdb") {
ASNName = fi.Name()
return P.Join(p.homeDir, fi.Name())
}
}
}
return P.Join(p.homeDir, ASNName)
}
func (p *path) OldCache() string { func (p *path) OldCache() string {
return P.Join(p.homeDir, ".cache") return P.Join(p.homeDir, ".cache")
} }

View File

@@ -73,6 +73,7 @@ type ProxyProvider interface {
HealthCheck() HealthCheck()
Version() uint32 Version() uint32
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
HealthCheckURL() string
} }
// RuleProvider interface // RuleProvider interface

View File

@@ -5,15 +5,18 @@ const (
Domain RuleType = iota Domain RuleType = iota
DomainSuffix DomainSuffix
DomainKeyword DomainKeyword
DomainRegex
GEOSITE GEOSITE
GEOIP GEOIP
IPCIDR IPCIDR
IPASN
SrcIPCIDR SrcIPCIDR
IPSuffix IPSuffix
SrcIPSuffix SrcIPSuffix
SrcPort SrcPort
DstPort DstPort
InPort InPort
DSCP
InUser InUser
InName InName
InType InType
@@ -39,12 +42,16 @@ func (rt RuleType) String() string {
return "DomainSuffix" return "DomainSuffix"
case DomainKeyword: case DomainKeyword:
return "DomainKeyword" return "DomainKeyword"
case DomainRegex:
return "DomainRegex"
case GEOSITE: case GEOSITE:
return "GeoSite" return "GeoSite"
case GEOIP: case GEOIP:
return "GeoIP" return "GeoIP"
case IPCIDR: case IPCIDR:
return "IPCIDR" return "IPCIDR"
case IPASN:
return "IPASN"
case SrcIPCIDR: case SrcIPCIDR:
return "SrcIPCIDR" return "SrcIPCIDR"
case IPSuffix: case IPSuffix:
@@ -73,6 +80,8 @@ func (rt RuleType) String() string {
return "RuleSet" return "RuleSet"
case Network: case Network:
return "Network" return "Network"
case DSCP:
return "DSCP"
case Uid: case Uid:
return "Uid" return "Uid"
case SubRules: case SubRules:

View File

@@ -12,6 +12,7 @@ import (
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"
@@ -77,7 +78,9 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
options = append(options, dialer.WithInterface(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)) dialHandler := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)
addr := net.JoinHostPort(ip.String(), c.port)
conn, err := dialHandler(ctx, network, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -97,12 +100,31 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig)) conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
} }
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ dConn := &D.Conn{
Conn: conn, Conn: conn,
UDPSize: c.Client.UDPSize, UDPSize: c.Client.UDPSize,
TsigSecret: c.Client.TsigSecret, TsigSecret: c.Client.TsigSecret,
TsigProvider: c.Client.TsigProvider, TsigProvider: c.Client.TsigProvider,
}) }
msg, _, err := c.Client.ExchangeWithConn(m, dConn)
// Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)!
if msg != nil && msg.Truncated && c.Client.Net == "" {
tcpClient := *c.Client // copy a client
tcpClient.Net = "tcp"
network = "tcp"
log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String())
dConn.Conn, err = dialHandler(ctx, network, addr)
if err != nil {
ch <- result{msg, err}
return
}
defer func() {
_ = conn.Close()
}()
msg, _, err = tcpClient.ExchangeWithConn(m, dConn)
}
ch <- result{msg, err} ch <- result{msg, err}
}() }()

View File

@@ -709,7 +709,8 @@ func (doh *dnsOverHTTPS) tlsDial(ctx context.Context, dialContext dialHandler, n
err = conn.SetDeadline(time.Now().Add(dialTimeout)) err = conn.SetDeadline(time.Now().Add(dialTimeout))
if err != nil { if err != nil {
// Must not happen in normal circumstances. // Must not happen in normal circumstances.
panic(fmt.Errorf("cannot set deadline: %w", err)) log.Errorln("cannot set deadline: %v", err)
return nil, err
} }
err = conn.Handshake() err = conn.Handshake()

View File

@@ -24,7 +24,7 @@ var geoIPMatcher *router.GeoIPMatcher
func (gf *geoipFilter) Match(ip netip.Addr) bool { func (gf *geoipFilter) Match(ip netip.Addr) bool {
if !C.GeodataMode { if !C.GeodataMode {
codes := mmdb.Instance().LookupCode(ip.AsSlice()) codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range codes { for _, code := range codes {
if !strings.EqualFold(code, gf.code) && !ip.IsPrivate() { if !strings.EqualFold(code, gf.code) && !ip.IsPrivate() {
return true return true
@@ -41,7 +41,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
return false return false
} }
} }
return !geoIPMatcher.Match(ip.AsSlice()) return !geoIPMatcher.Match(ip)
} }
type ipnetFilter struct { type ipnetFilter struct {

View File

@@ -49,7 +49,7 @@ func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
func FlushCacheWithDefaultResolver() { func FlushCacheWithDefaultResolver() {
if r := resolver.DefaultResolver; r != nil { if r := resolver.DefaultResolver; r != nil {
r.(*Resolver).lruCache = lru.New[string, *D.Msg](lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) r.(*Resolver).cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
} }
} }

View File

@@ -324,7 +324,7 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (ips []netip.Addr, err error) { func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (ips []netip.Addr, err error) {
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
if err == nil { if err == nil {
isIPv4 := ip.Is4() isIPv4 := ip.Is4() || ip.Is4In6()
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return []netip.Addr{ip}, nil return []netip.Addr{ip}, nil
} else if dnsType == D.TypeA && isIPv4 { } else if dnsType == D.TypeA && isIPv4 {

View File

@@ -289,8 +289,6 @@ 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)
@@ -320,12 +318,12 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
case D.TypeAAAA: case D.TypeAAAA:
if len(ips) == 0 { if len(ips) == 0 {
noIpMsg = m noIpMsg = m
return nil, errIPNotFound return nil, resolver.ErrIPNotFound
} }
case D.TypeA: case D.TypeA:
if len(ips) == 0 { if len(ips) == 0 {
noIpMsg = m noIpMsg = m
return nil, errIPNotFound return nil, resolver.ErrIPNotFound
} }
} }
} }

View File

@@ -674,6 +674,8 @@ proxies: # socks5
type: hysteria2 type: hysteria2
server: server.com server: server.com
port: 443 port: 443
# ports: 1000,2000-3000,5000 # port 不可省略
# hop-interval: 15
# up和down均不写或为0则使用BBR流控 # up和down均不写或为0则使用BBR流控
# up: "30 Mbps" # 若不写单位,默认为 Mbps # up: "30 Mbps" # 若不写单位,默认为 Mbps
# down: "200 Mbps" # 若不写单位,默认为 Mbps # down: "200 Mbps" # 若不写单位,默认为 Mbps
@@ -706,12 +708,10 @@ proxies: # socks5
# dialer-proxy: "ss1" # dialer-proxy: "ss1"
# remote-dns-resolve: true # 强制dns远程解析默认值为false # remote-dns-resolve: true # 强制dns远程解析默认值为false
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效
# 如果peers不为空该段落中的allowed-ips不可为空前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略但private-key会被保留且只能在顶层指定 # 如果peers不为空该段落中的allowed-ips不可为空前面段落的server,port,public-key,pre-shared-key均会被忽略但private-key会被保留且只能在顶层指定
# peers: # peers:
# - server: 162.159.192.1 # - server: 162.159.192.1
# port: 2480 # port: 2480
# ip: 172.16.0.2
# ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
# public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= # public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
# # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
# allowed-ips: ['0.0.0.0/0'] # allowed-ips: ['0.0.0.0/0']
@@ -767,6 +767,18 @@ proxies: # socks5
# protocol-param: "#" # protocol-param: "#"
# udp: true # udp: true
- name: "ssh-out"
type: ssh
server: 127.0.0.1
port: 22
username: root
password: password
privateKey: path
# dns出站会将请求劫持到内部dns模块,所有请求均在内部处理
- name: "dns-out"
type: dns
proxy-groups: proxy-groups:
# 代理链目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic # 代理链目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic
# wireguard目前不支持在relay中使用请使用proxy中的dialer-proxy配置项 # wireguard目前不支持在relay中使用请使用proxy中的dialer-proxy配置项
@@ -788,6 +800,7 @@ proxy-groups:
- vmess1 - vmess1
# tolerance: 150 # tolerance: 150
# lazy: true # lazy: true
# expected-status: 204 # 当健康检查返回状态码与期望值不符时,认为节点不可用
url: "https://cp.cloudflare.com/generate_204" url: "https://cp.cloudflare.com/generate_204"
interval: 300 interval: 300
@@ -851,6 +864,7 @@ proxy-providers:
interval: 600 interval: 600
# lazy: true # lazy: true
url: https://cp.cloudflare.com/generate_204 url: https://cp.cloudflare.com/generate_204
# expected-status: 204 # 当健康检查返回状态码与期望值不符时,认为节点不可用
override: # 覆写节点加载时的一些配置项 override: # 覆写节点加载时的一些配置项
skip-cert-verify: true skip-cert-verify: true
udp: true udp: true
@@ -860,6 +874,8 @@ proxy-providers:
# interface-name: tailscale0 # interface-name: tailscale0
# routing-mark: 233 # routing-mark: 233
# ip-version: ipv4-prefer # ip-version: ipv4-prefer
# additional-prefix: "[provider1]"
# additional-suffix: "test"
test: test:
type: file type: file
path: /test.yaml path: /test.yaml
@@ -881,6 +897,8 @@ rule-providers:
type: file type: file
rules: rules:
- RULE-SET,rule1,REJECT - RULE-SET,rule1,REJECT
- IP-ASN,1,PROXY
- DOMAIN-REGEX,^abc,DIRECT
- DOMAIN-SUFFIX,baidu.com,DIRECT - DOMAIN-SUFFIX,baidu.com,DIRECT
- DOMAIN-KEYWORD,google,ss1 - DOMAIN-KEYWORD,google,ss1
- IP-CIDR,1.1.1.1/32,ss1 - IP-CIDR,1.1.1.1/32,ss1
@@ -1068,4 +1086,4 @@ listeners:
# authentication-timeout: 1000 # authentication-timeout: 1000
# alpn: # alpn:
# - h3 # - h3
# max-udp-relay-packet-size: 1500 # max-udp-relay-packet-size: 1500

62
go.mod
View File

@@ -8,51 +8,51 @@ require (
github.com/bahlo/generic-list-go v0.2.0 github.com/bahlo/generic-list-go v0.2.0
github.com/cilium/ebpf v0.12.3 github.com/cilium/ebpf v0.12.3
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.7.0
github.com/dlclark/regexp2 v1.10.0 github.com/dlclark/regexp2 v1.11.0
github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.3.1 github.com/gobwas/ws v1.3.2
github.com/gofrs/uuid/v5 v5.0.0 github.com/gofrs/uuid/v5 v5.0.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8
github.com/jpillora/backoff v1.0.0 github.com/klauspost/cpuid/v2 v2.2.7
github.com/klauspost/cpuid/v2 v2.2.6
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.2
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c
github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796 github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01
github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks v0.2.6
github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1 github.com/metacubex/sing-shadowsocks2 v0.2.0
github.com/metacubex/sing-tun v0.2.0-beta.4 github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f
github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63
github.com/miekg/dns v1.1.57 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66
github.com/miekg/dns v1.1.58
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21 github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/puzpuzpuz/xsync/v3 v3.0.2 github.com/puzpuzpuz/xsync/v3 v3.1.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.3.0-rc.3 github.com/sagernet/sing v0.3.6
github.com/sagernet/sing-mux v0.1.6-beta.1 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
github.com/sagernet/utls v1.5.4 github.com/sagernet/utls v1.5.4
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e
github.com/samber/lo v1.39.0 github.com/samber/lo v1.39.0
github.com/shirou/gopsutil/v3 v3.23.11 github.com/shirou/gopsutil/v3 v3.24.2
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/zhangyunhao116/fastrand v0.3.0 github.com/zhangyunhao116/fastrand v0.3.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
golang.org/x/crypto v0.16.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb golang.org/x/crypto v0.21.0
golang.org/x/net v0.19.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/sync v0.5.0 golang.org/x/net v0.22.0
golang.org/x/sys v0.15.0 golang.org/x/sync v0.6.0
google.golang.org/protobuf v1.31.0 golang.org/x/sys v0.18.0
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.1 lukechampine.com/blake3 v1.2.1
) )
@@ -84,7 +84,7 @@ require (
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc // indirect github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
@@ -93,7 +93,6 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
@@ -102,14 +101,13 @@ require (
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.4 // 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.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/mod v0.15.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.18.0 // indirect
) )
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542

121
go.sum
View File

@@ -28,8 +28,8 @@ github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFE
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8= github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -44,8 +44,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
@@ -60,16 +60,14 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU= github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -80,18 +78,16 @@ github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
@@ -106,26 +102,28 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc h1:+yTZ6q2EeQCAJNpKNEu5j32Pm23ShD38ElIa635wTrk= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc/go.mod h1:rhBU9tD5ktoGPBtXUquhWuGJ4u+8ZZzBMi2cAdv9q8Y= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 h1:dIT+KB2hknBCrwVAXPeY9tpzzkOZP5m40yqUteRT6/Y= github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c h1:AhaPKvVqF3N/jXFmlW51Cf1+KddslKAsZqcdgGhZjr0=
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs= github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ=
github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f h1:T2PuaAiXMSC3mjRRUmIomuiu3jhi7EWSbzXtVIrVUC4= github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 h1:e9nBnrJBv3HzZVeSzJN0G2SADjebd2ZLF1F5dmsjUTc=
github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796 h1:xiCPttMGAaIh4Ad6t85VxUoUv+Sg88eXzzUvYN8gT5w= github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 h1:5INHs85Gp1JZsdF7fQp1pXUjfJOX2dhwZjuUQWJVSt8=
github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796/go.mod h1:E1e1Uu6YaJddD+c0DtJlSOkfMI0NLdOVhM60KAlcssY= github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8=
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1 h1:ftbpVCK1+n3jxIP7+NMkRYOFEQtGPodV42MizsPey0w= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=
github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1/go.mod h1:kUVj2X+2wUh6Z5pAk9WrjDRehPyXolC6nJyFl7ln4V4= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
github.com/metacubex/sing-tun v0.2.0-beta.4 h1:42F+uF9zKsaWsiUXNKzZD8aRkyPN9m5SdpF2yZEZar8= github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd h1:NgLb6Lvr8ZxX0inWswVYjal2SUzsJJ54dFQNOluUJuE=
github.com/metacubex/sing-tun v0.2.0-beta.4/go.mod h1:O8wFThUDfiwb6y56I714dQuyaqT8DW9VCD/wvGesyLM= github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 h1:loWjR+k9dxqBSgruGyT5hE8UCRMmCEjxqZbryfY9no4= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI=
github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232/go.mod h1:NGCrBZ+fUmp81yaA1kVskcNWBnwl5z4UHxz47A01zm8= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
@@ -148,8 +146,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
@@ -159,24 +157,20 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing-mux v0.1.6-beta.1 h1:ADs1TgiMfA628Y2qfv21tEvePDZjBRRYddwtNFZiwe8= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.1.6-beta.1/go.mod h1:WWtRmrwCDgb+g+7Da6o62I9WiMNB0a3w6BJhEpNQlNA= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI=
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY=
github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo= github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -192,13 +186,15 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -212,35 +208,35 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695AP
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc= github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= 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.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/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.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -256,21 +252,22 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -146,15 +146,18 @@ func GetGeneral() *config.General {
AllowLan: listener.AllowLan(), AllowLan: listener.AllowLan(),
BindAddress: listener.BindAddress(), BindAddress: listener.BindAddress(),
}, },
Controller: config.Controller{}, Controller: config.Controller{},
Mode: tunnel.Mode(), Mode: tunnel.Mode(),
LogLevel: log.Level(), LogLevel: log.Level(),
IPv6: !resolver.DisableIPv6, IPv6: !resolver.DisableIPv6,
GeodataLoader: G.LoaderName(), GeodataMode: G.GeodataMode(),
GeositeMatcher: G.SiteMatcherName(), GeoAutoUpdate: G.GeoAutoUpdate(),
Interface: dialer.DefaultInterface.Load(), GeoUpdateInterval: G.GeoUpdateInterval(),
Sniffing: tunnel.IsSniffing(), GeodataLoader: G.LoaderName(),
TCPConcurrent: dialer.GetTcpConcurrent(), GeositeMatcher: G.SiteMatcherName(),
Interface: dialer.DefaultInterface.Load(),
Sniffing: tunnel.IsSniffing(),
TCPConcurrent: dialer.GetTcpConcurrent(),
} }
return general return general
@@ -194,6 +197,7 @@ func updateExperimental(c *config.Config) {
if c.Experimental.QUICGoDisableECN { if c.Experimental.QUICGoDisableECN {
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true)) _ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
} }
dialer.GetIP4PEnable(c.Experimental.IP4PEnable)
} }
func updateNTP(c *config.NTP) { func updateNTP(c *config.NTP) {
@@ -475,6 +479,9 @@ func updateIPTables(cfg *config.Config) {
bypass = iptables.Bypass bypass = iptables.Bypass
tProxyPort = cfg.General.TProxyPort tProxyPort = cfg.General.TProxyPort
dnsCfg = cfg.DNS dnsCfg = cfg.DNS
DnsRedirect = iptables.DnsRedirect
dnsPort netip.AddrPort
) )
if tProxyPort == 0 { if tProxyPort == 0 {
@@ -482,15 +489,17 @@ func updateIPTables(cfg *config.Config) {
return return
} }
if !dnsCfg.Enable { if DnsRedirect {
err = fmt.Errorf("DNS server must be enable") if !dnsCfg.Enable {
return err = fmt.Errorf("DNS server must be enable")
} return
}
dnsPort, err := netip.ParseAddrPort(dnsCfg.Listen) dnsPort, err = netip.ParseAddrPort(dnsCfg.Listen)
if err != nil { if err != nil {
err = fmt.Errorf("DNS server must be correct") err = fmt.Errorf("DNS server must be correct")
return return
}
} }
if iptables.InboundInterface != "" { if iptables.InboundInterface != "" {
@@ -501,7 +510,7 @@ func updateIPTables(cfg *config.Config) {
dialer.DefaultRoutingMark.Store(2158) dialer.DefaultRoutingMark.Store(2158)
} }
err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), dnsPort.Port()) err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), DnsRedirect, dnsPort.Port())
if err != nil { if err != nil {
return return
} }

View File

@@ -91,6 +91,7 @@ type tunSchema struct {
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
TableIndex *int `yaml:"table-index" json:"table-index"`
} }
type tuicServerSchema struct { type tuicServerSchema struct {
@@ -209,6 +210,9 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.FileDescriptor != nil { if p.FileDescriptor != nil {
def.FileDescriptor = *p.FileDescriptor def.FileDescriptor = *p.FileDescriptor
} }
if p.TableIndex != nil {
def.TableIndex = *p.TableIndex
}
} }
return def return def
} }

View File

@@ -78,7 +78,7 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
return return
} }
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected")) expectedStatus, err := utils.NewUnsignedRanges[uint16](query.Get("expected"))
if err != nil { if err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)

View File

@@ -113,7 +113,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
return return
} }
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected")) expectedStatus, err := utils.NewUnsignedRanges[uint16](query.Get("expected"))
if err != nil { if err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)

View File

@@ -137,6 +137,8 @@ func prepare(exePath string) (err error) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + ".exe" updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + ".exe"
} else if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
updateExeName = "mihomo-android-arm64-v8"
} else { } else {
updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible
} }
@@ -440,7 +442,11 @@ func updateDownloadURL() {
middle = fmt.Sprintf("-%s-%s%s-%s", runtime.GOOS, runtime.GOARCH, goarm, latestVersion) middle = fmt.Sprintf("-%s-%s%s-%s", runtime.GOOS, runtime.GOARCH, goarm, latestVersion)
} else if runtime.GOARCH == "arm64" { } else if runtime.GOARCH == "arm64" {
//-linux-arm64-alpha-e552b54.gz //-linux-arm64-alpha-e552b54.gz
middle = fmt.Sprintf("-%s-%s-%s", runtime.GOOS, runtime.GOARCH, latestVersion) if runtime.GOOS == "android" {
middle = fmt.Sprintf("-%s-%s-v8-%s", runtime.GOOS, runtime.GOARCH, latestVersion)
} else {
middle = fmt.Sprintf("-%s-%s-%s", runtime.GOOS, runtime.GOARCH, latestVersion)
}
} else if isMIPS(runtime.GOARCH) && gomips != "" { } else if isMIPS(runtime.GOARCH) && gomips != "" {
middle = fmt.Sprintf("-%s-%s-%s-%s", runtime.GOOS, runtime.GOARCH, gomips, latestVersion) middle = fmt.Sprintf("-%s-%s-%s-%s", runtime.GOOS, runtime.GOARCH, gomips, latestVersion)
} else { } else {

View File

@@ -21,6 +21,7 @@ type Hysteria2Server struct {
IgnoreClientBandwidth bool `yaml:"ignore-client-bandwidth" json:"ignore-client-bandwidth,omitempty"` IgnoreClientBandwidth bool `yaml:"ignore-client-bandwidth" json:"ignore-client-bandwidth,omitempty"`
Masquerade string `yaml:"masquerade" json:"masquerade,omitempty"` Masquerade string `yaml:"masquerade" json:"masquerade,omitempty"`
CWND int `yaml:"cwnd" json:"cwnd,omitempty"` CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
UdpMTU int `yaml:"udp-mtu" json:"udp-mtu,omitempty"`
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
} }

View File

@@ -49,4 +49,5 @@ type Tun struct {
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
TableIndex int `yaml:"table-index" json:"table-index"`
} }

View File

@@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
@@ -30,7 +31,7 @@ func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
return nil, socks5.ErrAddressNotSupported return nil, socks5.ErrAddressNotSupported
} }
left, right := net.Pipe() left, right := N.Pipe()
go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, srcConn, right, additions...)) go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, srcConn, right, additions...))

View File

@@ -36,7 +36,9 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
} }
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true
additions = []inbound.Addition{ additions = []inbound.Addition{
inbound.WithInName("DEFAULT-HTTP"), inbound.WithInName("DEFAULT-HTTP"),
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
@@ -71,8 +73,8 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
t.SetKeepAlive(false) t.SetKeepAlive(false)
} }
} }
if len(additions) == 0 { // only apply on default listener if isDefault { // only apply on default listener
if inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
_ = conn.Close() _ = conn.Close()
continue continue
} }

View File

@@ -41,7 +41,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, tunnel C.Tunnel, additi
return return
} }
left, right := net.Pipe() left, right := N.Pipe()
go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, conn, right, additions...)) go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, conn, right, additions...))

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"net" "net"
"net/http" "net/http"
"net/netip"
"strings" "strings"
) )
@@ -48,6 +49,11 @@ func removeExtraHTTPHostPort(req *http.Request) {
if pHost, port, err := net.SplitHostPort(host); err == nil && (port == "80" || port == "443") { if pHost, port, err := net.SplitHostPort(host); err == nil && (port == "80" || port == "443") {
host = pHost host = pHost
if ip, err := netip.ParseAddr(pHost); err == nil && ip.Is6() {
// RFC 2617 Sec 3.2.2, for IPv6 literal
// addresses the Host header needs to follow the RFC 2732 grammar for "host"
host = "[" + host + "]"
}
} }
req.Host = host req.Host = host

View File

@@ -21,6 +21,7 @@ type Hysteria2Option struct {
IgnoreClientBandwidth bool `inbound:"ignore-client-bandwidth,omitempty"` IgnoreClientBandwidth bool `inbound:"ignore-client-bandwidth,omitempty"`
Masquerade string `inbound:"masquerade,omitempty"` Masquerade string `inbound:"masquerade,omitempty"`
CWND int `inbound:"cwnd,omitempty"` CWND int `inbound:"cwnd,omitempty"`
UdpMTU int `inbound:"udp-mtu,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"` MuxOption MuxOption `inbound:"mux-option,omitempty"`
} }
@@ -58,6 +59,7 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
IgnoreClientBandwidth: options.IgnoreClientBandwidth, IgnoreClientBandwidth: options.IgnoreClientBandwidth,
Masquerade: options.Masquerade, Masquerade: options.Masquerade,
CWND: options.CWND, CWND: options.CWND,
UdpMTU: options.UdpMTU,
MuxOption: options.MuxOption.Build(), MuxOption: options.MuxOption.Build(),
}, },
}, nil }, nil

View File

@@ -40,6 +40,7 @@ type TunOption struct {
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"` UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"` FileDescriptor int `inbound:"file-descriptor,omitempty"`
TableIndex int `inbound:"table-index,omitempty"`
} }
func (o TunOption) Equal(config C.InboundConfig) bool { func (o TunOption) Equal(config C.InboundConfig) bool {
@@ -118,6 +119,7 @@ func NewTun(options *TunOption) (*Tun, error) {
EndpointIndependentNat: options.EndpointIndependentNat, EndpointIndependentNat: options.EndpointIndependentNat,
UDPTimeout: options.UDPTimeout, UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor, FileDescriptor: options.FileDescriptor,
TableIndex: options.TableIndex,
}, },
}, nil }, nil
} }

View File

@@ -6,6 +6,7 @@ import (
"net/netip" "net/netip"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
) )
@@ -20,7 +21,7 @@ func HandleTcp(address string) (conn net.Conn, err error) {
return nil, errors.New("tcp uninitialized") return nil, errors.New("tcp uninitialized")
} }
// executor Parsed // executor Parsed
conn1, conn2 := net.Pipe() conn1, conn2 := N.Pipe()
metadata := &C.Metadata{} metadata := &C.Metadata{}
metadata.NetWork = C.TCP metadata.NetWork = C.TCP

View File

@@ -823,7 +823,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
LastTunConf.StrictRoute != tunConf.StrictRoute || LastTunConf.StrictRoute != tunConf.StrictRoute ||
LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat || LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat ||
LastTunConf.UDPTimeout != tunConf.UDPTimeout || LastTunConf.UDPTimeout != tunConf.UDPTimeout ||
LastTunConf.FileDescriptor != tunConf.FileDescriptor { LastTunConf.FileDescriptor != tunConf.FileDescriptor ||
LastTunConf.TableIndex != tunConf.TableIndex {
return true return true
} }

View File

@@ -37,7 +37,9 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true
additions = []inbound.Addition{ additions = []inbound.Addition{
inbound.WithInName("DEFAULT-MIXED"), inbound.WithInName("DEFAULT-MIXED"),
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
@@ -62,8 +64,8 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
} }
continue continue
} }
if len(additions) == 0 { // only apply on default listener if isDefault { // only apply on default listener
if inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close() _ = c.Close()
continue continue
} }

View File

@@ -104,6 +104,12 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
} }
} }
if config.UdpMTU == 0 {
// "1200" from quic-go's MaxDatagramSize
// "-3" from quic-go's DatagramFrame.MaxDataLen
config.UdpMTU = 1200 - 3
}
service, err := hysteria2.NewService[string](hysteria2.ServiceOptions{ service, err := hysteria2.NewService[string](hysteria2.ServiceOptions{
Context: context.Background(), Context: context.Background(),
Logger: log.SingLogger, Logger: log.SingLogger,
@@ -115,6 +121,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
Handler: h, Handler: h,
MasqueradeHandler: masqueradeHandler, MasqueradeHandler: masqueradeHandler,
CWND: config.CWND, CWND: config.CWND,
UdpMTU: config.UdpMTU,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -2,29 +2,21 @@ package sing_tun
import ( import (
"context" "context"
"encoding/binary"
"io"
"net" "net"
"net/netip" "net/netip"
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
D "github.com/miekg/dns"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/network"
) )
const DefaultDnsReadTimeout = time.Second * 10
const DefaultDnsRelayTimeout = time.Second * 5
type ListenerHandler struct { type ListenerHandler struct {
*sing.ListenerHandler *sing.ListenerHandler
DnsAdds []netip.AddrPort DnsAdds []netip.AddrPort
@@ -45,61 +37,11 @@ func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
if h.ShouldHijackDns(metadata.Destination.AddrPort()) { if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
log.Debugln("[DNS] hijack tcp:%s", metadata.Destination.String()) log.Debugln("[DNS] hijack tcp:%s", metadata.Destination.String())
buff := pool.Get(pool.UDPBufferSize) return resolver.RelayDnsConn(ctx, conn, resolver.DefaultDnsReadTimeout)
defer func() {
_ = pool.Put(buff)
_ = conn.Close()
}()
for {
if conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) != nil {
break
}
length := uint16(0)
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
break
}
if int(length) > len(buff) {
break
}
n, err := io.ReadFull(conn, buff[:length])
if err != nil {
break
}
err = func() error {
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
defer cancel()
inData := buff[:n]
msg, err := RelayDnsPacket(ctx, inData, buff)
if err != nil {
return err
}
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
if err != nil {
return err
}
_, err = conn.Write(msg)
if err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
}
return nil
} }
return h.ListenerHandler.NewConnection(ctx, conn, metadata) return h.ListenerHandler.NewConnection(ctx, conn, metadata)
} }
const SafeDnsPacketSize = 2 * 1024 // safe size which is 1232 from https://dnsflagday.net/2020/, so 2048 is enough
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
if h.ShouldHijackDns(metadata.Destination.AddrPort()) { if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String()) log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String())
@@ -114,7 +56,7 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
rwOptions := network.ReadWaitOptions{ rwOptions := network.ReadWaitOptions{
FrontHeadroom: network.CalculateFrontHeadroom(conn), FrontHeadroom: network.CalculateFrontHeadroom(conn),
RearHeadroom: network.CalculateRearHeadroom(conn), RearHeadroom: network.CalculateRearHeadroom(conn),
MTU: SafeDnsPacketSize, MTU: resolver.SafeDnsPacketSize,
} }
readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn)
if isReadWaiter { if isReadWaiter {
@@ -126,7 +68,7 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
dest M.Socksaddr dest M.Socksaddr
err error err error
) )
_ = conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) _ = conn.SetReadDeadline(time.Now().Add(resolver.DefaultDnsReadTimeout))
readBuff = nil // clear last loop status, avoid repeat release readBuff = nil // clear last loop status, avoid repeat release
if isReadWaiter { if isReadWaiter {
readBuff, dest, err = readWaiter.WaitReadPacket() readBuff, dest, err = readWaiter.WaitReadPacket()
@@ -147,15 +89,15 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
return err return err
} }
go func() { go func() {
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDnsRelayTimeout)
defer cancel() defer cancel()
inData := readBuff.Bytes() inData := readBuff.Bytes()
writeBuff := readBuff writeBuff := readBuff
writeBuff.Resize(writeBuff.Start(), 0) writeBuff.Resize(writeBuff.Start(), 0)
if len(writeBuff.FreeBytes()) < SafeDnsPacketSize { // only create a new buffer when space don't enough if len(writeBuff.FreeBytes()) < resolver.SafeDnsPacketSize { // only create a new buffer when space don't enough
writeBuff = rwOptions.NewPacketBuffer() writeBuff = rwOptions.NewPacketBuffer()
} }
msg, err := RelayDnsPacket(ctx, inData, writeBuff.FreeBytes()) msg, err := resolver.RelayDnsPacket(ctx, inData, writeBuff.FreeBytes())
if writeBuff != readBuff { if writeBuff != readBuff {
readBuff.Release() readBuff.Release()
} }
@@ -182,21 +124,3 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
} }
return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata)
} }
func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) {
msg := &D.Msg{}
if err := msg.Unpack(payload); err != nil {
return nil, err
}
r, err := resolver.ServeMsg(ctx, msg)
if err != nil {
m := new(D.Msg)
m.SetRcode(msg, D.RcodeServerFailure)
return m.PackBuffer(target)
}
r.SetRcode(msg, r.Rcode)
r.Compress = true
return r.PackBuffer(target)
}

View File

@@ -112,6 +112,10 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} else { } else {
udpTimeout = int64(sing.UDPTimeout.Seconds()) udpTimeout = int64(sing.UDPTimeout.Seconds())
} }
tableIndex := options.TableIndex
if tableIndex == 0 {
tableIndex = 2022
}
includeUID := uidToRange(options.IncludeUID) includeUID := uidToRange(options.IncludeUID)
if len(options.IncludeUIDRange) > 0 { if len(options.IncludeUIDRange) > 0 {
var err error var err error
@@ -225,7 +229,7 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
ExcludePackage: options.ExcludePackage, ExcludePackage: options.ExcludePackage,
FileDescriptor: options.FileDescriptor, FileDescriptor: options.FileDescriptor,
InterfaceMonitor: defaultInterfaceMonitor, InterfaceMonitor: defaultInterfaceMonitor,
TableIndex: 2022, TableIndex: tableIndex,
} }
err = l.buildAndroidRules(&tunOptions) err = l.buildAndroidRules(&tunOptions)

View File

@@ -35,7 +35,9 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true
additions = []inbound.Addition{ additions = []inbound.Addition{
inbound.WithInName("DEFAULT-SOCKS"), inbound.WithInName("DEFAULT-SOCKS"),
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
@@ -59,8 +61,8 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
} }
continue continue
} }
if len(additions) == 0 { // only apply on default listener if isDefault { // only apply on default listener
if inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close() _ = c.Close()
continue continue
} }

View File

@@ -34,7 +34,23 @@ func setsockopt(rc syscall.RawConn, addr string) error {
if err == nil && isIPv6 { if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
} }
if err == nil {
_ = setDSCPsockopt(fd, isIPv6)
}
}) })
return err return err
} }
func setDSCPsockopt(fd uintptr, isIPv6 bool) (err error) {
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVTOS, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, syscall.IPV6_RECVTCLASS, 1)
}
return
}

View File

@@ -15,6 +15,7 @@ var (
dnsPort uint16 dnsPort uint16
tProxyPort uint16 tProxyPort uint16
interfaceName string interfaceName string
DnsRedirect bool
) )
const ( const (
@@ -22,7 +23,7 @@ const (
PROXY_ROUTE_TABLE = "0x2d0" PROXY_ROUTE_TABLE = "0x2d0"
) )
func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint16) error { func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dnsredir bool, dport uint16) error {
if _, err := cmd.ExecCmd("iptables -V"); err != nil { if _, err := cmd.ExecCmd("iptables -V"); err != nil {
return fmt.Errorf("current operations system [%s] are not support iptables or command iptables does not exist", runtime.GOOS) return fmt.Errorf("current operations system [%s] are not support iptables or command iptables does not exist", runtime.GOOS)
} }
@@ -33,6 +34,7 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1
interfaceName = ifname interfaceName = ifname
tProxyPort = tport tProxyPort = tport
DnsRedirect = dnsredir
dnsPort = dport dnsPort = dport
// add route // add route
@@ -58,8 +60,10 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1
execCmd("iptables -t mangle -N mihomo_prerouting") execCmd("iptables -t mangle -N mihomo_prerouting")
execCmd("iptables -t mangle -F mihomo_prerouting") execCmd("iptables -t mangle -F mihomo_prerouting")
execCmd("iptables -t mangle -A mihomo_prerouting -s 172.17.0.0/16 -j RETURN") execCmd("iptables -t mangle -A mihomo_prerouting -s 172.17.0.0/16 -j RETURN")
execCmd("iptables -t mangle -A mihomo_prerouting -p udp --dport 53 -j ACCEPT") if DnsRedirect {
execCmd("iptables -t mangle -A mihomo_prerouting -p tcp --dport 53 -j ACCEPT") execCmd("iptables -t mangle -A mihomo_prerouting -p udp --dport 53 -j ACCEPT")
execCmd("iptables -t mangle -A mihomo_prerouting -p tcp --dport 53 -j ACCEPT")
}
execCmd("iptables -t mangle -A mihomo_prerouting -m addrtype --dst-type LOCAL -j RETURN") execCmd("iptables -t mangle -A mihomo_prerouting -m addrtype --dst-type LOCAL -j RETURN")
addLocalnetworkToChain("mihomo_prerouting", bypass) addLocalnetworkToChain("mihomo_prerouting", bypass)
execCmd("iptables -t mangle -A mihomo_prerouting -p tcp -m socket -j mihomo_divert") execCmd("iptables -t mangle -A mihomo_prerouting -p tcp -m socket -j mihomo_divert")
@@ -68,8 +72,10 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1
execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK)) execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK))
execCmd("iptables -t mangle -A PREROUTING -j mihomo_prerouting") execCmd("iptables -t mangle -A PREROUTING -j mihomo_prerouting")
execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) if DnsRedirect {
execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort))
execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort))
}
// set post routing // set post routing
if interfaceName != "lo" { if interfaceName != "lo" {
@@ -80,8 +86,10 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1
execCmd("iptables -t mangle -N mihomo_output") execCmd("iptables -t mangle -N mihomo_output")
execCmd("iptables -t mangle -F mihomo_output") execCmd("iptables -t mangle -F mihomo_output")
execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load())) execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load()))
execCmd("iptables -t mangle -A mihomo_output -p udp -m multiport --dports 53,123,137 -j ACCEPT") if DnsRedirect {
execCmd("iptables -t mangle -A mihomo_output -p tcp --dport 53 -j ACCEPT") execCmd("iptables -t mangle -A mihomo_output -p udp -m multiport --dports 53,123,137 -j ACCEPT")
execCmd("iptables -t mangle -A mihomo_output -p tcp --dport 53 -j ACCEPT")
}
execCmd("iptables -t mangle -A mihomo_output -m addrtype --dst-type LOCAL -j RETURN") execCmd("iptables -t mangle -A mihomo_output -m addrtype --dst-type LOCAL -j RETURN")
execCmd("iptables -t mangle -A mihomo_output -m addrtype --dst-type BROADCAST -j RETURN") execCmd("iptables -t mangle -A mihomo_output -m addrtype --dst-type BROADCAST -j RETURN")
addLocalnetworkToChain("mihomo_output", bypass) addLocalnetworkToChain("mihomo_output", bypass)
@@ -90,20 +98,22 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1
execCmd(fmt.Sprintf("iptables -t mangle -I OUTPUT -o %s -j mihomo_output", interfaceName)) execCmd(fmt.Sprintf("iptables -t mangle -I OUTPUT -o %s -j mihomo_output", interfaceName))
// set dns output // set dns output
execCmd("iptables -t nat -N mihomo_dns_output") if DnsRedirect {
execCmd("iptables -t nat -F mihomo_dns_output") execCmd("iptables -t nat -N mihomo_dns_output")
execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load())) execCmd("iptables -t nat -F mihomo_dns_output")
execCmd("iptables -t nat -A mihomo_dns_output -s 172.17.0.0/16 -j RETURN") execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load()))
execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -p udp -j REDIRECT --to-ports %d", dnsPort)) execCmd("iptables -t nat -A mihomo_dns_output -s 172.17.0.0/16 -j RETURN")
execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -p tcp -j REDIRECT --to-ports %d", dnsPort)) execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -p udp -j REDIRECT --to-ports %d", dnsPort))
execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j mihomo_dns_output") execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -p tcp -j REDIRECT --to-ports %d", dnsPort))
execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j mihomo_dns_output") execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j mihomo_dns_output")
execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j mihomo_dns_output")
}
return nil return nil
} }
func CleanupTProxyIPTables() { func CleanupTProxyIPTables() {
if runtime.GOOS != "linux" || interfaceName == "" || tProxyPort == 0 || dnsPort == 0 { if runtime.GOOS != "linux" || interfaceName == "" || tProxyPort == 0 {
return return
} }
@@ -130,8 +140,10 @@ func CleanupTProxyIPTables() {
} }
// clean PREROUTING // clean PREROUTING
execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) if DnsRedirect {
execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort))
execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort))
}
execCmd("iptables -t mangle -D PREROUTING -j mihomo_prerouting") execCmd("iptables -t mangle -D PREROUTING -j mihomo_prerouting")
// clean POSTROUTING // clean POSTROUTING
@@ -141,8 +153,10 @@ func CleanupTProxyIPTables() {
// clean OUTPUT // clean OUTPUT
execCmd(fmt.Sprintf("iptables -t mangle -D OUTPUT -o %s -j mihomo_output", interfaceName)) execCmd(fmt.Sprintf("iptables -t mangle -D OUTPUT -o %s -j mihomo_output", interfaceName))
execCmd("iptables -t nat -D OUTPUT -p tcp --dport 53 -j mihomo_dns_output") if DnsRedirect {
execCmd("iptables -t nat -D OUTPUT -p udp --dport 53 -j mihomo_dns_output") execCmd("iptables -t nat -D OUTPUT -p tcp --dport 53 -j mihomo_dns_output")
execCmd("iptables -t nat -D OUTPUT -p udp --dport 53 -j mihomo_dns_output")
}
// clean chain // clean chain
execCmd("iptables -t mangle -F mihomo_prerouting") execCmd("iptables -t mangle -F mihomo_prerouting")
@@ -151,9 +165,10 @@ func CleanupTProxyIPTables() {
execCmd("iptables -t mangle -X mihomo_divert") execCmd("iptables -t mangle -X mihomo_divert")
execCmd("iptables -t mangle -F mihomo_output") execCmd("iptables -t mangle -F mihomo_output")
execCmd("iptables -t mangle -X mihomo_output") execCmd("iptables -t mangle -X mihomo_output")
execCmd("iptables -t nat -F mihomo_dns_output") if DnsRedirect {
execCmd("iptables -t nat -X mihomo_dns_output") execCmd("iptables -t nat -F mihomo_dns_output")
execCmd("iptables -t nat -X mihomo_dns_output")
}
interfaceName = "" interfaceName = ""
tProxyPort = 0 tProxyPort = 0
dnsPort = 0 dnsPort = 0

View File

@@ -79,6 +79,9 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi
continue continue
} }
dscp, _ := getDSCP(oob[:oobn])
additions = append(additions, inbound.WithDSCP(dscp))
if rAddr.Addr().Is4() { if rAddr.Addr().Is4() {
// try to unmap 4in6 address // try to unmap 4in6 address
lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port()) lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port())

Some files were not shown because too many files have changed in this diff Show More