Compare commits

...

249 Commits

Author SHA1 Message Date
xishang0128
43f21c0b41 fix: fallback cannot be unfixed 2024-09-02 16:18:28 +08:00
Larvan2
56fe7d5304 chore: clean up update_ui code 2024-09-02 11:17:35 +08:00
Larvan2
802267fb5b ci: better release 2024-08-31 23:38:31 +08:00
wwqgtxx
6306c6b580 chore: add route.ApplyConfig for CMFA 2024-08-31 22:42:24 +08:00
wwqgtxx
f6164ac195 feat: add fake-ip-filter-mode in dns
https://github.com/MetaCubeX/mihomo/issues/1479
2024-08-31 09:59:48 +08:00
wwqgtxx
08ac9a3fae fix: tfo ipv6 addr zone 2024-08-30 20:09:50 +08:00
wwqgtxx
38fd37108b feat: GEOIP,IP-ASN,IP-CIDR,IP-CIDR6 and IP-SUFFIX in rules support ,src option
keep the same writing style as `RULE-SET`
2024-08-30 00:04:59 +08:00
wwqgtxx
763a127287 feat: RULE-SET in rules support ,src option
should only be used with `ipcidr` behavior
2024-08-29 23:49:16 +08:00
wwqgtxx
a96f72ade4 fix: geoip wrong matching logic in fallback-filter
https://github.com/MetaCubeX/mihomo/issues/1478
2024-08-29 22:00:55 +08:00
wwqgtxx
4fecf68b8b chore: add sourceGeoIP and sourceIPASN to metadata 2024-08-28 12:25:45 +08:00
wwqgtxx
8483178524 feat: sniff add skip-src-address and skip-dst-address 2024-08-27 20:33:43 +08:00
wwqgtxx
3e2c9ce821 chore: cleanup patch code 2024-08-27 11:04:42 +08:00
wwqgtxx
6e04e1e9dc fix: hysteria2 close safety 2024-08-27 08:41:57 +08:00
wwqgtxx
d79423a4fa fix: tun should not care "force" when Put configs from restful api 2024-08-26 20:54:32 +08:00
wwqgtxx
9cf3eb39f5 fix: hysteria1 outbound should be closed when proxy removed 2024-08-26 18:47:54 +08:00
wwqgtxx
81756fc927 fix: wireguard outbound memory leaks when close 2024-08-26 14:28:31 +08:00
wwqgtxx
518e9bdb0b feat: socks5, http and mixed listeners support independence users 2024-08-25 19:33:31 +08:00
wwqgtxx
27bcb26ecd chore: better config internal structure 2024-08-24 20:49:12 +08:00
karin0
53425bb9f2 chore: add json struct tags for more fields in config.RawConfig (#1469)
Applications like CMFA marshall their config overrides in JSON, so
`json` tags are required for parsing configurations of features such as
external controllers.
2024-08-24 18:10:09 +08:00
wwqgtxx
f5834dd5e2 chore: code cleanup 2024-08-24 14:20:21 +08:00
wwqgtxx
16c95fca87 fix: tradition shadowsocks server not apply additions
https://github.com/MetaCubeX/mihomo/issues/1466
2024-08-23 21:14:36 +08:00
wwqgtxx
41efc5e5ab chore: update dependencies 2024-08-22 09:24:27 +08:00
wwqgtxx
c4660e1aad chore: reopen tfo support on windows for golang1.23
maybe broken again when golang1.24 release
2024-08-21 10:57:44 +08:00
Larvan2
512d188384 fix redundant WindowsDNS (#1456)
Co-authored-by: ForestL <45709305+forestl18@users.noreply.github.com>
2024-08-20 21:13:26 +08:00
wwqgtxx
ba605b693b action: show go env when build 2024-08-20 09:39:44 +08:00
xishang0128
c8380335cb chore: improve include-all-proxies compatibility 2024-08-19 16:02:43 +08:00
wwqgtxx
82984891e4 action: fix build for armv5/6/7 2024-08-19 10:01:30 +08:00
Larvan2
0793998de8 chore: drop support of eBPF 2024-08-16 14:15:36 +08:00
wwqgtxx
6bf419c5fe chore: better geo init logging 2024-08-16 12:04:37 +08:00
wwqgtxx
4fedfc47b0 chore: update geo unneeded reload whole config 2024-08-16 09:19:18 +08:00
wwqgtxx
92ec5f2236 chore: cleanup dns policy match code 2024-08-15 20:08:14 +08:00
wwqgtxx
4c10d42fbf fix: normal rule not working in fake-ip-filter 2024-08-15 07:42:59 +08:00
wwqgtxx
7fd0467aef feat: sniffer's force-domain and skip-domain support rule-set: and geosite: 2024-08-14 23:29:53 +08:00
wwqgtxx
696b75ee37 feat: fake-ip-filter support rule-set: and geosite: 2024-08-14 20:45:36 +08:00
wwqgtxx
f20f371a61 chore: better keepalive handle 2024-08-14 13:01:06 +08:00
Larvan2
24c6e7d819 chore: update tcp keepAlive setting for go1.23 2024-08-14 11:51:39 +08:00
wwqgtxx
acaacd8ab1 action: let golang1.23's build can work on windows7/8 2024-08-14 10:13:57 +08:00
wwqgtxx
12c5cf361d chore: update golang to 1.23 2024-08-14 09:03:42 +08:00
Larvan2
50d0cd363c chore: auto download external UI when 'external-ui' is set and not empty 2024-08-13 14:19:34 +08:00
wwqgtxx
5bf22422d9 fix: wireguard not working in CMFA 2024-08-13 13:36:25 +08:00
xishang0128
c17d7c0281 ci: update loongarch golang and android ndk 2024-08-13 00:57:08 +08:00
wwqgtxx
951cae2156 chore: corrected the incoming parameters of PowerUnregisterSuspendResumeNotification 2024-08-08 13:27:06 +08:00
wwqgtxx
4f339265d3 chore: update dependencies 2024-08-08 07:29:56 +08:00
wwqgtxx
ea4181308d chore: update quic-go to 0.46.0 2024-08-08 07:25:16 +08:00
Chris Gardner
030631607e fix: parameter order in ChaCha20 constructor 2024-08-07 15:54:16 +08:00
wwqgtxx
beefe37260 chore: logic rules dynamic obtain parameters 2024-08-06 17:17:48 +08:00
xishang0128
5a73d99c6f fix: logic rules display error 2024-08-06 16:30:44 +08:00
wwqgtxx
e4646fc3d2 chore: update dependencies 2024-08-03 09:51:13 +08:00
wwqgtxx
bb554e89d9 action: add GOTOOLCHAIN=local to env 2024-08-03 09:12:10 +08:00
wwqgtxx
fd205bfa8d chore: update quic-go to 0.45.2 2024-08-03 08:42:36 +08:00
OxO
e7e1400126 chore: reduce image size 2024-07-31 13:04:30 +08:00
wwqgtxx
117cdd8b54 chore: remove suppress_prefixlength in tun linux auto-route for inet4/6-route-address
https://github.com/MetaCubeX/mihomo/issues/1368
2024-07-29 21:14:59 +08:00
wwqgtxx
c830b8aaf7 feat: support convert mrs format back to text format 2024-07-28 11:00:27 +08:00
wwqgtxx
1db3e4583b chore: better converter 2024-07-27 23:54:28 +08:00
wwqgtxx
4f8a5a5f54 feat: add mrs format ipcidr ruleset 2024-07-27 10:36:11 +08:00
wwqgtxx
303f6e4567 feat: add mrs format domain ruleset 2024-07-26 22:30:42 +08:00
wwqgtxx
0d90a93645 chore: sort proxies and providers by name before include all 2024-07-26 10:59:39 +08:00
wwqgtxx
40c9829328 fix: auth with CFMA compile issue 2024-07-26 10:55:03 +08:00
wwqgtxx
4051ea522a chore: improve authentication parsing logic in http listener (#1336) 2024-07-25 19:49:56 +08:00
wwqgtxx
cc7823dad8 fix: remove unneeded http proxy compression 2024-07-24 14:56:46 +08:00
wwqgtxx
d6a1af23a7 feat: local file type provider will auto update after modify 2024-07-24 14:37:10 +08:00
wwqgtxx
313493cc94 chore: add fswatch 2024-07-24 14:37:10 +08:00
wwqgtxx
4b9fdacbad feat: doh client support plain http and skip-cert-verify 2024-07-23 10:48:54 +08:00
wwqgtxx
13b7ab8da3 fix: better doh server compatibility 2024-07-23 08:46:27 +08:00
wwqgtxx
de61e81ff7 feat: support external-doh-server 2024-07-23 00:01:41 +08:00
wwqgtxx
4eb13a73bf fix: wrong usage of RLock 2024-07-22 09:57:57 +08:00
wwqgtxx
fd5b537ab1 fix: doh concurrent race issue 2024-07-21 23:03:04 +08:00
ruokeqx
28794c62c4 chore: reduce func findProcessName mem allocs and copy (#1393) 2024-07-19 22:24:27 +08:00
wwqgtxx
e263518f01 fix: some auto-redirect issue 2024-07-19 22:20:07 +08:00
wwqgtxx
345061a7cc chore: support some chacha8 method 2024-07-19 22:08:05 +08:00
wwqgtxx
a05016a5da chore: better dns logging 2024-07-19 19:27:29 +08:00
xishang0128
9e3589d638 chore: include-all-providers logic correction 2024-07-15 13:12:40 +08:00
xishang0128
fc03bd2f0d chore: Modify the default value to avoid outputting Deprecated 2024-07-12 02:59:14 +08:00
xishang0128
0e228765fc fix: Make the ruleset take effect in a single line 2024-06-28 14:14:36 +08:00
wwqgtxx
f45ccc0761 chore: update dependencies 2024-06-27 09:52:47 +08:00
xishang0128
9f4cd646c2 fix: dhcp:// with special notation cannot be parsed 2024-06-23 15:33:38 +08:00
wwqgtxx
a9ecc627e6 fix: subrule can't recursion correctly (#1339) 2024-06-22 13:18:23 +08:00
wwqgtxx
50286678bf fix: auto-redirect rule error 2024-06-22 13:08:15 +08:00
wwqgtxx
917c5fdd80 fix: auto-redirect android rules 2024-06-19 12:11:00 +08:00
wwqgtxx
1457f83530 fix: dns server using direct outbound lookback resolve problem 2024-06-18 13:15:20 +08:00
wwqgtxx
5ab8154e7e fix: wireguard ip update 2024-06-18 10:30:43 +08:00
wwqgtxx
09be5cbc99 feat: tun support auto-redirect, route-address-set and route-exclude-address-set 2024-06-17 22:04:51 +08:00
xishang0128
0738e18100 chore: add override fields 2024-06-16 18:19:04 +08:00
wwqgtxx
40f40f6d24 fix: dns dial to wrong target 2024-06-15 13:32:57 +08:00
wwqgtxx
ad5bc51c77 chore: deprecated the relay group type, please using dialer-proxy instead 2024-06-15 13:32:57 +08:00
wwqgtxx
75c16f9b87 feat: add refresh-server-ip-interval for wireguard outbound 2024-06-14 14:01:52 +08:00
wwqgtxx
d96d7651ca chore: add inner dns proxied connection log 2024-06-13 09:07:05 +08:00
wwqgtxx
a5f25a2246 chore: code split 2024-06-12 20:54:43 +08:00
wwqgtxx
2b4741fbc7 chore: add inner dns proxied connection statistic to restful api 2024-06-12 17:10:47 +08:00
wwqgtxx
f317baa8de feat: add respect-rules for dns 2024-06-12 15:25:34 +08:00
wwqgtxx
5678131591 fix: wireguard server resolve when only a server in peers 2024-06-12 11:37:23 +08:00
xishang0128
10f8ba4434 chore: Disable the loop back detector for CMFA 2024-06-12 04:46:13 +08:00
wwqgtxx
cacfefad4b fix: quic-go cached dial error 2024-06-10 08:48:23 +08:00
wwqgtxx
0d4e57cb21 chore: update quic-go to 0.45.0 2024-06-07 21:41:49 +08:00
wwqgtxx
063836fe5d chore: sync hysteria2 bbr changes
e0e75c4630
2024-06-05 11:56:27 +08:00
xishang0128
7b3c9e94e6 chore: Better package name handling on Android 2024-06-02 02:36:15 +08:00
wwqgtxx
be3d121ec6 fix: darwin calculate correct tunIndex 2024-06-01 13:36:57 +08:00
xishang0128
59fd3cffe3 ci: fix arm package create 2024-05-31 17:02:31 +08:00
wwqgtxx
39eda257a7 chore: replace zhangyunhao116/fastrand to our metacubex/randv2 2024-05-31 13:07:48 +08:00
wwqgtxx
d3fea909e9 chore: remove tfo windows support
Golang officially decided not to open `internal/poll.execIO` to third-party libraries after 1.23 was released, so we can only choose to remove tfo support on the Windows platform.
2024-05-30 10:39:17 +08:00
wwqgtxx
7eb70aeb4d fix: windows build number 2024-05-29 00:08:32 +08:00
xishang0128
846bdfa812 chore: Allow customization of GLOBAL 2024-05-25 08:09:59 +08:00
wwqgtxx
0b6ae6ffb8 feat: add ss-opts for trojan outbound like trojan-go's shadowsocks config
https://github.com/MetaCubeX/mihomo/issues/1269
2024-05-22 09:00:59 +08:00
wwqgtxx
71922dd0b1 fix: bad usage for exec in sing-tun
https://github.com/MetaCubeX/mihomo/issues/1234
2024-05-21 23:53:13 +08:00
moexiami
5eb8958ff2 fix: correct type for vmess.ws-opts.path in ConvertsV2Ray (#1145)
It should be a string for the following reasons:
1. During conversion, it is conditionally assigned with
   `wsOpts["path"] = path.(string)`
2. After conversion, it is decoded into `WSOptions.Path` in
   `adapter/outbound/vmess.go` which requires a string.
2024-05-21 23:31:28 +08:00
5aaee9
ac2506154f fix: possibly using released buffer in tproxy (#1286) 2024-05-21 23:25:28 +08:00
wwqgtxx
43bdc76f87 fix: darwin calculate correct tunIndex
https://github.com/MetaCubeX/mihomo/pull/1285
2024-05-21 19:13:44 +08:00
wwqgtxx
3195c678c7 chore: update quic-go to 0.44.0 2024-05-21 08:45:36 +08:00
Larvan2
bd43eca09d ci:docker tags 2024-05-20 19:47:25 +08:00
xishang0128
c504985b99 chore: Adjust sniff logs 2024-05-19 19:35:12 +08:00
Larvan2
4243a74284 chore: auto update geo 2024-05-19 18:51:06 +08:00
Larvan2
e749c7e492 ci: docker 2024-05-19 16:41:05 +08:00
Larvan2
c3ee921d30 chore: apply config when geo update 2024-05-19 15:46:23 +08:00
wwqgtxx
df69a31e62 chore: stop using go:linkname for crypto/tls.aesgcmPreferred and update utls to 1.6.6 2024-05-19 11:32:22 +08:00
Larvan2
b7c02a5923 ci: fix docker 2024-05-18 23:09:21 +08:00
wwqgtxx
bfb6caeeaf chore: stop using go:linkname for x/sys/windows 2024-05-18 20:54:28 +08:00
wwqgtxx
30a913aad6 chore: stop using go:linkname for net.lookupStaticHost 2024-05-18 20:45:15 +08:00
hunshcn
00e361c5ac chore: stop using go:linkname for http.registerOnHitEOF, http.requestBodyRemains (#1275)
relate to https://github.com/MetaCubeX/mihomo/pull/952#issuecomment-2118639385
2024-05-18 20:16:53 +08:00
Larvan2
56edd8f671 ci: better release 2024-05-18 18:36:00 +08:00
wwqgtxx
2b52809d2c chore: update quic-go to 0.43.1 2024-05-18 11:47:45 +08:00
Larvan2
5c3a9b1dfc fix: geo auto update #1261 2024-05-17 11:51:32 +08:00
xishang0128
fe88f0e437 chore: Ensure that some expressions take effect 2024-05-15 15:38:55 +08:00
wwqgtxx
87877d1b80 fix: don't ignore http.NewRequest's error 2024-05-15 13:53:18 +08:00
wwqgtxx
1bc3c16b59 feat: add PROCESS-NAME-REGEX and PROCESS-PATH-REGEX 2024-05-15 10:44:56 +08:00
wwqgtxx
ed1e7e32c7 action: revert more golang1.22 commit for win7 2024-05-15 09:14:34 +08:00
xishang0128
5da9ccaa98 action: Upgrade loongarch golang version 2024-05-15 08:32:57 +08:00
xishang0128
fd7ecc004f chore: Add filter for include-all-proxies 2024-05-13 20:30:31 +08:00
wwqgtxx
3ae4014b39 chore: disable tfo when lower than Windows 10.0.14393 2024-05-12 20:44:12 +08:00
wwqgtxx
a50339bd5f chore: swtich RtlGetNtVersionNumbers to RtlGetVersion
https://go-review.googlesource.com/c/go/+/571015
2024-05-12 20:23:13 +08:00
wwqgtxx
7df1c26942 fix: fingerprint passing 2024-05-12 19:34:25 +08:00
wwqgtxx
fc82a32a48 fix: system tun stack not working in win7 2024-05-12 15:52:10 +08:00
wwqgtxx
adf0ff588f action: let golang1.22's build can work on windows7/8 2024-05-12 13:32:07 +08:00
wwqgtxx
b840eae4c6 fix: x509 error in windows7/8 2024-05-12 12:36:48 +08:00
wwqgtxx
619f34119e action: add golang1.21 with special revert commit to work on Windows7 2024-05-12 00:10:51 +08:00
wwqgtxx
6d1c62bbf0 fix: shadowsocks uot not work with dialer-proxy 2024-05-08 09:27:31 +08:00
xishang0128
5dd883e790 chore: Add use-system-hosts option 2024-05-06 14:03:29 +08:00
Larvan2
a2b43faa0b Update README.md 2024-05-04 18:41:08 +08:00
wwqgtxx
8861eaf903 chore: hysteria2 will only change remote port in hopLoop 2024-05-01 09:41:22 +08:00
Pylogmon
107e3e7630 feat: Allow upgrade to latest release (#1235) 2024-04-30 17:01:46 +08:00
wwqgtxx
314c0bb34b fix: hy2 udp incompatible with quic-go 0.43.0 2024-04-29 12:14:11 +08:00
wwqgtxx
89a097faa8 chore: update quic-go to 0.43.0 2024-04-28 13:24:33 +08:00
H1JK
df01582996 fix: HTTP proxy variable shadowing 2024-04-27 01:05:34 +08:00
xishang0128
8ff56b5bb8 chore: Add InUser for http/socks/mixed 2024-04-25 11:48:53 +08:00
wwqgtxx
2f8f139f7c fix: wireguard can't be auto closed 2024-04-24 11:07:22 +08:00
wwqgtxx
b2280c85b7 chore: update dependencies 2024-04-23 14:40:21 +08:00
xishang0128
002b8af94a Chore: Let CA read following homeDir 2024-04-20 22:22:17 +08:00
xishang0128
99b274acbc chore: Make unix socket follow homeDir 2024-04-19 17:33:00 +08:00
xishang0128
d4ececae20 chore: Update workflow 2024-04-18 18:02:12 +08:00
xishang0128
ff2071c1da chore: Update systemd service file 2024-04-17 17:55:13 +08:00
xishang0128
189b7b9c5f chore: Working with unix directory 2024-04-17 11:12:29 +08:00
wwqgtxx
a878254662 feat: support -ext-ctl-unix cmdline to override external controller unix address 2024-04-17 10:08:54 +08:00
wwqgtxx
3566542d0e doc: cleanup 2024-04-17 09:39:30 +08:00
wwqgtxx
ca84ab1a94 feat: support external-controller-unix 2024-04-17 09:39:00 +08:00
wwqgtxx
d84f88b50f fix: system:// should ignore dns server setting by tun listener 2024-04-13 08:02:43 +08:00
xishang0128
e3b69b8ae2 chore: Make SubScriptioninfo query also follow Proxy 2024-04-12 04:58:07 +08:00
wwqgtxx
91a7ffaad2 fix: udp loopback detector not working 2024-04-11 09:24:53 +08:00
wwqgtxx
16fadd2441 fix: don't ignore bind6 error when udp's target is ipv6 address 2024-04-10 15:34:28 +08:00
wwqgtxx
cff7df164f fix: hy2 packetId race 2024-04-09 14:52:22 +08:00
wwqgtxx
de38fa882c fix: dns outbound udp timeout 2024-04-09 14:26:14 +08:00
wwqgtxx
bd703b8ff2 fix: truncate dns message in udp response
https://github.com/MetaCubeX/mihomo/issues/1168
2024-04-09 14:16:09 +08:00
wwqgtxx
72df27be44 chore: update dependencies 2024-04-08 10:15:29 +08:00
xishang0128
f3e23b1128 feat: Allow providers to set individual proxy and headers 2024-04-08 01:27:17 +08:00
Larvan2
19f7220c0b chore: adjust testURL priority 2024-04-06 11:49:46 +08:00
wwqgtxx
3249572dc1 action: typo 2024-04-05 10:23:15 +08:00
wwqgtxx
ba09139bd7 fix: avoid netlink dos networkUpdateMonitor 2024-04-05 10:18:05 +08:00
riolurs
90bf158e9f fix(group.parser): set default TestURL if empty 2024-04-05 01:51:18 +08:00
wwqgtxx
d1539e6c07 fix: IN-PORT not work in tproxy tcp inbound
https://github.com/MetaCubeX/mihomo/issues/1162
2024-04-04 23:54:25 +08:00
wwqgtxx
c893e3c462 fix: IN-PORT not work in http inbound
https://github.com/MetaCubeX/mihomo/issues/1162
2024-04-04 21:25:27 +08:00
wwqgtxx
b56e73a02a fix: close mixed stack panic #1014 2024-04-03 23:41:04 +08:00
wwqgtxx
40f5c5b987 chore: don't retry when rejected loopback connection 2024-04-03 08:42:15 +08:00
wwqgtxx
d48517b29d fix: timer usage for monitor check update 2024-04-02 23:01:04 +08:00
xishang0128
3b472f786e chore: Add source matching for ip type rules 2024-04-01 18:16:34 +08:00
xishang0128
f3743fc7f9 chore: Introducing Punycode conversion for domain matching 2024-04-01 16:21:34 +08:00
wwqgtxx
56ed9019a6 ci: add armv5/6 back 2024-03-30 23:06:02 +08:00
H1JK
3e0bd65135 feat: Converter support Xray HTTPUpgrade fast open path 2024-03-30 18:19:40 +08:00
wwqgtxx
72d0948224 fix: atomic.TypedValue panic 2024-03-29 14:43:42 +08:00
wwqgtxx
4542fc0991 fix: tun lookback when don't have an activated network 2024-03-29 14:33:05 +08:00
xishang0128
914bc8a3e9 fix: Fix DOMAIN-REGEX parsing 2024-03-29 13:43:11 +08:00
wwqgtxx
11f0983e5c fix: resolve by ProxyServerHostResolver should not retry with DefaultResolver when error occurs 2024-03-28 23:33:56 +08:00
wwqgtxx
89d7b8138a chore: turned off ECN by default 2024-03-28 23:19:42 +08:00
wwqgtxx
eae1f05e88 fix: wireguard multi peers public key parse 2024-03-28 21:57:48 +08:00
wwqgtxx
367a287153 chore: don't lookup process when Type==INNER 2024-03-28 21:49:44 +08:00
wwqgtxx
06b5121d9e chore: embed ca-certificates.crt 2024-03-28 19:26:57 +08:00
Larvan2
82517e6ba8 chore: include short commit ID in release note 2024-03-27 19:34:11 +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
273 changed files with 8258 additions and 9976 deletions

View File

@@ -18,15 +18,15 @@ if [ -z "$version_range" ]; then
fi
echo "## What's Changed" > release.md
git log --pretty=format:"* %s by @%an" --grep="^feat" -i $version_range | sort -f | uniq >> release.md
git log --pretty=format:"* %h %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
git log --pretty=format:"* %h %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
git log --pretty=format:"* %h %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
echo "**Full Changelog**: https://github.com/MetaCubeX/mihomo/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 CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
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

@@ -1,6 +1,10 @@
name: Build
on:
workflow_dispatch:
inputs:
version:
description: "Tag version to release"
required: true
push:
paths-ignore:
- "docs/**"
@@ -13,358 +17,472 @@ on:
pull_request_target:
branches:
- Alpha
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
env:
REGISTRY: docker.io
jobs:
Build:
permissions: write-all
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- {
type: "WithoutCGO",
target: "linux-amd64 linux-amd64-compatible",
id: "1",
}
- {
type: "WithoutCGO",
target: "linux-armv5 linux-armv6 linux-armv7",
id: "2",
}
- {
type: "WithoutCGO",
target: "linux-arm64 linux-mips64 linux-mips64le",
id: "3",
}
- {
type: "WithoutCGO",
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
id: "4",
}
- { type: "WithoutCGO", target: "linux-386 linux-riscv64 linux-loong64", id: "5" }
- {
type: "WithoutCGO",
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
id: "6",
}
- {
type: "WithoutCGO",
target: "windows-amd64-compatible windows-amd64 windows-386",
id: "7",
}
- {
type: "WithoutCGO",
target: "windows-arm64 windows-arm32v7",
id: "8",
}
- {
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" }
jobs:
- { goos: darwin, goarch: arm64, output: arm64 }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: linux, goarch: '386', output: '386' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: linux, goarch: arm64, output: arm64 }
- { goos: linux, goarch: arm, goarm: '5', output: armv5 }
- { goos: linux, goarch: arm, goarm: '6', output: armv6 }
- { goos: linux, goarch: arm, goarm: '7', output: armv7 }
- { goos: linux, goarch: mips, gomips: hardfloat, output: mips-hardfloat }
- { goos: linux, goarch: mips, gomips: softfloat, output: mips-softfloat }
- { goos: linux, goarch: mipsle, gomips: hardfloat, output: mipsle-hardfloat }
- { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat }
- { goos: linux, goarch: mips64, output: mips64 }
- { goos: linux, goarch: mips64le, output: mips64le }
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' }
- { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2' }
- { goos: linux, goarch: riscv64, output: riscv64 }
- { goos: linux, goarch: s390x, output: s390x }
- { goos: windows, goarch: '386', output: '386' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: windows, goarch: arm, goarm: '7', output: armv7 }
- { goos: windows, goarch: arm64, output: arm64 }
- { goos: freebsd, goarch: '386', output: '386' }
- { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-compatible }
- { goos: freebsd, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: freebsd, goarch: arm64, output: arm64 }
- { goos: android, goarch: '386', ndk: i686-linux-android34, output: '386' }
- { goos: android, goarch: amd64, ndk: x86_64-linux-android34, output: amd64 }
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
# Go 1.22 with special patch can work on Windows 7
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go122, goversion: '1.22' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go122, goversion: '1.22' }
# Go 1.21 can revert commit `9e4385` to work on Windows 7
# https://github.com/golang/go/issues/64622#issuecomment-1847475161
# (OR we can just use golang1.21.4 which unneeded any patch)
- { goos: windows, goarch: '386', output: '386-go121', goversion: '1.21' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go121, goversion: '1.21' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go121, goversion: '1.21' }
# 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.22 is the last release that will run on macOS 10.15 Catalina. Go 1.23 will require macOS 11 Big Sur or later.
- { goos: darwin, goarch: arm64, output: arm64-go122, goversion: '1.22' }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go122, goversion: '1.22' }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go122, goversion: '1.22' }
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
- { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" }
# - { type: "WithCGO", target: "windows/*", id: "1" }
# - { type: "WithCGO", target: "linux/386", id: "2" }
# - { type: "WithCGO", target: "linux/amd64", id: "3" }
# - { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" }
# - { type: "WithCGO", target: "linux/arm,", id: "5" }
# - { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" }
# - { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" }
# - { 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" }
- { goos: darwin, goarch: arm64, output: arm64-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
# only for test
- { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20', test: test }
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set variables
run: echo "VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Set variables
if: ${{github.ref_name=='Alpha'}}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.jobs.goversion }}
- name: Set variables
if: ${{github.ref_name=='Beta'}}
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go1.22 loongarch abi1
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi1.tar.gz
sudo tar zxf go1.22.4.linux-amd64-abi1.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set variables
if: ${{github.ref_name=='Meta'}}
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set up Go1.22 loongarch abi2
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi2.tar.gz
sudo tar zxf go1.22.4.linux-amd64-abi2.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set variables
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x
# that means after golang1.24 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.23 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
run: |
cd $(go env GOROOT)
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
- name: Set ENV
run: |
sudo timedatectl set-timezone "Asia/Shanghai"
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.22.x
# that means after golang1.23 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.22 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
run: |
cd $(go env GOROOT)
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
- name: Set ENV
run: |
echo "TAGS=with_gvisor,with_lwip" >> $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 "GOTOOLCHAIN=local" >> $GITHUB_ENV
shell: bash
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
- name: Revert Golang1.21 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }}
run: |
cd $(go env GOROOT)
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
- name: Setup Go
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
uses: actions/setup-go@v4
with:
go-version: "1.21"
check-latest: true
- name: Set variables
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
shell: bash
- name: Setup Go
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
uses: actions/setup-go@v4
with:
go-version: "1.20"
check-latest: true
- name: Set variables
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_name == 'Alpha' }}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
run: |
go test ./...
- name: Set Time Variable
run: |
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "CGO_ENABLED=0" >> $GITHUB_ENV
echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV
echo "GOTOOLCHAIN=local" >> $GITHUB_ENV
- name: Build WithoutCGO
if: ${{ matrix.job.type!='WithCGO' }}
env:
NAME: mihomo
BINDIR: bin
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }}
- name: Setup NDK
if: ${{ matrix.jobs.goos == 'android' }}
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r27
- uses: nttld/setup-ndk@v1
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
id: setup-ndk
with:
ndk-version: r26b
add-to-path: true
- name: Set NDK path
if: ${{ matrix.jobs.goos == 'android' }}
run: |
echo "CC=${{steps.setup-ndk.outputs.ndk-path}}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{matrix.jobs.ndk}}-clang" >> $GITHUB_ENV
echo "CGO_ENABLED=1" >> $GITHUB_ENV
echo "BUILDTAG=" >> $GITHUB_ENV
- name: Build Android
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
env:
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: Test
if: ${{ matrix.jobs.test == 'test' }}
run: |
go test ./...
echo "---test with_gvisor---"
go test ./... -tags "with_gvisor" -count=1
- name: Set up xgo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
run: |
docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest
- name: Update CA
run: |
sudo apt-get install ca-certificates
sudo update-ca-certificates
cp -f /etc/ssl/certs/ca-certificates.crt component/ca/ca-certificates.crt
- name: Build by xgo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
mkdir bin
xgo --targets="${{ matrix.job.target }}" --tags="${TAGS}" -ldflags="${LDFLAGS}" --out bin/${NAME} ./
- name: Build core
env:
GOOS: ${{matrix.jobs.goos}}
GOARCH: ${{matrix.jobs.goarch}}
GOAMD64: ${{matrix.jobs.goamd64}}
GOARM: ${{matrix.jobs.goarm}}
GOMIPS: ${{matrix.jobs.gomips}}
run: |
go env
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
- name: Rename
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: Create DEB package
if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }}
run: |
sudo apt-get install dpkg
if [ "${{matrix.jobs.abi}}" = "1" ]; then
ARCH=loongarch64
elif [ "${{matrix.jobs.goarm}}" = "7" ]; then
ARCH=armhf
elif [ "${{matrix.jobs.goarch}}" = "arm" ]; then
ARCH=armel
else
ARCH=${{matrix.jobs.goarch}}
fi
PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' )
if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then
PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}"
fi
- name: Rename
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
run: |
cd bin
ls -la
cp ../.github/rename-go120.sh ./
bash ./rename-go120.sh
rm ./rename-go120.sh
ls -la
cd ..
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: Zip
if: ${{ success() }}
run: |
cd bin
ls -la
chmod +x *
cp ../.github/release.sh ./
bash ./release.sh
rm ./release.sh
ls -la
cd ..
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/mihomo
cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
cp .github/mihomo.service mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
- name: Save version
run: echo ${VERSION} > bin/version.txt
shell: bash
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
mixed-port: 7890
external-controller: 127.0.0.1:9090
EOF
- uses: actions/upload-artifact@v3
if: ${{ success() }}
with:
name: artifact
path: bin/
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
Package: mihomo
Version: ${PackageVersion}
Section:
Priority: extra
Architecture: ${ARCH}
Maintainer: MetaCubeX <none@example.com>
Homepage: https://wiki.metacubex.one/
Description: The universal proxy platform.
EOF
dpkg-deb -Z gzip --build mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}
- name: Convert DEB to RPM
if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }}
run: |
sudo apt-get install -y alien
alien --to-rpm --scripts mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb
mv mihomo*.rpm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.rpm
# - name: Convert DEB to PKG
# if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') && !contains(matrix.jobs.goarch, 'loong64') }}
# run: |
# docker pull archlinux
# docker run --rm -v ./:/mnt archlinux bash -c "
# pacman -Syu pkgfile base-devel --noconfirm
# curl -L https://github.com/helixarch/debtap/raw/master/debtap > /usr/bin/debtap
# chmod 755 /usr/bin/debtap
# debtap -u
# debtap -Q /mnt/mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb
# "
# mv mihomo*.pkg.tar.zst mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.pkg.tar.zst
- name: Save version
run: |
echo ${VERSION} > version.txt
shell: bash
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: "${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}"
path: |
mihomo*.gz
mihomo*.deb
mihomo*.rpm
mihomo*.zip
version.txt
Upload-Prerelease:
permissions: write-all
if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
needs: [Build]
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Download all workflow run artifacts
uses: actions/download-artifact@v4
with:
path: bin/
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Delete current release assets
uses: 8Mi-Tech/delete-release-assets-action@main
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
uses: 8Mi-Tech/delete-release-assets-action@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
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
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Tag Repo
uses: richardsimko/update-tag@v1.0.6
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
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: 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
- 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:
permissions: write-all
if: ${{ github.ref_type=='tag' }}
needs: [Build]
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: Meta
fetch-depth: '0'
fetch-tags: 'true'
- name: Get tags
run: |
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
echo "CURRENTVERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
- name: Force push Alpha branch to Meta
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git fetch origin Alpha:Alpha
git push origin Alpha:Meta --force
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Tag the commit on Alpha
run: |
git checkout Alpha
git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate release notes
run: |
cp ./.github/genReleaseNote.sh ./
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
rm ./genReleaseNote.sh
- uses: actions/download-artifact@v3
cp ./.github/genReleaseNote.sh ./
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
rm ./genReleaseNote.sh
- uses: actions/download-artifact@v4
with:
name: artifact
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() }}
uses: softprops/action-gh-release@v2
if: ${{ success() }}
with:
tag_name: ${{ github.ref_name }}
tag_name: ${{ github.event.inputs.version }}
files: bin/*
generate_release_notes: true
body_path: release.md
Docker:
if: ${{ !startsWith(github.event_name, 'pull_request') }}
permissions: write-all
needs: [Build]
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: artifact
path: bin/
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
with:
version: latest
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4
if: ${{ github.event_name != 'workflow_dispatch' }}
id: meta_alpha
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}
images: '${{ env.REGISTRY }}/${{ github.repository }}'
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
id: meta_release
uses: docker/metadata-action@v5
with:
images: '${{ env.REGISTRY }}/${{ github.repository }}'
tags: |
${{ github.event.inputs.version }}
flavor: |
latest=true
labels: org.opencontainers.image.version=${{ github.event.inputs.version }}
- name: Show files
run: |
ls .
ls bin/
- name: login to docker REGISTRY
uses: docker/login-action@v3
with:
@@ -375,7 +493,7 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: docker/build-push-action@v5
with:
context: .
@@ -386,5 +504,20 @@ jobs:
linux/amd64
linux/arm64
linux/arm/v7
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta_alpha.outputs.tags }}
labels: ${{ steps.meta_alpha.outputs.labels }}
- name: Build and push Docker image
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64
linux/arm/v7
tags: ${{ steps.meta_release.outputs.tags }}
labels: ${{ steps.meta_release.outputs.labels }}

View File

@@ -12,8 +12,8 @@ COPY docker/file-name.sh /mihomo/file-name.sh
WORKDIR /mihomo
COPY bin/ bin/
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && chmod +x mihomo && echo "$FILE_NAME" > /mihomo-config/test
FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo"
@@ -23,5 +23,4 @@ VOLUME ["/root/.config/mihomo/"]
COPY --from=builder /mihomo-config/ /root/.config/mihomo/
COPY --from=builder /mihomo/mihomo /mihomo
RUN chmod +x /mihomo
ENTRYPOINT [ "/mihomo" ]

View File

@@ -17,6 +17,7 @@ GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.
-w -s -buildid='
PLATFORM_LIST = \
darwin-amd64-compatible \
darwin-amd64 \
darwin-arm64 \
linux-amd64-compatible \
@@ -162,7 +163,3 @@ clean:
CLANG ?= clang-14
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
ebpf: export BPF_CLANG := $(CLANG)
ebpf: export BPF_CFLAGS := $(CFLAGS)
ebpf:
cd component/ebpf/ && go generate ./...

View File

@@ -98,4 +98,3 @@ API.
This software is released under the GPL-3.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo?ref=badge_large)

View File

@@ -2,6 +2,7 @@ package adapter
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
@@ -14,6 +15,7 @@ import (
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/queue"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant"
"github.com/puzpuzpuz/xsync/v3"
@@ -230,6 +232,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
}
client := http.Client{

View File

@@ -47,7 +47,7 @@ func WithDstAddr(addr net.Addr) Addition {
func WithSrcAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
if err := m.SetRemoteAddr(addr); err == nil {
metadata.SrcIP = m.DstIP
metadata.SrcPort = m.DstPort
}
@@ -57,7 +57,7 @@ func WithSrcAddr(addr net.Addr) Addition {
func WithInAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
if err := m.SetRemoteAddr(addr); err == nil {
metadata.InIP = m.DstIP
metadata.InPort = m.DstPort
}
@@ -69,3 +69,5 @@ func WithDSCP(dscp uint8) Addition {
metadata.DSCP = dscp
}
}
func Placeholder(metadata *C.Metadata) {}

View File

@@ -14,7 +14,7 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A
metadata.Type = C.HTTP
metadata.RawSrcAddr = srcConn.RemoteAddr()
metadata.RawDstAddr = srcConn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"net"
"github.com/sagernet/tfo-go"
"github.com/metacubex/tfo-go"
)
var (
@@ -17,10 +17,18 @@ func SetTfo(open bool) {
lc.DisableTFO = !open
}
func Tfo() bool {
return !lc.DisableTFO
}
func SetMPTCP(open bool) {
setMultiPathTCP(&lc.ListenConfig, open)
}
func MPTCP() bool {
return getMultiPathTCP(&lc.ListenConfig)
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
return lc.Listen(ctx, network, address)
}

View File

@@ -8,3 +8,7 @@ const multipathTCPAvailable = false
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
}
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
return false
}

View File

@@ -9,3 +9,7 @@ const multipathTCPAvailable = true
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
listenConfig.SetMultipathTCP(open)
}
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
return listenConfig.MultipathTCP()
}

View File

@@ -3,18 +3,22 @@ package outbound
import (
"context"
"errors"
"fmt"
"net/netip"
"os"
"strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
)
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
type Direct struct {
*Base
loopBack *loopBackDetector
loopBack *loopback.Detector
}
type DirectOption struct {
@@ -24,8 +28,10 @@ type DirectOption struct {
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if d.loopBack.CheckConn(metadata.SourceAddrPort()) {
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress())
if !features.CMFA && !DisableLoopBackDetector {
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
}
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
@@ -38,8 +44,10 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) {
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress())
if !features.CMFA && !DisableLoopBackDetector {
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err
}
}
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() {
@@ -49,13 +57,17 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
}
metadata.DstIP = ip
}
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
if err != nil {
return nil, err
}
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil
}
func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool {
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
}
func NewDirectWithOption(option DirectOption) *Direct {
return &Direct{
Base: &Base{
@@ -68,7 +80,7 @@ func NewDirectWithOption(option DirectOption) *Direct {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
loopBack: newLoopBackDetector(),
loopBack: loopback.NewDetector(),
}
}
@@ -80,7 +92,7 @@ func NewDirect() *Direct {
udp: true,
prefer: C.DualStack,
},
loopBack: newLoopBackDetector(),
loopBack: loopback.NewDetector(),
}
}
@@ -92,6 +104,6 @@ func NewCompatible() *Direct {
udp: true,
prefer: C.DualStack,
},
loopBack: newLoopBackDetector(),
loopBack: loopback.NewDetector(),
}
}

View File

@@ -1,68 +0,0 @@
package outbound
import (
"net/netip"
"github.com/metacubex/mihomo/common/callback"
C "github.com/metacubex/mihomo/constant"
"github.com/puzpuzpuz/xsync/v3"
)
type loopBackDetector struct {
connMap *xsync.MapOf[netip.AddrPort, struct{}]
packetConnMap *xsync.MapOf[netip.AddrPort, struct{}]
}
func newLoopBackDetector() *loopBackDetector {
return &loopBackDetector{
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
}
}
func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn {
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
}
connAddr := metadata.AddrPort()
if !connAddr.IsValid() {
return conn
}
l.connMap.Store(connAddr, struct{}{})
return callback.NewCloseCallbackConn(conn, func() {
l.connMap.Delete(connAddr)
})
}
func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn {
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
}
connAddr := metadata.AddrPort()
if !connAddr.IsValid() {
return conn
}
l.packetConnMap.Store(connAddr, struct{}{})
return callback.NewCloseCallbackPacketConn(conn, func() {
l.packetConnMap.Delete(connAddr)
})
}
func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool {
if !connAddr.IsValid() {
return false
}
_, ok := l.connMap.Load(connAddr)
return ok
}
func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool {
if !connAddr.IsValid() {
return false
}
_, ok := l.packetConnMap.Load(connAddr)
return ok
}

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
}
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
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
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

@@ -7,6 +7,7 @@ import (
"fmt"
"net"
"net/netip"
"runtime"
"strconv"
"time"
@@ -14,6 +15,7 @@ import (
"github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
@@ -43,6 +45,8 @@ type Hysteria struct {
option *HysteriaOption
client *core.Client
closeCh chan struct{} // for test
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
@@ -51,7 +55,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
return nil, err
}
return NewConn(tcpConn, h), nil
return NewConn(CN.NewRefConn(tcpConn, h), h), nil
}
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
@@ -59,7 +63,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
if err != nil {
return nil, err
}
return newPacketConn(&hyPacketConn{udpConn}, h), nil
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil
}
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
@@ -218,7 +222,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
}
return &Hysteria{
outbound := &Hysteria{
Base: &Base{
name: option.Name,
addr: addr,
@@ -231,7 +235,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
},
option: &option,
client: client,
}, nil
}
runtime.SetFinalizer(outbound, closeHysteria)
return outbound, nil
}
func closeHysteria(h *Hysteria) {
if h.client != nil {
_ = h.client.Close()
}
if h.closeCh != nil {
close(h.closeCh)
}
}
type hyPacketConn struct {

View File

@@ -8,16 +8,20 @@ import (
"net"
"runtime"
"strconv"
"time"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
"github.com/metacubex/sing-quic/hysteria2"
"github.com/metacubex/randv2"
M "github.com/sagernet/sing/common/metadata"
)
@@ -25,19 +29,26 @@ func init() {
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
}
const minHopInterval = 5
const defaultHopInterval = 30
type Hysteria2 struct {
*Base
option *Hysteria2Option
client *hysteria2.Client
dialer proxydialer.SingDialer
closeCh chan struct{} // for test
}
type Hysteria2Option struct {
BasicOption
Name string `proxy:"name"`
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"`
Down string `proxy:"down,omitempty"`
Password string `proxy:"password,omitempty"`
@@ -80,6 +91,9 @@ func closeHysteria2(h *Hysteria2) {
if h.client != nil {
_ = h.client.CloseWithError(errors.New("proxy removed"))
}
if h.closeCh != nil {
close(h.closeCh)
}
}
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
@@ -129,7 +143,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
clientOptions := hysteria2.ClientOptions{
Context: context.TODO(),
Dialer: singDialer,
ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
Logger: log.SingLogger,
SendBPS: StringToBps(option.Up),
ReceiveBPS: StringToBps(option.Down),
SalamanderPassword: salamanderPassword,
@@ -138,6 +152,37 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
UDPDisabled: false,
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[randv2.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)

View File

@@ -0,0 +1,38 @@
package outbound
import (
"context"
"runtime"
"testing"
"time"
)
func TestHysteria2GC(t *testing.T) {
option := Hysteria2Option{}
option.Server = "127.0.0.1"
option.Ports = "200,204,401-429,501-503"
option.HopInterval = 30
option.Password = "password"
option.Obfs = "salamander"
option.ObfsPassword = "password"
option.SNI = "example.com"
option.ALPN = []string{"h3"}
hy, err := NewHysteria2(option)
if err != nil {
t.Error(err)
return
}
closeCh := make(chan struct{})
hy.closeCh = closeCh
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
hy = nil
runtime.GC()
select {
case <-closeCh:
return
case <-ctx.Done():
t.Error("timeout not GC")
}
}

View File

@@ -0,0 +1,39 @@
package outbound
import (
"context"
"runtime"
"testing"
"time"
)
func TestHysteriaGC(t *testing.T) {
option := HysteriaOption{}
option.Server = "127.0.0.1"
option.Ports = "200,204,401-429,501-503"
option.Protocol = "udp"
option.Up = "1Mbps"
option.Down = "1Mbps"
option.HopInterval = 30
option.Obfs = "salamander"
option.SNI = "example.com"
option.ALPN = []string{"h3"}
hy, err := NewHysteria(option)
if err != nil {
t.Error(err)
return
}
closeCh := make(chan struct{})
hy.closeCh = closeCh
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
hy = nil
runtime.GC()
select {
case <-closeCh:
return
case <-ctx.Done():
t.Error("timeout not GC")
}
}

View File

@@ -166,12 +166,6 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
// ListenPacketWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
if ss.option.UDPOverTCP {
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
if err != nil {
@@ -179,6 +173,12 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
}
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
}
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
return nil, err
@@ -273,6 +273,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint
}
} else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode

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/metacubex/randv2"
"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 randv2.IntN(2) == 0 {
version += "7." + strconv.Itoa(randv2.IntN(10))
} else {
version += "8." + strconv.Itoa(randv2.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

@@ -3,6 +3,7 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
@@ -15,6 +16,7 @@ import (
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan"
)
@@ -29,6 +31,8 @@ type Trojan struct {
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
ssCipher core.Cipher
}
type TrojanOption struct {
@@ -46,9 +50,17 @@ type TrojanOption struct {
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
type TrojanSSOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Method string `proxy:"method,omitempty"`
Password string `proxy:"password,omitempty"`
}
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr)
@@ -95,6 +107,10 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err
@@ -112,6 +128,10 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close()
return nil, err
@@ -161,6 +181,11 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
@@ -193,6 +218,10 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
@@ -257,6 +286,20 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
}
tOption.Reality = t.realityConfig
if option.SSOpts.Enabled {
if option.SSOpts.Password == "" {
return nil, errors.New("empty password")
}
if option.SSOpts.Method == "" {
option.SSOpts.Method = "AES-128-GCM"
}
ciph, err := core.PickCipher(option.SSOpts.Method, nil, option.SSOpts.Password)
if err != nil {
return nil, err
}
t.ssCipher = ciph
}
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
var err error

View File

@@ -373,7 +373,7 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
}, M.SocksaddrFromNet(metadata.UDPAddr())),
), 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

View File

@@ -179,6 +179,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts := mihomoVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
@@ -208,6 +209,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
tlsOpts := &mihomoVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,

View File

@@ -12,7 +12,9 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/metacubex/mihomo/common/atomic"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
@@ -24,7 +26,6 @@ import (
wireguard "github.com/metacubex/sing-wireguard"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -37,16 +38,29 @@ type WireGuard struct {
device *device.Device
tunDevice wireguard.Device
dialer proxydialer.SingDialer
startOnce sync.Once
startErr error
resolver *dns.Resolver
refP *refProxyAdapter
initOk atomic.Bool
initMutex sync.Mutex
initErr error
option WireGuardOption
connectAddr M.Socksaddr
localPrefixes []netip.Prefix
serverAddrMap map[M.Socksaddr]netip.AddrPort
serverAddrTime atomic.TypedValue[time.Time]
serverAddrMutex sync.Mutex
closeCh chan struct{} // for test
}
type WireGuardOption struct {
BasicOption
WireGuardPeerOption
Name string `proxy:"name"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PrivateKey string `proxy:"private-key"`
Workers int `proxy:"workers,omitempty"`
MTU int `proxy:"mtu,omitempty"`
@@ -57,13 +71,13 @@ type WireGuardOption struct {
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
Dns []string `proxy:"dns,omitempty"`
RefreshServerIPInterval int `proxy:"refresh-server-ip-interval,omitempty"`
}
type WireGuardPeerOption struct {
Server string `proxy:"server"`
Port int `proxy:"port"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PublicKey string `proxy:"public-key,omitempty"`
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
Reserved []uint8 `proxy:"reserved,omitempty"`
@@ -98,7 +112,7 @@ func (option WireGuardPeerOption) Addr() M.Socksaddr {
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)
if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") {
@@ -149,125 +163,83 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
copy(reserved[:], option.Reserved)
}
var isConnect bool
var connectAddr M.Socksaddr
if len(option.Peers) < 2 {
isConnect = true
if len(option.Peers) == 1 {
connectAddr = option.Peers[0].Addr()
outbound.connectAddr = option.Peers[0].Addr()
} else {
connectAddr = option.Addr()
outbound.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, outbound.connectAddr.AddrPort(), reserved)
var localPrefixes []netip.Prefix
var err error
outbound.localPrefixes, err = option.Prefixes()
if err != nil {
return nil, err
}
var privateKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
if err != nil {
return nil, E.Cause(err, "decode private key")
}
privateKey = hex.EncodeToString(bytes)
option.PrivateKey = hex.EncodeToString(bytes)
}
ipcConf := "private_key=" + privateKey
if peersLen := len(option.Peers); peersLen > 0 {
localPrefixes = make([]netip.Prefix, 0, peersLen*2)
for i, peer := range option.Peers {
var peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public key for peer ", i)
}
peerPublicKey = hex.EncodeToString(bytes)
if len(option.Peers) > 0 {
for i := range option.Peers {
peer := &option.Peers[i] // we need modify option here
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public key for peer ", i)
}
peer.PublicKey = hex.EncodeToString(bytes)
if peer.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key for peer ", i)
}
preSharedKey = hex.EncodeToString(bytes)
}
destination := peer.Addr()
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + destination.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
peer.PreSharedKey = hex.EncodeToString(bytes)
}
if len(peer.AllowedIPs) == 0 {
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) != 3 {
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 {
var peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode peer public key")
}
peerPublicKey = hex.EncodeToString(bytes)
option.PublicKey = hex.EncodeToString(bytes)
}
if option.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key")
}
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"
option.PreSharedKey = hex.EncodeToString(bytes)
}
}
outbound.option = option
if option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
}
mtu := option.MTU
if mtu == 0 {
mtu = 1408
}
if len(localPrefixes) == 0 {
if len(outbound.localPrefixes) == 0 {
return nil, E.New("missing local address")
}
var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
outbound.tunDevice, err = wireguard.NewStackDevice(outbound.localPrefixes, uint32(mtu))
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
@@ -279,17 +251,9 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
},
}, 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
for _, address := range localPrefixes {
for _, address := range outbound.localPrefixes {
if !address.Addr().Unmap().Is4() {
has6 = true
break
@@ -315,22 +279,193 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
return outbound, nil
}
func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
if address.Addr.IsValid() {
return address.AddrPort(), nil
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.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
}
func (w *WireGuard) init(ctx context.Context) error {
err := w.init0(ctx)
if err != nil {
return err
}
w.updateServerAddr(ctx)
return nil
}
func (w *WireGuard) init0(ctx context.Context) error {
if w.initOk.Load() {
return nil
}
w.initMutex.Lock()
defer w.initMutex.Unlock()
// double check like sync.Once
if w.initOk.Load() {
return nil
}
if w.initErr != nil {
return w.initErr
}
w.bind.ResetReservedForEndpoint()
w.serverAddrMap = make(map[M.Socksaddr]netip.AddrPort)
ipcConf, err := w.genIpcConf(ctx, false)
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return err
}
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf))
}
err = w.device.IpcSet(ipcConf)
if err != nil {
w.initErr = E.Cause(err, "setup wireguard")
return w.initErr
}
w.serverAddrTime.Store(time.Now())
err = w.tunDevice.Start()
if err != nil {
w.initErr = err
return w.initErr
}
w.initOk.Store(true)
return nil
}
func (w *WireGuard) updateServerAddr(ctx context.Context) {
if w.option.RefreshServerIPInterval != 0 && time.Since(w.serverAddrTime.Load()) > time.Second*time.Duration(w.option.RefreshServerIPInterval) {
if w.serverAddrMutex.TryLock() {
defer w.serverAddrMutex.Unlock()
ipcConf, err := w.genIpcConf(ctx, true)
if err != nil {
log.Warnln("[WG](%s)UpdateServerAddr failed to generate wireguard ipc conf: %s", w.option.Name, err)
return
}
err = w.device.IpcSet(ipcConf)
if err != nil {
log.Warnln("[WG](%s)UpdateServerAddr failed to update wireguard ipc conf: %s", w.option.Name, err)
return
}
w.serverAddrTime.Store(time.Now())
}
}
}
func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, error) {
ipcConf := ""
if !updateOnly {
ipcConf += "private_key=" + w.option.PrivateKey + "\n"
}
if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers {
peerAddr := peer.Addr()
destination, err := w.resolve(ctx, peerAddr)
if err != nil {
return "", E.Cause(err, "resolve endpoint domain for peer ", i)
}
if w.serverAddrMap[peerAddr] != destination {
w.serverAddrMap[peerAddr] = destination
} else if updateOnly {
continue
}
if len(w.option.Peers) == 1 { // must call SetConnectAddr if isConnect == true
w.bind.SetConnectAddr(destination)
}
ipcConf += "public_key=" + peer.PublicKey + "\n"
if updateOnly {
ipcConf += "update_only=true\n"
}
ipcConf += "endpoint=" + destination.String() + "\n"
if len(peer.Reserved) > 0 {
var reserved [3]uint8
copy(reserved[:], w.option.Reserved)
w.bind.SetReservedForEndpoint(destination, reserved)
}
if updateOnly {
continue
}
if peer.PreSharedKey != "" {
ipcConf += "preshared_key=" + peer.PreSharedKey + "\n"
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "allowed_ip=" + allowedIP + "\n"
}
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
}
}
} else {
destination, err := w.resolve(ctx, w.connectAddr)
if err != nil {
return "", E.Cause(err, "resolve endpoint domain")
}
if w.serverAddrMap[w.connectAddr] != destination {
w.serverAddrMap[w.connectAddr] = destination
} else if updateOnly {
return "", nil
}
w.bind.SetConnectAddr(destination) // must call SetConnectAddr if isConnect == true
ipcConf += "public_key=" + w.option.PublicKey + "\n"
if updateOnly {
ipcConf += "update_only=true\n"
}
ipcConf += "endpoint=" + destination.String() + "\n"
if updateOnly {
return ipcConf, nil
}
if w.option.PreSharedKey != "" {
ipcConf += "preshared_key=" + w.option.PreSharedKey + "\n"
}
var has4, has6 bool
for _, address := range w.localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "allowed_ip=0.0.0.0/0\n"
}
if has6 {
ipcConf += "allowed_ip=::/0\n"
}
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
}
}
return ipcConf, nil
}
func closeWireGuard(w *WireGuard) {
if w.device != nil {
w.device.Close()
}
_ = common.Close(w.tunDevice)
if w.closeCh != nil {
close(w.closeCh)
}
}
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := w.Base.DialOptions(opts...)
w.dialer.SetDialer(dialer.NewDialer(options...))
var conn net.Conn
w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start()
})
if w.startErr != nil {
return nil, w.startErr
if err = w.init(ctx); err != nil {
return nil, err
}
if !metadata.Resolved() || w.resolver != nil {
r := resolver.DefaultResolver
@@ -358,13 +493,7 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
options := w.Base.DialOptions(opts...)
w.dialer.SetDialer(dialer.NewDialer(options...))
var pc net.PacketConn
w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start()
})
if w.startErr != nil {
return nil, w.startErr
}
if err != nil {
if err = w.init(ctx); err != nil {
return nil, err
}
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {

View File

@@ -0,0 +1,45 @@
//go:build with_gvisor
package outbound
import (
"context"
"runtime"
"testing"
"time"
)
func TestWireGuardGC(t *testing.T) {
option := WireGuardOption{}
option.Server = "162.159.192.1"
option.Port = 2408
option.PrivateKey = "iOx7749AdqH3IqluG7+0YbGKd0m1mcEXAfGRzpy9rG8="
option.PublicKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo="
option.Ip = "172.16.0.2"
option.Ipv6 = "2606:4700:110:8d29:be92:3a6a:f4:c437"
option.Reserved = []uint8{51, 69, 125}
wg, err := NewWireGuard(option)
if err != nil {
t.Error(err)
}
closeCh := make(chan struct{})
wg.closeCh = closeCh
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
err = wg.init(ctx)
if err != nil {
t.Error(err)
return
}
// must do a small sleep before test GC
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
time.Sleep(10 * time.Millisecond)
wg = nil
runtime.GC()
select {
case <-closeCh:
return
case <-ctx.Done():
t.Error("timeout not GC")
}
}

View File

@@ -164,6 +164,8 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers,
}),
disableUDP: option.DisableUDP,

View File

@@ -31,20 +31,24 @@ type GroupBase struct {
failedTesting atomic.Bool
proxies [][]C.Proxy
versions []atomic.Uint32
TestTimeout int
maxFailedTimes int
}
type GroupBaseOption struct {
outbound.BaseOption
filter string
excludeFilter string
excludeType string
providers []provider.ProxyProvider
filter string
excludeFilter string
excludeType string
TestTimeout int
maxFailedTimes int
providers []provider.ProxyProvider
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
var excludeFilterReg *regexp2.Regexp
if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
}
var excludeTypeArray []string
if opt.excludeType != "" {
@@ -54,7 +58,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
var filterRegs []*regexp2.Regexp
if opt.filter != "" {
for _, filter := range strings.Split(opt.filter, "`") {
filterReg := regexp2.MustCompile(filter, 0)
filterReg := regexp2.MustCompile(filter, regexp2.None)
filterRegs = append(filterRegs, filterReg)
}
}
@@ -66,6 +70,15 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
excludeTypeArray: excludeTypeArray,
providers: opt.providers,
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))
@@ -113,7 +126,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
@@ -137,7 +150,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
@@ -178,7 +191,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
var newProxies []C.Proxy
for _, p := range proxies {
name := p.Name()
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
if mat, _ := gb.excludeFilterReg.MatchString(name); mat {
continue
}
newProxies = append(newProxies, p)
@@ -240,13 +253,13 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond {
gb.failedTimes = 0
return
}
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())
gb.healthCheck()
}
@@ -275,20 +288,8 @@ func (gb *GroupBase) healthCheck() {
gb.failedTimes = 0
}
func (gb *GroupBase) failedIntervalTime() int64 {
return 5 * time.Second.Milliseconds()
}
func (gb *GroupBase) onDialSuccess() {
if !gb.failedTesting.Load() {
gb.failedTimes = 0
}
}
func (gb *GroupBase) maxFailedTimes() int {
return 5
}
func (gb *GroupBase) failedTimeoutInterval() time.Duration {
return 5 * time.Second
}

View File

@@ -205,7 +205,6 @@ func strategyStickySessions(url string) strategyFn {
proxy := proxies[nowIdx]
if proxy.AliveForTestUrl(url) {
if nowIdx != idx {
lruCache.Delete(key)
lruCache.Set(key, nowIdx)
}
@@ -215,7 +214,6 @@ func strategyStickySessions(url string) strategyFn {
}
}
lruCache.Delete(key)
lruCache.Set(key, 0)
return proxies[0]
}
@@ -266,6 +264,8 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers,
}),
strategyFn: strategyFn,

View File

@@ -5,6 +5,8 @@ import (
"fmt"
"strings"
"github.com/dlclark/regexp2"
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure"
@@ -29,6 +31,7 @@ type GroupCommonOption struct {
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
TestTimeout int `group:"timeout,omitempty"`
MaxFailedTimes int `group:"max-failed-times,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
@@ -64,20 +67,33 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
groupOption.IncludeAllProviders = true
groupOption.IncludeAllProxies = true
}
var GroupUse []string
var GroupProxies []string
if groupOption.IncludeAllProviders {
GroupUse = append(GroupUse, AllProviders...)
} else {
GroupUse = groupOption.Use
groupOption.Use = AllProviders
}
if groupOption.IncludeAllProxies {
GroupProxies = append(groupOption.Proxies, AllProxies...)
} else {
GroupProxies = groupOption.Proxies
if groupOption.Filter != "" {
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(groupOption.Filter, "`") {
filterReg := regexp2.MustCompile(filter, regexp2.None)
filterRegs = append(filterRegs, filterReg)
}
for _, p := range AllProxies {
for _, filterReg := range filterRegs {
if mat, _ := filterReg.MatchString(p); mat {
groupOption.Proxies = append(groupOption.Proxies, p)
}
}
}
} else {
groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
groupOption.Proxies = []string{"COMPATIBLE"}
}
}
if len(GroupProxies) == 0 && len(GroupUse) == 0 {
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
}
@@ -91,17 +107,32 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
status = "*"
}
groupOption.ExpectedStatus = status
testUrl := groupOption.URL
if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
testUrl = groupOption.URL
if len(groupOption.Use) != 0 {
PDs, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
// if test URL is empty, use the first health check URL of providers
if groupOption.URL == "" {
for _, pd := range PDs {
if pd.HealthCheckURL() != "" {
groupOption.URL = pd.HealthCheckURL()
break
}
}
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
} else {
addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
}
providers = append(providers, PDs...)
}
if len(GroupProxies) != 0 {
ps, err := getProxies(proxyMap, GroupProxies)
if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
@@ -110,38 +141,28 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
}
// select don't need health check
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
// select don't need auto health check
if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.Interval == 0 {
groupOption.Interval = 300
}
}
hc := provider.NewHealthCheck(ps, testUrl, uint(groupOption.TestTimeout), 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)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
providers = append(providers, pd)
providers = append([]types.ProxyProvider{pd}, providers...)
providersMap[groupName] = pd
}
if len(GroupUse) != 0 {
list, err := getProviders(providersMap, GroupUse)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
// different proxy groups use different test URL
addTestUrlToProviders(list, testUrl, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
providers = append(providers, list...)
} else {
groupOption.Filter = ""
}
var group C.ProxyAdapter
switch groupOption.Type {
case "url-test":

View File

@@ -9,6 +9,7 @@ import (
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
)
type Relay struct {
@@ -149,6 +150,7 @@ func (r *Relay) Addr() string {
}
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
log.Warnln("The group [%s] with relay type is deprecated, please using dialer-proxy instead", option.Name)
return &Relay{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
@@ -160,6 +162,8 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
"",
"",
"",
5000,
5,
providers,
}),
Hidden: option.Hidden,

View File

@@ -114,6 +114,8 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers,
}),
selected: "COMPATIBLE",

View File

@@ -235,6 +235,8 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
option.TestTimeout,
option.MaxFailedTimes,
providers,
}),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),

View File

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

View File

@@ -27,6 +27,8 @@ type extraOption struct {
}
type HealthCheck struct {
ctx context.Context
ctxCancel context.CancelFunc
url string
extra map[string]*extraOption
mu sync.Mutex
@@ -36,7 +38,6 @@ type HealthCheck struct {
lazy bool
expectedStatus utils.IntRanges[uint16]
lastTouch atomic.TypedValue[time.Time]
done chan struct{}
singleDo *singledo.Single[struct{}]
timeout time.Duration
}
@@ -59,7 +60,7 @@ func (hc *HealthCheck) process() {
} else {
log.Debugln("Skip once health check because we are lazy")
}
case <-hc.done:
case <-hc.ctx.Done():
ticker.Stop()
hc.stop()
return
@@ -146,7 +147,7 @@ func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10))
// execute default health check
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
@@ -181,21 +182,21 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
filters = append(filters, filter)
}
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0)
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), regexp2.None)
}
}
for _, proxy := range hc.proxies {
// skip proxies that do not require health check
if filterReg != nil {
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil {
if match, _ := filterReg.MatchString(proxy.Name()); !match {
continue
}
}
p := proxy
b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), hc.timeout)
ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout)
defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus)
@@ -206,19 +207,22 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
}
func (hc *HealthCheck) close() {
hc.done <- struct{}{}
hc.ctxCancel()
}
func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
if url == "" {
// expectedStatus = nil
url = C.DefaultTestURL
expectedStatus = nil
interval = 0
}
if timeout == 0 {
timeout = 5000
}
ctx, cancel := context.WithCancel(context.Background())
return &HealthCheck{
ctx: ctx,
ctxCancel: cancel,
proxies: proxies,
url: url,
timeout: time.Duration(timeout) * time.Millisecond,
@@ -226,7 +230,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint,
interval: time.Duration(interval) * time.Second,
lazy: lazy,
expectedStatus: expectedStatus,
done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second),
}
}

View File

@@ -28,28 +28,35 @@ type healthCheckSchema struct {
}
type OverrideSchema struct {
UDP *bool `provider:"udp,omitempty"`
Up *string `provider:"up,omitempty"`
Down *string `provider:"down,omitempty"`
DialerProxy *string `provider:"dialer-proxy,omitempty"`
SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"`
Interface *string `provider:"interface-name,omitempty"`
RoutingMark *int `provider:"routing-mark,omitempty"`
IPVersion *string `provider:"ip-version,omitempty"`
TFO *bool `provider:"tfo,omitempty"`
MPTcp *bool `provider:"mptcp,omitempty"`
UDP *bool `provider:"udp,omitempty"`
UDPOverTCP *bool `provider:"udp-over-tcp,omitempty"`
Up *string `provider:"up,omitempty"`
Down *string `provider:"down,omitempty"`
DialerProxy *string `provider:"dialer-proxy,omitempty"`
SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"`
Interface *string `provider:"interface-name,omitempty"`
RoutingMark *int `provider:"routing-mark,omitempty"`
IPVersion *string `provider:"ip-version,omitempty"`
AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
}
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"`
Header map[string][]string `provider:"header,omitempty"`
}
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
@@ -84,16 +91,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path)
case "http":
path := C.Path.GetPathByHash("proxies", schema.URL)
if schema.Path != "" {
path := C.Path.Resolve(schema.Path)
path = C.Path.Resolve(schema.Path)
if !features.CMFA && !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("proxies", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}

View File

@@ -14,23 +14,6 @@ type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (pp *proxySetProvider) UpdatedAt() time.Time {
return pp.Fetcher.UpdatedAt
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
pp.Fetcher.Destroy()
return nil
}
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
"time"
@@ -46,19 +47,14 @@ type proxySetProvider struct {
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if pp.healthCheck.expectedStatus != nil {
expectedStatus = pp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url,
"expectedStatus": expectedStatus,
"updatedAt": pp.UpdatedAt,
"expectedStatus": pp.healthCheck.expectedStatus.String(),
"updatedAt": pp.UpdatedAt(),
"subscriptionInfo": pp.subscriptionInfo,
})
}
@@ -106,6 +102,10 @@ func (pp *proxySetProvider) 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) {
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
}
@@ -125,8 +125,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy())
if err != nil {
return
}
@@ -134,8 +134,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
if err != nil {
return
}
@@ -164,13 +164,13 @@ func (pp *proxySetProvider) closeAllConnections() {
})
}
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
_ = pd.Fetcher.Destroy()
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
return pp.Fetcher.Close()
}
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
}
@@ -181,7 +181,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, 0)
filterReg, err := regexp2.Compile(filter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
@@ -200,10 +200,15 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
pd.Fetcher = fetcher
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
return wrapper, nil
}
func (pp *ProxySetProvider) Close() error {
runtime.SetFinalizer(pp, nil)
return pp.proxySetProvider.Close()
}
// CompatibleProvider for auto gc
type CompatibleProvider struct {
*compatibleProvider
@@ -217,18 +222,13 @@ type compatibleProvider struct {
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if cp.healthCheck.expectedStatus != nil {
expectedStatus = cp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url,
"expectedStatus": expectedStatus,
"expectedStatus": cp.healthCheck.expectedStatus.String(),
})
}
@@ -271,12 +271,17 @@ func (cp *compatibleProvider) 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) {
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
}
func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close()
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
@@ -295,10 +300,15 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
}
wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
runtime.SetFinalizer(wrapper, (*CompatibleProvider).Close)
return wrapper, nil
}
func (cp *CompatibleProvider) Close() error {
runtime.SetFinalizer(cp, nil)
return cp.compatibleProvider.Close()
}
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
@@ -358,12 +368,12 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
continue
}
if len(excludeFilter) > 0 {
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
if mat, _ := excludeFilterReg.MatchString(name); mat {
continue
}
}
if len(filter) > 0 {
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
if mat, _ := filterReg.MatchString(name); !mat {
continue
}
}
@@ -375,29 +385,23 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
mapping["dialer-proxy"] = dialerProxy
}
if override.UDP != nil {
mapping["udp"] = *override.UDP
}
if override.Up != nil {
mapping["up"] = *override.Up
}
if override.Down != nil {
mapping["down"] = *override.Down
}
if override.DialerProxy != nil {
mapping["dialer-proxy"] = *override.DialerProxy
}
if override.SkipCertVerify != nil {
mapping["skip-cert-verify"] = *override.SkipCertVerify
}
if override.Interface != nil {
mapping["interface-name"] = *override.Interface
}
if override.RoutingMark != nil {
mapping["routing-mark"] = *override.RoutingMark
}
if override.IPVersion != nil {
mapping["ip-version"] = *override.IPVersion
val := reflect.ValueOf(override)
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
if field.IsNil() {
continue
}
fieldName := strings.Split(val.Type().Field(i).Tag.Get("provider"), ",")[0]
switch fieldName {
case "additional-prefix":
name := mapping["name"].(string)
mapping["name"] = *field.Interface().(*string) + name
case "additional-suffix":
name := mapping["name"].(string)
mapping["name"] = name + *field.Interface().(*string)
default:
mapping[fieldName] = field.Elem().Interface()
}
}
proxy, err := adapter.ParseProxy(mapping)

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,28 +15,39 @@ type TypedValue[T any] struct {
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 {
value := t.value.Load()
if value == nil {
return DefaultValue[T]()
}
return value.(T)
return value.(tValue[T]).value
}
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 {
old := t.value.Swap(new)
old := t.value.Swap(tValue[T]{new})
if old == nil {
return DefaultValue[T]()
}
return old.(T)
return old.(tValue[T]).value
}
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) {

View File

@@ -330,15 +330,38 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["h2-opts"] = h2Opts
case "ws":
case "ws", "httpupgrade":
headers := make(map[string]any)
wsOpts := make(map[string]any)
wsOpts["path"] = []string{"/"}
wsOpts["path"] = "/"
if host, ok := values["host"]; ok && host != "" {
headers["Host"] = host.(string)
}
if path, ok := values["path"]; ok && path != "" {
wsOpts["path"] = path.(string)
path := path.(string)
pathURL, err := url.Parse(path)
if err == nil {
query := pathURL.Query()
if earlyData := query.Get("ed"); earlyData != "" {
med, err := strconv.Atoi(earlyData)
if err == nil {
switch network {
case "ws":
wsOpts["max-early-data"] = med
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
case "httpupgrade":
wsOpts["v2ray-http-upgrade-fast-open"] = true
}
query.Del("ed")
pathURL.RawQuery = query.Encode()
path = pathURL.String()
}
}
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader
}
}
wsOpts["path"] = path
}
wsOpts["headers"] = headers
vmess["ws-opts"] = wsOpts

View File

@@ -8,8 +8,8 @@ import (
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/randv2"
"github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/zhangyunhao116/fastrand"
)
var hostsSuffix = []string{
@@ -302,11 +302,11 @@ func RandHost() string {
prefix += string(buf[6:8]) + "-"
prefix += string(buf[len(buf)-8:])
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
return prefix + hostsSuffix[randv2.IntN(hostsLen)]
}
func RandUserAgent() string {
return userAgents[fastrand.Intn(uaLen)]
return userAgents[randv2.IntN(uaLen)]
}
func SetUserAgent(header http.Header) {

View File

@@ -100,7 +100,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
h2Opts["headers"] = headers
proxy["h2-opts"] = h2Opts
case "ws":
case "ws", "httpupgrade":
headers := make(map[string]any)
wsOpts := make(map[string]any)
headers["User-Agent"] = RandUserAgent()
@@ -113,7 +113,13 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
if err != nil {
return fmt.Errorf("bad WebSocket max early data size: %v", err)
}
wsOpts["max-early-data"] = med
switch network {
case "ws":
wsOpts["max-early-data"] = med
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
case "httpupgrade":
wsOpts["v2ray-http-upgrade-fast-open"] = true
}
}
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader

View File

@@ -223,6 +223,10 @@ func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock()
defer c.mu.Unlock()
c.delete(key)
}
func (c *LruCache[K, V]) delete(key K) {
if le, ok := c.cache[key]; ok {
c.deleteElement(le)
}
@@ -255,6 +259,34 @@ func (c *LruCache[K, V]) Clear() error {
return nil
}
// Compute either sets the computed new value for the key or deletes
// the value for the key. When the delete result of the valueFn function
// is set to true, the value will be deleted, if it exists. When delete
// is set to false, the value is updated to the newValue.
// The ok result indicates whether value was computed and stored, thus, is
// present in the map. The actual result contains the new value in cases where
// the value was computed and stored.
func (c *LruCache[K, V]) Compute(
key K,
valueFn func(oldValue V, loaded bool) (newValue V, delete bool),
) (actual V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if el := c.get(key); el != nil {
actual, ok = el.value, true
}
if newValue, del := valueFn(actual, ok); del {
if ok { // data not in cache, so needn't delete
c.delete(key)
}
return lo.Empty[V](), false
} else {
c.set(key, newValue)
return newValue, true
}
}
type entry[K comparable, V any] struct {
key K
value V

View File

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

View File

@@ -215,3 +215,8 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
return nil, os.ErrDeadlineExceeded
}
}
func IsPipe(conn any) bool {
_, ok := conn.(*pipe)
return ok
}

View File

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

View File

@@ -23,6 +23,12 @@ type ExtendedReader = network.ExtendedReader
var WriteBuffer = bufio.WriteBuffer
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)
}

View File

@@ -0,0 +1,23 @@
package net
import (
"net"
"runtime"
"time"
)
var (
KeepAliveIdle = 0 * time.Second
KeepAliveInterval = 0 * time.Second
DisableKeepAlive = false
)
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
if runtime.GOOS == "android" || DisableKeepAlive {
_ = tcp.SetKeepAlive(false)
} else {
tcpKeepAlive(tcp)
}
}
}

View File

@@ -0,0 +1,10 @@
//go:build !go1.23
package net
import "net"
func tcpKeepAlive(tcp *net.TCPConn) {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
}

View File

@@ -0,0 +1,19 @@
//go:build go1.23
package net
import "net"
func tcpKeepAlive(tcp *net.TCPConn) {
config := net.KeepAliveConfig{
Enable: true,
Idle: KeepAliveIdle,
Interval: KeepAliveInterval,
}
if !SupportTCPKeepAliveCount() {
// it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1
// for Count on those old Windows if you intend to customize the TCP keep-alive settings.
config.Count = -1
}
_ = tcp.SetKeepAliveConfig(config)
}

View File

@@ -0,0 +1,15 @@
//go:build go1.23 && unix
package net
func SupportTCPKeepAliveIdle() bool {
return true
}
func SupportTCPKeepAliveInterval() bool {
return true
}
func SupportTCPKeepAliveCount() bool {
return true
}

View File

@@ -0,0 +1,63 @@
//go:build go1.23 && windows
// copy and modify from golang1.23's internal/syscall/windows/version_windows.go
package net
import (
"errors"
"sync"
"syscall"
"github.com/metacubex/mihomo/constant/features"
"golang.org/x/sys/windows"
)
var (
supportTCPKeepAliveIdle bool
supportTCPKeepAliveInterval bool
supportTCPKeepAliveCount bool
)
var initTCPKeepAlive = sync.OnceFunc(func() {
s, err := windows.WSASocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP, nil, 0, windows.WSA_FLAG_NO_HANDLE_INHERIT)
if err != nil {
// Fallback to checking the Windows version.
major, build := features.WindowsMajorVersion, features.WindowsBuildNumber
supportTCPKeepAliveIdle = major >= 10 && build >= 16299
supportTCPKeepAliveInterval = major >= 10 && build >= 16299
supportTCPKeepAliveCount = major >= 10 && build >= 15063
return
}
defer windows.Closesocket(s)
var optSupported = func(opt int) bool {
err := windows.SetsockoptInt(s, syscall.IPPROTO_TCP, opt, 1)
return !errors.Is(err, syscall.WSAENOPROTOOPT)
}
supportTCPKeepAliveIdle = optSupported(windows.TCP_KEEPIDLE)
supportTCPKeepAliveInterval = optSupported(windows.TCP_KEEPINTVL)
supportTCPKeepAliveCount = optSupported(windows.TCP_KEEPCNT)
})
// SupportTCPKeepAliveIdle indicates whether TCP_KEEPIDLE is supported.
// The minimal requirement is Windows 10.0.16299.
func SupportTCPKeepAliveIdle() bool {
initTCPKeepAlive()
return supportTCPKeepAliveIdle
}
// SupportTCPKeepAliveInterval indicates whether TCP_KEEPINTVL is supported.
// The minimal requirement is Windows 10.0.16299.
func SupportTCPKeepAliveInterval() bool {
initTCPKeepAlive()
return supportTCPKeepAliveInterval
}
// SupportTCPKeepAliveCount indicates whether TCP_KEEPCNT is supported.
// supports TCP_KEEPCNT.
// The minimal requirement is Windows 10.0.15063.
func SupportTCPKeepAliveCount() bool {
initTCPKeepAlive()
return supportTCPKeepAliveCount
}

View File

@@ -4,11 +4,8 @@ import (
"fmt"
"net"
"strings"
"time"
)
var KeepAliveInterval = 15 * time.Second
func SplitNetworkType(s string) (string, string, error) {
var (
shecme string
@@ -47,10 +44,3 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
host, port, err = net.SplitHostPort(temp)
return
}
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
}
}

View File

@@ -51,3 +51,23 @@ func UnMasked(p netip.Prefix) netip.Addr {
}
return addr
}
// PrefixCompare returns an integer comparing two prefixes.
// The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2.
// modify from https://github.com/golang/go/issues/61642#issuecomment-1848587909
func PrefixCompare(p, p2 netip.Prefix) int {
// compare by validity, address family and prefix base address
if c := p.Masked().Addr().Compare(p2.Masked().Addr()); c != 0 {
return c
}
// compare by prefix length
f1, f2 := p.Bits(), p2.Bits()
if f1 < f2 {
return -1
}
if f1 > f2 {
return 1
}
// compare by prefix address
return p.Addr().Compare(p2.Addr())
}

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

@@ -3,8 +3,8 @@ package pool
import (
"testing"
"github.com/metacubex/randv2"
"github.com/stretchr/testify/assert"
"github.com/zhangyunhao116/fastrand"
)
func TestAllocGet(t *testing.T) {
@@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
func BenchmarkMSB(b *testing.B) {
for i := 0; i < b.N; i++ {
msb(fastrand.Int())
msb(randv2.Int())
}
}

View File

@@ -59,8 +59,8 @@ func (q *Queue[T]) Copy() []T {
// Len returns the number of items in this queue.
func (q *Queue[T]) Len() int64 {
q.lock.Lock()
defer q.lock.Unlock()
q.lock.RLock()
defer q.lock.RUnlock()
return int64(len(q.items))
}

View File

@@ -0,0 +1,224 @@
// copy and modify from "golang.org/x/sync/singleflight"
// Copyright 2013 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.
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight
import (
"bytes"
"errors"
"fmt"
"runtime"
"runtime/debug"
"sync"
)
// errGoexit indicates the runtime.Goexit was called in
// the user given function.
var errGoexit = errors.New("runtime.Goexit was called")
// A panicError is an arbitrary value recovered from a panic
// with the stack trace during the execution of given function.
type panicError struct {
value interface{}
stack []byte
}
// Error implements error interface.
func (p *panicError) Error() string {
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
}
func (p *panicError) Unwrap() error {
err, ok := p.value.(error)
if !ok {
return nil
}
return err
}
func newPanicError(v interface{}) error {
stack := debug.Stack()
// The first line of the stack trace is of the form "goroutine N [status]:"
// but by the time the panic reaches Do the goroutine may no longer exist
// and its status will have changed. Trim out the misleading line.
if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
stack = stack[line+1:]
}
return &panicError{value: v, stack: stack}
}
// call is an in-flight or completed singleflight.Do call
type call[T any] struct {
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
val T
err error
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
dups int
chans []chan<- Result[T]
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group[T any] struct {
mu sync.Mutex // protects m
m map[string]*call[T] // lazily initialized
StoreResult bool
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result[T any] struct {
Val T
Err error
Shared bool
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok {
c.dups++
g.mu.Unlock()
c.wg.Wait()
if e, ok := c.err.(*panicError); ok {
panic(e)
} else if c.err == errGoexit {
runtime.Goexit()
}
return c.val, c.err, true
}
c := new(call[T])
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {
ch := make(chan Result[T], 1)
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
return ch
}
c := &call[T]{chans: []chan<- Result[T]{ch}}
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
go g.doCall(c, key, fn)
return ch
}
// doCall handles the single call for a key.
func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
normalReturn := false
recovered := false
// use double-defer to distinguish panic from runtime.Goexit,
// more details see https://golang.org/cl/134395
defer func() {
// the given function invoked runtime.Goexit
if !normalReturn && !recovered {
c.err = errGoexit
}
g.mu.Lock()
defer g.mu.Unlock()
c.wg.Done()
if g.m[key] == c && !g.StoreResult {
delete(g.m, key)
}
if e, ok := c.err.(*panicError); ok {
// In order to prevent the waiting channels from being blocked forever,
// needs to ensure that this panic cannot be recovered.
if len(c.chans) > 0 {
go panic(e)
select {} // Keep this goroutine around so that it will appear in the crash dump.
} else {
panic(e)
}
} else if c.err == errGoexit {
// Already in the process of goexit, no need to call again
} else {
// Normal return
for _, ch := range c.chans {
ch <- Result[T]{c.val, c.err, c.dups > 0}
}
}
}()
func() {
defer func() {
if !normalReturn {
// Ideally, we would wait to take a stack trace until we've determined
// whether this is a panic or a runtime.Goexit.
//
// Unfortunately, the only way we can distinguish the two is to see
// whether the recover stopped the goroutine from terminating, and by
// the time we know that, the part of the stack trace relevant to the
// panic has been discarded.
if r := recover(); r != nil {
c.err = newPanicError(r)
}
}
}()
c.val, c.err = fn()
normalReturn = true
}()
if !normalReturn {
recovered = true
}
}
// Forget tells the singleflight to forget about a key. Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group[T]) Forget(key string) {
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
}
func (g *Group[T]) Reset() {
g.mu.Lock()
g.m = nil
g.mu.Unlock()
}

50
common/utils/callback.go Normal file
View File

@@ -0,0 +1,50 @@
package utils
import (
"io"
"sync"
list "github.com/bahlo/generic-list-go"
)
type Callback[T any] struct {
list list.List[func(T)]
mutex sync.RWMutex
}
func NewCallback[T any]() *Callback[T] {
return &Callback[T]{}
}
func (c *Callback[T]) Register(item func(T)) io.Closer {
c.mutex.Lock()
defer c.mutex.Unlock()
element := c.list.PushBack(item)
return &callbackCloser[T]{
element: element,
callback: c,
}
}
func (c *Callback[T]) Emit(item T) {
c.mutex.RLock()
defer c.mutex.RUnlock()
for element := c.list.Front(); element != nil; element = element.Next() {
go element.Value(item)
}
}
type callbackCloser[T any] struct {
element *list.Element[func(T)]
callback *Callback[T]
once sync.Once
}
func (c *callbackCloser[T]) Close() error {
c.once.Do(func() {
c.callback.mutex.Lock()
defer c.callback.mutex.Unlock()
c.callback.list.Remove(c.element)
})
return nil
}

View File

@@ -20,6 +20,8 @@ func newIntRanges[T constraints.Integer](expected string, parseFn func(string) (
return nil, nil
}
// support: 200,302 or 200,204,401-429,501-503
expected = strings.ReplaceAll(expected, ",", "/")
list := strings.Split(expected, "/")
if len(list) > 28 {
return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges)
@@ -47,9 +49,9 @@ func newIntRangesFromList[T constraints.Integer](list []string, parseFn func(str
}
switch statusLen {
case 1:
case 1: // Port range
ranges = append(ranges, NewRange(T(start), T(start)))
case 2:
case 2: // Single port
end, err := parseFn(strings.Trim(status[1], "[ ]"))
if err != nil {
return nil, errIntRanges
@@ -108,7 +110,7 @@ func (ranges IntRanges[T]) Check(status T) bool {
return false
}
func (ranges IntRanges[T]) ToString() string {
func (ranges IntRanges[T]) String() string {
if len(ranges) == 0 {
return "*"
}
@@ -130,3 +132,17 @@ func (ranges IntRanges[T]) ToString() string {
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

@@ -2,19 +2,39 @@ package utils
import (
"github.com/gofrs/uuid/v5"
"github.com/zhangyunhao116/fastrand"
"github.com/metacubex/randv2"
)
type fastRandReader struct{}
type unsafeRandReader struct{}
func (r fastRandReader) Read(p []byte) (int, error) {
return fastrand.Read(p)
func (r unsafeRandReader) Read(p []byte) (n int, err error) {
// modify from https://github.com/golang/go/blob/587c3847da81aa7cfc3b3db2677c8586c94df13a/src/runtime/rand.go#L70-L89
// Inspired by wyrand.
n = len(p)
v := randv2.Uint64()
for len(p) > 0 {
v ^= 0xa0761d6478bd642f
v *= 0xe7037ed1a0b428db
size := 8
if len(p) < 8 {
size = len(p)
}
for i := 0; i < size; i++ {
p[i] ^= byte(v >> (8 * i))
}
p = p[size:]
v = v>>32 | v<<32
}
return
}
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
var UnsafeRandReader = unsafeRandReader{}
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(UnsafeRandReader))
func NewUUIDV1() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV1() // fastrand.Read wouldn't cause error, so ignore err is safe
u, _ := UnsafeUUIDGenerator.NewV1() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}
@@ -23,7 +43,7 @@ func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
}
func NewUUIDV4() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV4() // fastrand.Read wouldn't cause error, so ignore err is safe
u, _ := UnsafeUUIDGenerator.NewV4() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}
@@ -32,12 +52,12 @@ func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
}
func NewUUIDV6() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV6() // fastrand.Read wouldn't cause error, so ignore err is safe
u, _ := UnsafeUUIDGenerator.NewV6() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV7() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV7() // fastrand.Read wouldn't cause error, so ignore err is safe
u, _ := UnsafeUUIDGenerator.NewV7() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}

View File

View File

@@ -5,12 +5,16 @@ import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
_ "embed"
"encoding/hex"
"errors"
"fmt"
"os"
"strconv"
"strings"
"sync"
C "github.com/metacubex/mihomo/constant"
)
var trustCerts []*x509.Certificate
@@ -18,6 +22,11 @@ var globalCertPool *x509.CertPool
var mutex sync.RWMutex
var errNotMatch = errors.New("certificate fingerprints do not match")
//go:embed ca-certificates.crt
var _CaCertificates []byte
var DisableEmbedCa, _ = strconv.ParseBool(os.Getenv("DISABLE_EMBED_CA"))
var DisableSystemCa, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_CA"))
func AddCertificate(certificate string) error {
mutex.Lock()
defer mutex.Unlock()
@@ -34,13 +43,20 @@ func AddCertificate(certificate string) error {
func initializeCertPool() {
var err error
globalCertPool, err = x509.SystemCertPool()
if err != nil {
if DisableSystemCa {
globalCertPool = x509.NewCertPool()
} else {
globalCertPool, err = x509.SystemCertPool()
if err != nil {
globalCertPool = x509.NewCertPool()
}
}
for _, cert := range trustCerts {
globalCertPool.AddCert(cert)
}
if !DisableEmbedCa {
globalCertPool.AppendCertsFromPEM(_CaCertificates)
}
}
func ResetCertificate() {
@@ -51,9 +67,6 @@ func ResetCertificate() {
}
func getCertPool() *x509.CertPool {
if len(trustCerts) == 0 {
return nil
}
if globalCertPool == nil {
mutex.Lock()
defer mutex.Unlock()
@@ -103,7 +116,7 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
var certificate []byte
var err error
if len(customCA) > 0 {
certificate, err = os.ReadFile(customCA)
certificate, err = os.ReadFile(C.Path.Resolve(customCA))
if err != nil {
return nil, fmt.Errorf("load ca error: %w", err)
}

View File

@@ -0,0 +1,14 @@
package ca
import (
"github.com/metacubex/mihomo/constant/features"
)
func init() {
// crypto/x509: certificate validation in Windows fails to validate IP in SAN
// https://github.com/golang/go/issues/37176
// As far as I can tell this is still the case on most older versions of Windows (but seems to be fixed in 10)
if features.WindowsMajorVersion < 10 && len(_CaCertificates) > 0 {
DisableSystemCa = true
}
}

View File

@@ -43,12 +43,20 @@ func (set *IpCidrSet) IsContainForString(ipString string) bool {
}
func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
return set.toIPSet().Contains(ip.WithZone(""))
return set.ToIPSet().Contains(ip.WithZone(""))
}
// MatchIp implements C.IpMatcher
func (set *IpCidrSet) MatchIp(ip netip.Addr) bool {
if set.IsEmpty() {
return false
}
return set.IsContain(ip)
}
func (set *IpCidrSet) Merge() error {
var b netipx.IPSetBuilder
b.AddSet(set.toIPSet())
b.AddSet(set.ToIPSet())
i, err := b.IPSet()
if err != nil {
return err
@@ -57,7 +65,23 @@ func (set *IpCidrSet) Merge() error {
return nil
}
func (set *IpCidrSet) toIPSet() *netipx.IPSet {
func (set *IpCidrSet) IsEmpty() bool {
return set == nil || len(set.rr) == 0
}
func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) {
for _, r := range set.rr {
for _, prefix := range r.Prefixes() {
if !f(prefix) {
return
}
}
}
}
// ToIPSet not safe convert to *netipx.IPSet
// be careful, must be used after Merge
func (set *IpCidrSet) ToIPSet() *netipx.IPSet {
return (*netipx.IPSet)(unsafe.Pointer(set))
}

View File

@@ -0,0 +1,77 @@
package cidr
import (
"encoding/binary"
"errors"
"io"
"net/netip"
"go4.org/netipx"
)
func (ss *IpCidrSet) WriteBin(w io.Writer) (err error) {
// version
_, err = w.Write([]byte{1})
if err != nil {
return err
}
// rr
err = binary.Write(w, binary.BigEndian, int64(len(ss.rr)))
if err != nil {
return err
}
for _, r := range ss.rr {
err = binary.Write(w, binary.BigEndian, r.From().As16())
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, r.To().As16())
if err != nil {
return err
}
}
return nil
}
func ReadIpCidrSet(r io.Reader) (ss *IpCidrSet, err error) {
// version
version := make([]byte, 1)
_, err = io.ReadFull(r, version)
if err != nil {
return nil, err
}
if version[0] != 1 {
return nil, errors.New("version is invalid")
}
ss = NewIpCidrSet()
var length int64
// rr
err = binary.Read(r, binary.BigEndian, &length)
if err != nil {
return nil, err
}
if length < 1 {
return nil, errors.New("length is invalid")
}
ss.rr = make([]netipx.IPRange, length)
for i := int64(0); i < length; i++ {
var a16 [16]byte
err = binary.Read(r, binary.BigEndian, &a16)
if err != nil {
return nil, err
}
from := netip.AddrFrom16(a16).Unmap()
err = binary.Read(r, binary.BigEndian, &a16)
if err != nil {
return nil, err
}
to := netip.AddrFrom16(a16).Unmap()
ss.rr[i] = netipx.IPRangeFrom(from, to)
}
return ss, nil
}

View File

@@ -3,6 +3,7 @@ package dhcp
import (
"context"
"net"
"net/netip"
"runtime"
"github.com/metacubex/mihomo/component/dialer"
@@ -24,5 +25,5 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
options = append(options, dialer.WithFallbackBind(true))
}
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
return dialer.ListenPacket(ctx, "udp4", listenAddr, netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 67), options...)
}

View File

@@ -14,6 +14,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
if err != nil {
return nil, err
}
destination = destination.Unmap()
var addr netip.Prefix
switch network {
@@ -23,7 +24,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
addr, err = ifaceObj.PickIPv6Addr(destination)
default:
if destination.IsValid() {
if destination.Is4() || destination.Is4In6() {
if destination.Is4() {
addr, err = ifaceObj.PickIPv4Addr(destination)
} else {
addr, err = ifaceObj.PickIPv6Addr(destination)
@@ -74,7 +75,7 @@ func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network str
return nil
}
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
port = "0"

View File

@@ -46,7 +46,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
return nil
}
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return "", err

View File

@@ -35,7 +35,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
return nil
}
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
addControlToListenConfig(lc, bindControl(ifaceName))
return address, nil

View File

@@ -11,8 +11,8 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
}
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address, rAddrPort)
}
func ParseNetwork(network string, addr netip.Addr) string {

View File

@@ -36,7 +36,7 @@ func bind6(handle syscall.Handle, ifaceIdx int) error {
return err
}
func bindControl(ifaceIdx int) controlFn {
func bindControl(ifaceIdx int, rAddrPort netip.AddrPort) controlFn {
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
@@ -55,7 +55,7 @@ func bindControl(ifaceIdx int) controlFn {
innerErr = bind4err
case "udp6":
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil && rAddrPort.Addr().Unmap().Is4() {
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
if bind4err != nil {
innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err)
@@ -76,23 +76,23 @@ func bindControl(ifaceIdx int) controlFn {
}
}
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, destination netip.Addr) error {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return err
}
addControlToDialer(dialer, bindControl(ifaceObj.Index))
addControlToDialer(dialer, bindControl(ifaceObj.Index, netip.AddrPortFrom(destination, 0)))
return nil
}
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return "", err
}
addControlToListenConfig(lc, bindControl(ifaceObj.Index))
addControlToListenConfig(lc, bindControl(ifaceObj.Index, rAddrPort))
return address, nil
}

View File

@@ -7,12 +7,13 @@ import (
"net"
"net/netip"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/log"
)
const (
@@ -24,6 +25,7 @@ type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port s
var (
dialMux sync.Mutex
IP4PEnable bool
actualSingleStackDialContext = serialSingleStackDialContext
actualDualStackDialContext = serialDualStackDialContext
tcpConcurrent = false
@@ -75,30 +77,30 @@ func DialContext(ctx context.Context, network, address string, options ...Option
}
}
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
if features.CMFA && DefaultSocketHook != nil {
return listenPacketHooked(ctx, network, address)
}
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
cfg := applyOptions(options...)
lc := &net.ListenConfig{}
if cfg.interfaceName != "" {
bind := bindIfaceToListenConfig
if cfg.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
addr, err := bind(cfg.interfaceName, lc, network, address)
if err != nil {
return nil, err
}
address = addr
}
if cfg.addrReuse {
addrReuseToListenConfig(lc)
}
if cfg.routingMark != 0 {
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CFMA)
socketHookToListenConfig(lc)
} else {
if cfg.interfaceName != "" {
bind := bindIfaceToListenConfig
if cfg.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
if err != nil {
return nil, err
}
address = addr
}
if cfg.routingMark != 0 {
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
}
}
return lc.ListenPacket(ctx, network, address)
@@ -124,11 +126,11 @@ func GetTcpConcurrent() bool {
}
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
if features.CMFA && DefaultSocketHook != nil {
return dialContextHooked(ctx, network, destination, port)
var address string
if IP4PEnable {
destination, port = lookupIP4P(destination, port)
}
address := net.JoinHostPort(destination.String(), port)
address = net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer
switch netDialer.(type) {
@@ -142,24 +144,30 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
}
dialer := netDialer.(*net.Dialer)
if opt.interfaceName != "" {
bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
}
if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination)
}
if opt.mpTcp {
setMultiPathTCP(dialer)
}
if opt.tfo {
return dialTFO(ctx, *dialer, network, address)
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CFMA)
socketHookToToDialer(dialer)
} else {
if opt.interfaceName != "" {
bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
}
if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination)
}
if opt.tfo && !DisableTFO {
return dialTFO(ctx, *dialer, network, address)
}
}
return dialer.DialContext(ctx, network, address)
}
@@ -371,15 +379,33 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (net.C
}
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
opt := WithOption(d.Opt)
opt := d.Opt // make a copy
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
opt = WithInterface("")
WithInterface("")(&opt)
}
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(opt))
}
func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...)
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 netip.Addr, port string) (netip.Addr, string) {
ip := addr.AsSlice()
if ip[0] == 0x20 && ip[1] == 0x01 &&
ip[2] == 0x00 && ip[3] == 0x00 {
addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
return addr, port
}
return addr, port
}

View File

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

View File

@@ -1,22 +0,0 @@
//go:build !(android && cmfa)
package dialer
import (
"context"
"net"
"net/netip"
"syscall"
)
type SocketControl func(network, address string, conn syscall.RawConn) error
var DefaultSocketHook SocketControl
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
return nil, nil
}
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
return nil, nil
}

View File

@@ -0,0 +1,27 @@
package dialer
import (
"context"
"net"
"syscall"
)
// SocketControl
// never change type traits because it's used in CFMA
type SocketControl func(network, address string, conn syscall.RawConn) error
// DefaultSocketHook
// never change type traits because it's used in CFMA
var DefaultSocketHook SocketControl
func socketHookToToDialer(dialer *net.Dialer) {
addControlToDialer(dialer, func(ctx context.Context, network, address string, c syscall.RawConn) error {
return DefaultSocketHook(network, address, c)
})
}
func socketHookToListenConfig(lc *net.ListenConfig) {
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
return DefaultSocketHook(network, address, c)
})
}

View File

@@ -6,9 +6,11 @@ import (
"net"
"time"
"github.com/sagernet/tfo-go"
"github.com/metacubex/tfo-go"
)
var DisableTFO = false
type tfoConn struct {
net.Conn
closed bool

View File

@@ -0,0 +1,11 @@
package dialer
import "github.com/metacubex/mihomo/constant/features"
func init() {
// According to MSDN, this option is available since Windows 10, 1607
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx
if features.WindowsMajorVersion < 10 || (features.WindowsMajorVersion == 10 && features.WindowsBuildNumber < 14393) {
DisableTFO = true
}
}

View File

@@ -1,99 +0,0 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_ENDIAN__
#define __BPF_ENDIAN__
/*
* Isolate byte #n and put it into byte #m, for __u##b type.
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
*/
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
#define ___bpf_swab16(x) ((__u16)( \
___bpf_mvb(x, 16, 0, 1) | \
___bpf_mvb(x, 16, 1, 0)))
#define ___bpf_swab32(x) ((__u32)( \
___bpf_mvb(x, 32, 0, 3) | \
___bpf_mvb(x, 32, 1, 2) | \
___bpf_mvb(x, 32, 2, 1) | \
___bpf_mvb(x, 32, 3, 0)))
#define ___bpf_swab64(x) ((__u64)( \
___bpf_mvb(x, 64, 0, 7) | \
___bpf_mvb(x, 64, 1, 6) | \
___bpf_mvb(x, 64, 2, 5) | \
___bpf_mvb(x, 64, 3, 4) | \
___bpf_mvb(x, 64, 4, 3) | \
___bpf_mvb(x, 64, 5, 2) | \
___bpf_mvb(x, 64, 6, 1) | \
___bpf_mvb(x, 64, 7, 0)))
/* LLVM's BPF target selects the endianness of the CPU
* it compiles on, or the user specifies (bpfel/bpfeb),
* respectively. The used __BYTE_ORDER__ is defined by
* the compiler, we cannot rely on __BYTE_ORDER from
* libc headers, since it doesn't reflect the actual
* requested byte order.
*
* Note, LLVM's BPF target has different __builtin_bswapX()
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
* in bpfel and bpfeb case, which means below, that we map
* to cpu_to_be16(). We could use it unconditionally in BPF
* case, but better not rely on it, so that this header here
* can be used from application and BPF program side, which
* use different targets.
*/
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __bpf_ntohs(x) __builtin_bswap16(x)
# define __bpf_htons(x) __builtin_bswap16(x)
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
# define __bpf_constant_htons(x) ___bpf_swab16(x)
# define __bpf_ntohl(x) __builtin_bswap32(x)
# define __bpf_htonl(x) __builtin_bswap32(x)
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __bpf_ntohs(x) (x)
# define __bpf_htons(x) (x)
# define __bpf_constant_ntohs(x) (x)
# define __bpf_constant_htons(x) (x)
# define __bpf_ntohl(x) (x)
# define __bpf_htonl(x) (x)
# define __bpf_constant_ntohl(x) (x)
# define __bpf_constant_htonl(x) (x)
# define __bpf_be64_to_cpu(x) (x)
# define __bpf_cpu_to_be64(x) (x)
# define __bpf_constant_be64_to_cpu(x) (x)
# define __bpf_constant_cpu_to_be64(x) (x)
#else
# error "Fix your compiler's __BYTE_ORDER__?!"
#endif
#define bpf_htons(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_htons(x) : __bpf_htons(x))
#define bpf_ntohs(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
#define bpf_htonl(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_htonl(x) : __bpf_htonl(x))
#define bpf_ntohl(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
#define bpf_cpu_to_be64(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
#define bpf_be64_to_cpu(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
#endif /* __BPF_ENDIAN__ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,262 +0,0 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_HELPERS__
#define __BPF_HELPERS__
/*
* Note that bpf programs need to include either
* vmlinux.h (auto-generated from BTF) or linux/types.h
* in advance since bpf_helper_defs.h uses such types
* as __u64.
*/
#include "bpf_helper_defs.h"
#define __uint(name, val) int (*name)[val]
#define __type(name, val) typeof(val) *name
#define __array(name, val) typeof(val) *name[]
/*
* Helper macro to place programs, maps, license in
* different sections in elf_bpf file. Section names
* are interpreted by libbpf depending on the context (BPF programs, BPF maps,
* extern variables, etc).
* To allow use of SEC() with externs (e.g., for extern .maps declarations),
* make sure __attribute__((unused)) doesn't trigger compilation warning.
*/
#define SEC(name) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \
__attribute__((section(name), used)) \
_Pragma("GCC diagnostic pop") \
/* Avoid 'linux/stddef.h' definition of '__always_inline'. */
#undef __always_inline
#define __always_inline inline __attribute__((always_inline))
#ifndef __noinline
#define __noinline __attribute__((noinline))
#endif
#ifndef __weak
#define __weak __attribute__((weak))
#endif
/*
* Use __hidden attribute to mark a non-static BPF subprogram effectively
* static for BPF verifier's verification algorithm purposes, allowing more
* extensive and permissive BPF verification process, taking into account
* subprogram's caller context.
*/
#define __hidden __attribute__((visibility("hidden")))
/* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include
* any system-level headers (such as stddef.h, linux/version.h, etc), and
* commonly-used macros like NULL and KERNEL_VERSION aren't available through
* vmlinux.h. This just adds unnecessary hurdles and forces users to re-define
* them on their own. So as a convenience, provide such definitions here.
*/
#ifndef NULL
#define NULL ((void *)0)
#endif
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
#endif
/*
* Helper macros to manipulate data structures
*/
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) \
({ \
void *__mptr = (void *)(ptr); \
((type *)(__mptr - offsetof(type, member))); \
})
#endif
/*
* Helper macro to throw a compilation error if __bpf_unreachable() gets
* built into the resulting code. This works given BPF back end does not
* implement __builtin_trap(). This is useful to assert that certain paths
* of the program code are never used and hence eliminated by the compiler.
*
* For example, consider a switch statement that covers known cases used by
* the program. __bpf_unreachable() can then reside in the default case. If
* the program gets extended such that a case is not covered in the switch
* statement, then it will throw a build error due to the default case not
* being compiled out.
*/
#ifndef __bpf_unreachable
# define __bpf_unreachable() __builtin_trap()
#endif
/*
* Helper function to perform a tail call with a constant/immediate map slot.
*/
#if __clang_major__ >= 8 && defined(__bpf__)
static __always_inline void
bpf_tail_call_static(void *ctx, const void *map, const __u32 slot)
{
if (!__builtin_constant_p(slot))
__bpf_unreachable();
/*
* Provide a hard guarantee that LLVM won't optimize setting r2 (map
* pointer) and r3 (constant map index) from _different paths_ ending
* up at the _same_ call insn as otherwise we won't be able to use the
* jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel
* given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key
* tracking for prog array pokes") for details on verifier tracking.
*
* Note on clobber list: we need to stay in-line with BPF calling
* convention, so even if we don't end up using r0, r4, r5, we need
* to mark them as clobber so that LLVM doesn't end up using them
* before / after the call.
*/
asm volatile("r1 = %[ctx]\n\t"
"r2 = %[map]\n\t"
"r3 = %[slot]\n\t"
"call 12"
:: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot)
: "r0", "r1", "r2", "r3", "r4", "r5");
}
#endif
/*
* Helper structure used by eBPF C program
* to describe BPF map attributes to libbpf loader
*/
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
};
enum libbpf_pin_type {
LIBBPF_PIN_NONE,
/* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */
LIBBPF_PIN_BY_NAME,
};
enum libbpf_tristate {
TRI_NO = 0,
TRI_YES = 1,
TRI_MODULE = 2,
};
#define __kconfig __attribute__((section(".kconfig")))
#define __ksym __attribute__((section(".ksyms")))
#ifndef ___bpf_concat
#define ___bpf_concat(a, b) a ## b
#endif
#ifndef ___bpf_apply
#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
#endif
#ifndef ___bpf_nth
#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
#endif
#ifndef ___bpf_narg
#define ___bpf_narg(...) \
___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif
#define ___bpf_fill0(arr, p, x) do {} while (0)
#define ___bpf_fill1(arr, p, x) arr[p] = x
#define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args)
#define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args)
#define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args)
#define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args)
#define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args)
#define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args)
#define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args)
#define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args)
#define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args)
#define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args)
#define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args)
#define ___bpf_fill(arr, args...) \
___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
/*
* BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
* in a structure.
*/
#define BPF_SEQ_PRINTF(seq, fmt, args...) \
({ \
static const char ___fmt[] = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
___bpf_fill(___param, args); \
_Pragma("GCC diagnostic pop") \
\
bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \
___param, sizeof(___param)); \
})
/*
* BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of
* an array of u64.
*/
#define BPF_SNPRINTF(out, out_size, fmt, args...) \
({ \
static const char ___fmt[] = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
___bpf_fill(___param, args); \
_Pragma("GCC diagnostic pop") \
\
bpf_snprintf(out, out_size, ___fmt, \
___param, sizeof(___param)); \
})
#ifdef BPF_NO_GLOBAL_DATA
#define BPF_PRINTK_FMT_MOD
#else
#define BPF_PRINTK_FMT_MOD static const
#endif
#define __bpf_printk(fmt, ...) \
({ \
BPF_PRINTK_FMT_MOD char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), \
##__VA_ARGS__); \
})
/*
* __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
* instead of an array of u64.
*/
#define __bpf_vprintk(fmt, args...) \
({ \
static const char ___fmt[] = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
___bpf_fill(___param, args); \
_Pragma("GCC diagnostic pop") \
\
bpf_trace_vprintk(___fmt, sizeof(___fmt), \
___param, sizeof(___param)); \
})
/* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
* Otherwise use __bpf_vprintk
*/
#define ___bpf_pick_printk(...) \
___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
__bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
__bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\
__bpf_printk /*1*/, __bpf_printk /*0*/)
/* Helper macro to print out debug messages */
#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
#endif

View File

@@ -1,342 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
//#include <linux/types.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
//#include <linux/if_packet.h>
//#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/tcp.h>
//#include <linux/udp.h>
#include <linux/pkt_cls.h>
#include "bpf_endian.h"
#include "bpf_helpers.h"
#define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check))
#define IP_DST_OFF (ETH_HLEN + offsetof(struct iphdr, daddr))
#define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
#define IP_PROTO_OFF (ETH_HLEN + offsetof(struct iphdr, protocol))
#define TCP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, check))
#define TCP_SRC_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, source))
#define TCP_DST_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, dest))
//#define UDP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, check))
//#define UDP_SRC_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, source))
//#define UDP_DST_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct udphdr, dest))
#define IS_PSEUDO 0x10
struct origin_info {
__be32 ip;
__be16 port;
__u16 pad;
};
struct origin_info *origin_info_unused __attribute__((unused));
struct redir_info {
__be32 sip;
__be32 dip;
__be16 sport;
__be16 dport;
};
struct redir_info *redir_info_unused __attribute__((unused));
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, struct redir_info);
__type(value, struct origin_info);
__uint(max_entries, 65535);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} pair_original_dst_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u32);
__uint(max_entries, 3);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} redir_params_map SEC(".maps");
static __always_inline int rewrite_ip(struct __sk_buff *skb, __be32 new_ip, bool is_dest) {
int ret, off = 0, flags = IS_PSEUDO;
__be32 old_ip;
if (is_dest)
ret = bpf_skb_load_bytes(skb, IP_DST_OFF, &old_ip, 4);
else
ret = bpf_skb_load_bytes(skb, IP_SRC_OFF, &old_ip, 4);
if (ret < 0) {
return ret;
}
off = TCP_CSUM_OFF;
// __u8 proto;
//
// ret = bpf_skb_load_bytes(skb, IP_PROTO_OFF, &proto, 1);
// if (ret < 0) {
// return BPF_DROP;
// }
//
// switch (proto) {
// case IPPROTO_TCP:
// off = TCP_CSUM_OFF;
// break;
//
// case IPPROTO_UDP:
// off = UDP_CSUM_OFF;
// flags |= BPF_F_MARK_MANGLED_0;
// break;
//
// case IPPROTO_ICMPV6:
// off = offsetof(struct icmp6hdr, icmp6_cksum);
// break;
// }
//
// if (off) {
ret = bpf_l4_csum_replace(skb, off, old_ip, new_ip, flags | sizeof(new_ip));
if (ret < 0) {
return ret;
}
// }
ret = bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip));
if (ret < 0) {
return ret;
}
if (is_dest)
ret = bpf_skb_store_bytes(skb, IP_DST_OFF, &new_ip, sizeof(new_ip), 0);
else
ret = bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0);
if (ret < 0) {
return ret;
}
return 1;
}
static __always_inline int rewrite_port(struct __sk_buff *skb, __be16 new_port, bool is_dest) {
int ret, off = 0;
__be16 old_port;
if (is_dest)
ret = bpf_skb_load_bytes(skb, TCP_DST_OFF, &old_port, 2);
else
ret = bpf_skb_load_bytes(skb, TCP_SRC_OFF, &old_port, 2);
if (ret < 0) {
return ret;
}
off = TCP_CSUM_OFF;
ret = bpf_l4_csum_replace(skb, off, old_port, new_port, sizeof(new_port));
if (ret < 0) {
return ret;
}
if (is_dest)
ret = bpf_skb_store_bytes(skb, TCP_DST_OFF, &new_port, sizeof(new_port), 0);
else
ret = bpf_skb_store_bytes(skb, TCP_SRC_OFF, &new_port, sizeof(new_port), 0);
if (ret < 0) {
return ret;
}
return 1;
}
static __always_inline bool is_lan_ip(__be32 addr) {
if (addr == 0xffffffff)
return true;
__u8 fist = (__u8)(addr & 0xff);
if (fist == 127 || fist == 10)
return true;
__u8 second = (__u8)((addr >> 8) & 0xff);
if (fist == 172 && second >= 16 && second <= 31)
return true;
if (fist == 192 && second == 168)
return true;
return false;
}
SEC("tc_mihomo_auto_redir_ingress")
int tc_redir_ingress_func(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *iph = (struct iphdr *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return TC_ACT_OK;
__u32 key = 0, *route_index, *redir_ip, *redir_port;
route_index = bpf_map_lookup_elem(&redir_params_map, &key);
if (!route_index)
return TC_ACT_OK;
if (iph->protocol == IPPROTO_ICMP && *route_index != 0)
return bpf_redirect(*route_index, 0);
if (iph->protocol != IPPROTO_TCP)
return TC_ACT_OK;
struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
if ((void *)(tcph + 1) > data_end)
return TC_ACT_SHOT;
key = 1;
redir_ip = bpf_map_lookup_elem(&redir_params_map, &key);
if (!redir_ip)
return TC_ACT_OK;
key = 2;
redir_port = bpf_map_lookup_elem(&redir_params_map, &key);
if (!redir_port)
return TC_ACT_OK;
__be32 new_ip = bpf_htonl(*redir_ip);
__be16 new_port = bpf_htonl(*redir_port) >> 16;
__be32 old_ip = iph->daddr;
__be16 old_port = tcph->dest;
if (old_ip == new_ip || is_lan_ip(old_ip) || bpf_ntohs(old_port) == 53) {
return TC_ACT_OK;
}
struct redir_info p_key = {
.sip = iph->saddr,
.sport = tcph->source,
.dip = new_ip,
.dport = new_port,
};
if (tcph->syn && !tcph->ack) {
struct origin_info origin = {
.ip = old_ip,
.port = old_port,
};
bpf_map_update_elem(&pair_original_dst_map, &p_key, &origin, BPF_NOEXIST);
if (rewrite_ip(skb, new_ip, true) < 0) {
return TC_ACT_SHOT;
}
if (rewrite_port(skb, new_port, true) < 0) {
return TC_ACT_SHOT;
}
} else {
struct origin_info *origin = bpf_map_lookup_elem(&pair_original_dst_map, &p_key);
if (!origin) {
return TC_ACT_OK;
}
if (rewrite_ip(skb, new_ip, true) < 0) {
return TC_ACT_SHOT;
}
if (rewrite_port(skb, new_port, true) < 0) {
return TC_ACT_SHOT;
}
}
return TC_ACT_OK;
}
SEC("tc_mihomo_auto_redir_egress")
int tc_redir_egress_func(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
__u32 key = 0, *redir_ip, *redir_port; // *mihomo_mark
// mihomo_mark = bpf_map_lookup_elem(&redir_params_map, &key);
// if (mihomo_mark && *mihomo_mark != 0 && *mihomo_mark == skb->mark)
// return TC_ACT_OK;
struct iphdr *iph = (struct iphdr *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return TC_ACT_OK;
if (iph->protocol != IPPROTO_TCP)
return TC_ACT_OK;
struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
if ((void *)(tcph + 1) > data_end)
return TC_ACT_SHOT;
key = 1;
redir_ip = bpf_map_lookup_elem(&redir_params_map, &key);
if (!redir_ip)
return TC_ACT_OK;
key = 2;
redir_port = bpf_map_lookup_elem(&redir_params_map, &key);
if (!redir_port)
return TC_ACT_OK;
__be32 new_ip = bpf_htonl(*redir_ip);
__be16 new_port = bpf_htonl(*redir_port) >> 16;
__be32 old_ip = iph->saddr;
__be16 old_port = tcph->source;
if (old_ip != new_ip || old_port != new_port) {
return TC_ACT_OK;
}
struct redir_info p_key = {
.sip = iph->daddr,
.sport = tcph->dest,
.dip = iph->saddr,
.dport = tcph->source,
};
struct origin_info *origin = bpf_map_lookup_elem(&pair_original_dst_map, &p_key);
if (!origin) {
return TC_ACT_OK;
}
if (tcph->fin && tcph->ack) {
bpf_map_delete_elem(&pair_original_dst_map, &p_key);
}
if (rewrite_ip(skb, origin->ip, false) < 0) {
return TC_ACT_SHOT;
}
if (rewrite_port(skb, origin->port, false) < 0) {
return TC_ACT_SHOT;
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";

View File

@@ -1,103 +0,0 @@
#include <stdbool.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
//#include <linux/tcp.h>
//#include <linux/udp.h>
#include <linux/pkt_cls.h>
#include "bpf_endian.h"
#include "bpf_helpers.h"
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u32);
__uint(max_entries, 2);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} tc_params_map SEC(".maps");
static __always_inline bool is_lan_ip(__be32 addr) {
if (addr == 0xffffffff)
return true;
__u8 fist = (__u8)(addr & 0xff);
if (fist == 127 || fist == 10)
return true;
__u8 second = (__u8)((addr >> 8) & 0xff);
if (fist == 172 && second >= 16 && second <= 31)
return true;
if (fist == 192 && second == 168)
return true;
return false;
}
SEC("tc_mihomo_redirect_to_tun")
int tc_tun_func(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto == bpf_htons(ETH_P_ARP))
return TC_ACT_OK;
__u32 key = 0, *mihomo_mark, *tun_ifindex;
mihomo_mark = bpf_map_lookup_elem(&tc_params_map, &key);
if (!mihomo_mark)
return TC_ACT_OK;
if (skb->mark == *mihomo_mark)
return TC_ACT_OK;
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
struct iphdr *iph = (struct iphdr *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return TC_ACT_OK;
if (iph->protocol == IPPROTO_ICMP)
return TC_ACT_OK;
__be32 daddr = iph->daddr;
if (is_lan_ip(daddr))
return TC_ACT_OK;
// if (iph->protocol == IPPROTO_TCP) {
// struct tcphdr *tcph = (struct tcphdr *)(iph + 1);
// if ((void *)(tcph + 1) > data_end)
// return TC_ACT_OK;
//
// __u16 source = bpf_ntohs(tcph->source);
// if (source == 22 || source == 80 || source == 443 || source == 8080 || source == 8443 || source == 9090 || (source >= 7890 && source <= 7895))
// return TC_ACT_OK;
// } else if (iph->protocol == IPPROTO_UDP) {
// struct udphdr *udph = (struct udphdr *)(iph + 1);
// if ((void *)(udph + 1) > data_end)
// return TC_ACT_OK;
//
// __u16 source = bpf_ntohs(udph->source);
// if (source == 53 || (source >= 135 && source <= 139))
// return TC_ACT_OK;
// }
}
key = 1;
tun_ifindex = bpf_map_lookup_elem(&tc_params_map, &key);
if (!tun_ifindex)
return TC_ACT_OK;
//return bpf_redirect(*tun_ifindex, BPF_F_INGRESS); // __bpf_rx_skb
return bpf_redirect(*tun_ifindex, 0); // __bpf_tx_skb / __dev_xmit_skb
}
char _license[] SEC("license") = "GPL";

View File

@@ -1,13 +0,0 @@
package byteorder
import (
"net"
)
// NetIPv4ToHost32 converts an net.IP to a uint32 in host byte order. ip
// must be a IPv4 address, otherwise the function will panic.
func NetIPv4ToHost32(ip net.IP) uint32 {
ipv4 := ip.To4()
_ = ipv4[3] // Assert length of ipv4.
return Native.Uint32(ipv4)
}

View File

@@ -1,12 +0,0 @@
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
package byteorder
import "encoding/binary"
var Native binary.ByteOrder = binary.BigEndian
func HostToNetwork16(u uint16) uint16 { return u }
func HostToNetwork32(u uint32) uint32 { return u }
func NetworkToHost16(u uint16) uint16 { return u }
func NetworkToHost32(u uint32) uint32 { return u }

View File

@@ -1,15 +0,0 @@
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64
package byteorder
import (
"encoding/binary"
"math/bits"
)
var Native binary.ByteOrder = binary.LittleEndian
func HostToNetwork16(u uint16) uint16 { return bits.ReverseBytes16(u) }
func HostToNetwork32(u uint32) uint32 { return bits.ReverseBytes32(u) }
func NetworkToHost16(u uint16) uint16 { return bits.ReverseBytes16(u) }
func NetworkToHost32(u uint32) uint32 { return bits.ReverseBytes32(u) }

View File

@@ -1,33 +0,0 @@
package ebpf
import (
"net/netip"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
type TcEBpfProgram struct {
pros []C.EBpf
rawNICs []string
}
func (t *TcEBpfProgram) RawNICs() []string {
return t.rawNICs
}
func (t *TcEBpfProgram) Close() {
for _, p := range t.pros {
p.Close()
}
}
func (t *TcEBpfProgram) Lookup(srcAddrPort netip.AddrPort) (addr socks5.Addr, err error) {
for _, p := range t.pros {
addr, err = p.Lookup(srcAddrPort)
if err == nil {
return
}
}
return
}

View File

@@ -1,137 +0,0 @@
//go:build !android
package ebpf
import (
"fmt"
"net/netip"
"github.com/metacubex/mihomo/common/cmd"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ebpf/redir"
"github.com/metacubex/mihomo/component/ebpf/tc"
C "github.com/metacubex/mihomo/constant"
"github.com/sagernet/netlink"
)
func GetAutoDetectInterface() (string, error) {
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
if err != nil {
return "", err
}
for _, route := range routes {
if route.Dst == nil {
lk, err := netlink.LinkByIndex(route.LinkIndex)
if err != nil {
return "", err
}
if lk.Type() == "tuntap" {
continue
}
return lk.Attrs().Name, nil
}
}
return "", fmt.Errorf("interface not found")
}
// NewTcEBpfProgram new redirect to tun ebpf program
func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) {
tunIface, err := netlink.LinkByName(tunName)
if err != nil {
return nil, fmt.Errorf("lookup network iface %q: %w", tunName, err)
}
tunIndex := uint32(tunIface.Attrs().Index)
dialer.DefaultRoutingMark.Store(C.MihomoTrafficMark)
ifMark := uint32(dialer.DefaultRoutingMark.Load())
var pros []C.EBpf
for _, ifaceName := range ifaceNames {
iface, err := netlink.LinkByName(ifaceName)
if err != nil {
return nil, fmt.Errorf("lookup network iface %q: %w", ifaceName, err)
}
if iface.Attrs().OperState != netlink.OperUp {
return nil, fmt.Errorf("network iface %q is down", ifaceName)
}
attrs := iface.Attrs()
index := attrs.Index
tcPro := tc.NewEBpfTc(ifaceName, index, ifMark, tunIndex)
if err = tcPro.Start(); err != nil {
return nil, err
}
pros = append(pros, tcPro)
}
systemSetting(ifaceNames...)
return &TcEBpfProgram{pros: pros, rawNICs: ifaceNames}, nil
}
// NewRedirEBpfProgram new auto redirect ebpf program
func NewRedirEBpfProgram(ifaceNames []string, redirPort uint16, defaultRouteInterfaceName string) (*TcEBpfProgram, error) {
defaultRouteInterface, err := netlink.LinkByName(defaultRouteInterfaceName)
if err != nil {
return nil, fmt.Errorf("lookup network iface %q: %w", defaultRouteInterfaceName, err)
}
defaultRouteIndex := uint32(defaultRouteInterface.Attrs().Index)
var pros []C.EBpf
for _, ifaceName := range ifaceNames {
iface, err := netlink.LinkByName(ifaceName)
if err != nil {
return nil, fmt.Errorf("lookup network iface %q: %w", ifaceName, err)
}
attrs := iface.Attrs()
index := attrs.Index
addrs, err := netlink.AddrList(iface, netlink.FAMILY_V4)
if err != nil {
return nil, fmt.Errorf("lookup network iface %q address: %w", ifaceName, err)
}
if len(addrs) == 0 {
return nil, fmt.Errorf("network iface %q does not contain any ipv4 addresses", ifaceName)
}
address, _ := netip.AddrFromSlice(addrs[0].IP)
redirAddrPort := netip.AddrPortFrom(address, redirPort)
redirPro := redir.NewEBpfRedirect(ifaceName, index, 0, defaultRouteIndex, redirAddrPort)
if err = redirPro.Start(); err != nil {
return nil, err
}
pros = append(pros, redirPro)
}
systemSetting(ifaceNames...)
return &TcEBpfProgram{pros: pros, rawNICs: ifaceNames}, nil
}
func systemSetting(ifaceNames ...string) {
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1")
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.forwarding=1")
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_local=1")
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_redirects=1")
_, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.rp_filter=0")
for _, ifaceName := range ifaceNames {
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.forwarding=1", ifaceName))
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_local=1", ifaceName))
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_redirects=1", ifaceName))
_, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.rp_filter=0", ifaceName))
}
}

View File

@@ -1,21 +0,0 @@
//go:build !linux || android
package ebpf
import (
"fmt"
)
// NewTcEBpfProgram new ebpf tc program
func NewTcEBpfProgram(_ []string, _ string) (*TcEBpfProgram, error) {
return nil, fmt.Errorf("system not supported")
}
// NewRedirEBpfProgram new ebpf redirect program
func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) {
return nil, fmt.Errorf("system not supported")
}
func GetAutoDetectInterface() (string, error) {
return "", fmt.Errorf("system not supported")
}

View File

@@ -1,216 +0,0 @@
//go:build linux
package redir
import (
"encoding/binary"
"fmt"
"io"
"net"
"net/netip"
"os"
"path/filepath"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/rlimit"
"github.com/sagernet/netlink"
"golang.org/x/sys/unix"
"github.com/metacubex/mihomo/component/ebpf/byteorder"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/redir.c
const (
mapKey1 uint32 = 0
mapKey2 uint32 = 1
mapKey3 uint32 = 2
)
type EBpfRedirect struct {
objs io.Closer
originMap *ebpf.Map
qdisc netlink.Qdisc
filter netlink.Filter
filterEgress netlink.Filter
ifName string
ifIndex int
ifMark uint32
rtIndex uint32
redirIp uint32
redirPort uint16
bpfPath string
}
func NewEBpfRedirect(ifName string, ifIndex int, ifMark uint32, routeIndex uint32, redirAddrPort netip.AddrPort) *EBpfRedirect {
return &EBpfRedirect{
ifName: ifName,
ifIndex: ifIndex,
ifMark: ifMark,
rtIndex: routeIndex,
redirIp: binary.BigEndian.Uint32(redirAddrPort.Addr().AsSlice()),
redirPort: redirAddrPort.Port(),
}
}
func (e *EBpfRedirect) Start() error {
if err := rlimit.RemoveMemlock(); err != nil {
return fmt.Errorf("remove memory lock: %w", err)
}
e.bpfPath = filepath.Join(C.BpfFSPath, e.ifName)
if err := os.MkdirAll(e.bpfPath, os.ModePerm); err != nil {
return fmt.Errorf("failed to create bpf fs subpath: %w", err)
}
var objs bpfObjects
if err := loadBpfObjects(&objs, &ebpf.CollectionOptions{
Maps: ebpf.MapOptions{
PinPath: e.bpfPath,
},
}); err != nil {
e.Close()
return fmt.Errorf("loading objects: %w", err)
}
e.objs = &objs
e.originMap = objs.bpfMaps.PairOriginalDstMap
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey1, e.rtIndex, ebpf.UpdateAny); err != nil {
e.Close()
return fmt.Errorf("storing objects: %w", err)
}
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey2, e.redirIp, ebpf.UpdateAny); err != nil {
e.Close()
return fmt.Errorf("storing objects: %w", err)
}
if err := objs.bpfMaps.RedirParamsMap.Update(mapKey3, uint32(e.redirPort), ebpf.UpdateAny); err != nil {
e.Close()
return fmt.Errorf("storing objects: %w", err)
}
attrs := netlink.QdiscAttrs{
LinkIndex: e.ifIndex,
Handle: netlink.MakeHandle(0xffff, 0),
Parent: netlink.HANDLE_CLSACT,
}
qdisc := &netlink.GenericQdisc{
QdiscAttrs: attrs,
QdiscType: "clsact",
}
e.qdisc = qdisc
if err := netlink.QdiscAdd(qdisc); err != nil {
if os.IsExist(err) {
_ = netlink.QdiscDel(qdisc)
err = netlink.QdiscAdd(qdisc)
}
if err != nil {
e.Close()
return fmt.Errorf("cannot add clsact qdisc: %w", err)
}
}
filterAttrs := netlink.FilterAttrs{
LinkIndex: e.ifIndex,
Parent: netlink.HANDLE_MIN_INGRESS,
Handle: netlink.MakeHandle(0, 1),
Protocol: unix.ETH_P_IP,
Priority: 0,
}
filter := &netlink.BpfFilter{
FilterAttrs: filterAttrs,
Fd: objs.bpfPrograms.TcRedirIngressFunc.FD(),
Name: "mihomo-redir-ingress-" + e.ifName,
DirectAction: true,
}
if err := netlink.FilterAdd(filter); err != nil {
e.Close()
return fmt.Errorf("cannot attach ebpf object to filter ingress: %w", err)
}
e.filter = filter
filterAttrsEgress := netlink.FilterAttrs{
LinkIndex: e.ifIndex,
Parent: netlink.HANDLE_MIN_EGRESS,
Handle: netlink.MakeHandle(0, 1),
Protocol: unix.ETH_P_IP,
Priority: 0,
}
filterEgress := &netlink.BpfFilter{
FilterAttrs: filterAttrsEgress,
Fd: objs.bpfPrograms.TcRedirEgressFunc.FD(),
Name: "mihomo-redir-egress-" + e.ifName,
DirectAction: true,
}
if err := netlink.FilterAdd(filterEgress); err != nil {
e.Close()
return fmt.Errorf("cannot attach ebpf object to filter egress: %w", err)
}
e.filterEgress = filterEgress
return nil
}
func (e *EBpfRedirect) Close() {
if e.filter != nil {
_ = netlink.FilterDel(e.filter)
}
if e.filterEgress != nil {
_ = netlink.FilterDel(e.filterEgress)
}
if e.qdisc != nil {
_ = netlink.QdiscDel(e.qdisc)
}
if e.objs != nil {
_ = e.objs.Close()
}
_ = os.Remove(filepath.Join(e.bpfPath, "redir_params_map"))
_ = os.Remove(filepath.Join(e.bpfPath, "pair_original_dst_map"))
}
func (e *EBpfRedirect) Lookup(srcAddrPort netip.AddrPort) (socks5.Addr, error) {
rAddr := srcAddrPort.Addr().Unmap()
if rAddr.Is6() {
return nil, fmt.Errorf("remote address is ipv6")
}
srcIp := binary.BigEndian.Uint32(rAddr.AsSlice())
scrPort := srcAddrPort.Port()
key := bpfRedirInfo{
Sip: byteorder.HostToNetwork32(srcIp),
Sport: byteorder.HostToNetwork16(scrPort),
Dip: byteorder.HostToNetwork32(e.redirIp),
Dport: byteorder.HostToNetwork16(e.redirPort),
}
origin := bpfOriginInfo{}
err := e.originMap.Lookup(key, &origin)
if err != nil {
return nil, err
}
addr := make([]byte, net.IPv4len+3)
addr[0] = socks5.AtypIPv4
binary.BigEndian.PutUint32(addr[1:1+net.IPv4len], byteorder.NetworkToHost32(origin.Ip)) // big end
binary.BigEndian.PutUint16(addr[1+net.IPv4len:3+net.IPv4len], byteorder.NetworkToHost16(origin.Port)) // big end
return addr, nil
}

View File

@@ -1,139 +0,0 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
package redir
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type bpfOriginInfo struct {
Ip uint32
Port uint16
Pad uint16
}
type bpfRedirInfo struct {
Sip uint32
Dip uint32
Sport uint16
Dport uint16
}
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
TcRedirEgressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_egress_func"`
TcRedirIngressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_ingress_func"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
PairOriginalDstMap *ebpf.MapSpec `ebpf:"pair_original_dst_map"`
RedirParamsMap *ebpf.MapSpec `ebpf:"redir_params_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
PairOriginalDstMap *ebpf.Map `ebpf:"pair_original_dst_map"`
RedirParamsMap *ebpf.Map `ebpf:"redir_params_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.PairOriginalDstMap,
m.RedirParamsMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
TcRedirEgressFunc *ebpf.Program `ebpf:"tc_redir_egress_func"`
TcRedirIngressFunc *ebpf.Program `ebpf:"tc_redir_ingress_func"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.TcRedirEgressFunc,
p.TcRedirIngressFunc,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed bpf_bpfeb.o
var _BpfBytes []byte

Binary file not shown.

View File

@@ -1,139 +0,0 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64
package redir
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type bpfOriginInfo struct {
Ip uint32
Port uint16
Pad uint16
}
type bpfRedirInfo struct {
Sip uint32
Dip uint32
Sport uint16
Dport uint16
}
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
TcRedirEgressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_egress_func"`
TcRedirIngressFunc *ebpf.ProgramSpec `ebpf:"tc_redir_ingress_func"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
PairOriginalDstMap *ebpf.MapSpec `ebpf:"pair_original_dst_map"`
RedirParamsMap *ebpf.MapSpec `ebpf:"redir_params_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
PairOriginalDstMap *ebpf.Map `ebpf:"pair_original_dst_map"`
RedirParamsMap *ebpf.Map `ebpf:"redir_params_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.PairOriginalDstMap,
m.RedirParamsMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
TcRedirEgressFunc *ebpf.Program `ebpf:"tc_redir_egress_func"`
TcRedirIngressFunc *ebpf.Program `ebpf:"tc_redir_ingress_func"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.TcRedirEgressFunc,
p.TcRedirIngressFunc,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed bpf_bpfel.o
var _BpfBytes []byte

Binary file not shown.

View File

@@ -1,120 +0,0 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
package tc
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
TcTunFunc *ebpf.ProgramSpec `ebpf:"tc_tun_func"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
TcParamsMap *ebpf.MapSpec `ebpf:"tc_params_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
TcParamsMap *ebpf.Map `ebpf:"tc_params_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.TcParamsMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
TcTunFunc *ebpf.Program `ebpf:"tc_tun_func"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.TcTunFunc,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed bpf_bpfeb.o
var _BpfBytes []byte

Binary file not shown.

View File

@@ -1,120 +0,0 @@
//
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64
package tc
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
TcTunFunc *ebpf.ProgramSpec `ebpf:"tc_tun_func"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
TcParamsMap *ebpf.MapSpec `ebpf:"tc_params_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
TcParamsMap *ebpf.Map `ebpf:"tc_params_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.TcParamsMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
TcTunFunc *ebpf.Program `ebpf:"tc_tun_func"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.TcTunFunc,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed bpf_bpfel.o
var _BpfBytes []byte

Binary file not shown.

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